diff --git a/applets/digital-clock/plugin/timezonemodel.cpp b/applets/digital-clock/plugin/timezonemodel.cpp index efd4eb2e..60977464 100644 --- a/applets/digital-clock/plugin/timezonemodel.cpp +++ b/applets/digital-clock/plugin/timezonemodel.cpp @@ -1,234 +1,233 @@ /*************************************************************************** * Copyright (C) 2014 Kai Uwe Broulik * * Copyright (C) 2014 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 "timezonemodel.h" #include "timezonesi18n.h" #include #include #include #include TimeZoneFilterProxy::TimeZoneFilterProxy(QObject *parent) : QSortFilterProxyModel(parent) { m_stringMatcher.setCaseSensitivity(Qt::CaseInsensitive); } bool TimeZoneFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (!sourceModel() || m_filterString.isEmpty()) { return true; } const QString city = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::CityRole).toString(); const QString region = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::RegionRole).toString(); const QString comment = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::CommentRole).toString(); if (m_stringMatcher.indexIn(city) != -1 || m_stringMatcher.indexIn(region) != -1 || m_stringMatcher.indexIn(comment) != -1) { return true; } return false; } void TimeZoneFilterProxy::setFilterString(const QString &filterString) { m_filterString = filterString; m_stringMatcher.setPattern(filterString); emit filterStringChanged(); invalidate(); } //============================================================================= TimeZoneModel::TimeZoneModel(QObject *parent) : QAbstractListModel(parent), m_timezonesI18n(new TimezonesI18n(this)) { update(); } TimeZoneModel::~TimeZoneModel() { } int TimeZoneModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_data.count(); } QVariant TimeZoneModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { TimeZoneData currentData = m_data.at(index.row()); switch(role) { case TimeZoneIdRole: return currentData.id; case RegionRole: return currentData.region; case CityRole: return currentData.city; case CommentRole: return currentData.comment; case CheckedRole: return currentData.checked; } } return QVariant(); } bool TimeZoneModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || value.isNull()) { return false; } if (role == CheckedRole) { m_data[index.row()].checked = value.toBool(); emit dataChanged(index, index); if (m_data[index.row()].checked) { m_selectedTimeZones.append(m_data[index.row()].id); m_offsetData.insert(m_data[index.row()].id, m_data[index.row()].offsetFromUtc); } else { m_selectedTimeZones.removeAll(m_data[index.row()].id); m_offsetData.remove(m_data[index.row()].id); } sortTimeZones(); emit selectedTimeZonesChanged(); return true; } return false; } void TimeZoneModel::update() { beginResetModel(); m_data.clear(); QTimeZone localZone = QTimeZone(QTimeZone::systemTimeZoneId()); QStringList data = QString::fromUtf8(localZone.id()).split(QLatin1Char('/')); TimeZoneData local; local.id = QStringLiteral("Local"); local.region = i18nc("This means \"Local Timezone\"", "Local"); local.city = m_timezonesI18n->i18nCity(data.last()); local.comment = i18n("Your system time zone"); local.checked = false; m_data.append(local); QStringList cities; QHash zonesByCity; QList systemTimeZones = QTimeZone::availableTimeZoneIds(); for (auto it = systemTimeZones.constBegin(); it != systemTimeZones.constEnd(); ++it) { const QTimeZone zone(*it); - const QString continentCity = zone.id(); const QStringList splitted = QString::fromUtf8(zone.id()).split(QStringLiteral("/")); // CITY | COUNTRY | CONTINENT const QString key = QStringLiteral("%1|%2|%3").arg(splitted.last(), QLocale::countryToString(zone.country()), splitted.first()); cities.append(key); zonesByCity.insert(key, zone); } cities.sort(Qt::CaseInsensitive); Q_FOREACH (const QString &key, cities) { const QTimeZone timeZone = zonesByCity.value(key); QString comment = timeZone.comment(); if (!comment.isEmpty()) { comment = i18n(comment.toUtf8()); } QStringList cityCountryContinent = key.split(QLatin1Char('|')); TimeZoneData newData; newData.id = timeZone.id(); newData.region = timeZone.country() == QLocale::AnyCountry ? QString() : m_timezonesI18n->i18nContinents(cityCountryContinent.at(2)) + QLatin1Char('/') + m_timezonesI18n->i18nCountry(timeZone.country()); newData.city = m_timezonesI18n->i18nCity(cityCountryContinent.at(0)); newData.comment = comment; newData.checked = false; newData.offsetFromUtc = timeZone.offsetFromUtc(QDateTime::currentDateTimeUtc()); m_data.append(newData); } endResetModel(); } void TimeZoneModel::setSelectedTimeZones(const QStringList &selectedTimeZones) { m_selectedTimeZones = selectedTimeZones; for (int i = 0; i < m_data.size(); i++) { if (m_selectedTimeZones.contains(m_data.at(i).id)) { m_data[i].checked = true; m_offsetData.insert(m_data[i].id, m_data[i].offsetFromUtc); QModelIndex index = createIndex(i, 0); emit dataChanged(index, index); } } sortTimeZones(); } void TimeZoneModel::selectLocalTimeZone() { m_data[0].checked = true; QModelIndex index = createIndex(0, 0); emit dataChanged(index, index); m_selectedTimeZones << m_data[0].id; emit selectedTimeZonesChanged(); } QHash TimeZoneModel::roleNames() const { return QHash({ {TimeZoneIdRole, "timeZoneId"}, {RegionRole, "region"}, {CityRole, "city"}, {CommentRole, "comment"}, {CheckedRole, "checked"} }); } void TimeZoneModel::sortTimeZones() { std::sort(m_selectedTimeZones.begin(), m_selectedTimeZones.end(), [this](const QString &a, const QString &b) { return m_offsetData.value(a) < m_offsetData.value(b); }); } diff --git a/applets/notifications/plugin/notificationshelper.h b/applets/notifications/plugin/notificationshelper.h index 066048e8..1c92c06e 100644 --- a/applets/notifications/plugin/notificationshelper.h +++ b/applets/notifications/plugin/notificationshelper.h @@ -1,95 +1,95 @@ /* Copyright (C) 2014 Martin Klapetek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef NOTIFICATIONSHELPER_H #define NOTIFICATIONSHELPER_H #include #include #include #include class QQuickWindow; class QTimer; class QReadWriteLock; class NotificationsHelper : public QObject { Q_OBJECT Q_PROPERTY(PositionOnScreen popupLocation MEMBER m_popupLocation WRITE setPopupLocation NOTIFY popupLocationChanged) - Q_ENUMS(PositionOnScreen) public: enum PositionOnScreen { Default, // Follows the panel TopLeft, TopCenter, TopRight, Left, Center, Right, BottomLeft, BottomCenter, BottomRight }; + Q_ENUM(PositionOnScreen) NotificationsHelper(QObject *parent = 0); ~NotificationsHelper() override; Q_INVOKABLE void addNotificationPopup(QObject *win); Q_INVOKABLE void closePopup(const QString &sourceName); Q_INVOKABLE void setPlasmoidScreenGeometry(const QRect &geometry); void setPopupLocation(PositionOnScreen popupLocation); /** * Fills the popup with data from notificationData * and puts the popup on proper place on screen. * If there's no space on screen for the notification, * it's queued and displayed as soon as there's space for it */ Q_INVOKABLE void displayNotification(const QVariantMap ¬ificationData); Q_SIGNALS: void popupLocationChanged(); // void plasmoidScreenChanged(); private Q_SLOTS: void onPopupShown(); void onPopupClosed(); void processQueues(); void processShow(); void processHide(); private: void repositionPopups(); QList m_popupsOnScreen; QList m_availablePopups; QHash m_sourceMap; QRect m_plasmoidScreen; PositionOnScreen m_popupLocation; int m_offset; bool m_busy; QList m_hideQueue; QList m_showQueue; QReadWriteLock *m_mutex; QTimer *m_dispatchTimer; }; #endif // NOTIFICATIONSHELPER_H diff --git a/appmenu/appmenu.cpp b/appmenu/appmenu.cpp index bb2e7f59..86017f8b 100644 --- a/appmenu/appmenu.cpp +++ b/appmenu/appmenu.cpp @@ -1,420 +1,420 @@ /* This file is part of the KDE project. Copyright (c) 2011 Lionel Chauvin Copyright (c) 2011,2012 Cédric Bellegarde Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "appmenu.h" #include "kdbusimporter.h" #include "menuimporteradaptor.h" #include "appmenuadaptor.h" #include "appmenu_dbus.h" #if 0 #include "topmenubar.h" #endif #include "verticalmenu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(AppMenuFactory, "appmenu.json", registerPlugin();) AppMenuModule::AppMenuModule(QObject* parent, const QList&) : KDEDModule(parent), m_parent(parent), m_menuImporter(0), m_appmenuDBus(new AppmenuDBus(parent)), m_menubar(0), m_menu(0), m_screenTimer(new QTimer(this)), m_waitingAction(0), m_currentScreen(-1) { reconfigure(); m_appmenuDBus->connectToBus(); m_currentScreen = currentScreen(); connect(m_appmenuDBus, &AppmenuDBus::appShowMenu, this, &AppMenuModule::slotShowMenu); connect(m_appmenuDBus, &AppmenuDBus::moduleReconfigure, this, &AppMenuModule::reconfigure); // transfer our signals to dbus connect(this, &AppMenuModule::showRequest, m_appmenuDBus, &AppmenuDBus::showRequest); connect(this, &AppMenuModule::menuAvailable, m_appmenuDBus, &AppmenuDBus::menuAvailable); connect(this, &AppMenuModule::clearMenus, m_appmenuDBus, &AppmenuDBus::clearMenus); connect(this, &AppMenuModule::menuHidden, m_appmenuDBus, &AppmenuDBus::menuHidden); connect(this, &AppMenuModule::WindowRegistered, m_appmenuDBus, &AppmenuDBus::WindowRegistered); connect(this, &AppMenuModule::WindowUnregistered, m_appmenuDBus, &AppmenuDBus::WindowUnregistered); } AppMenuModule::~AppMenuModule() { emit clearMenus(); hideMenubar(); - if (m_menubar) { - delete m_menubar; - } +#if 0 + delete m_menubar; +#endif delete m_menuImporter; delete m_appmenuDBus; } void AppMenuModule::slotShowMenu(int x, int y, WId id) { static KDBusMenuImporter *importer = 0; if (!m_menuImporter) { return; } // If menu visible, hide it if (m_menu && m_menu->isVisible()) { m_menu->hide(); return; } //dbus call by user (for khotkey shortcut) if (x == -1 || y == -1) { // We do not know kwin button position, so tell kwin to show menu emit showRequest(KWindowSystem::self()->activeWindow()); return; } importer = getImporter(id); if (!importer) { return; } QMenu *menu = importer->menu(); // Window do not have menu if (!menu) { return; } m_menu = new VerticalMenu(); m_menu->setParentWid(id); // Populate menu foreach (QAction *action, menu->actions()) { m_menu->addAction(action); } m_menu->popup(QPoint(x, y)); // Activate waiting action if exist if (m_waitingAction) { m_menu->setActiveAction(m_waitingAction); m_waitingAction = 0; } connect(m_menu, &QMenu::aboutToHide, this, &AppMenuModule::slotAboutToHide); } void AppMenuModule::slotAboutToHide() { if (m_menu) { emit menuHidden(m_menu->parentWid()); m_menu->deleteLater(); m_menu = 0; } } // New window registered void AppMenuModule::slotWindowRegistered(WId id, const QString& service, const QDBusObjectPath& path) { KDBusMenuImporter* importer = m_importers.take(id); if (importer) { delete importer; } // Application already active so check if we need create menubar if ( m_menuStyle == QLatin1String("TopMenuBar") && id == KWindowSystem::self()->activeWindow()) { slotActiveWindowChanged(id); } else if (m_menuStyle == QLatin1String("ButtonVertical")) { KWindowInfo info(id, 0, NET::WM2WindowClass); // Tell Kwin menu is available emit menuAvailable(id); // FIXME: https://bugs.kde.org/show_bug.cgi?id=317926 if (info.windowClassName() != "kmix") { getImporter(id); } } // Send a signal on bus for others dbus interface registrars emit WindowRegistered(id, service, path); } // Window unregistered void AppMenuModule::slotWindowUnregistered(WId id) { KDBusMenuImporter* importer = m_importers.take(id); // Send a signal on bus for others dbus interface registrars emit WindowUnregistered(id); if (importer) { delete importer; } #if 0 if (m_menubar && m_menubar->parentWid() == id) { hideMenubar(); } #endif } // Keyboard activation requested, transmit it to menu void AppMenuModule::slotActionActivationRequested(QAction* a) { // If we have a topmenubar, activate action #if 0 if (m_menubar) { m_menubar->setActiveAction(a); m_menubar->show(); } else #endif { // else send request to kwin or others dbus interface registrars m_waitingAction = a; emit showRequest(KWindowSystem::self()->activeWindow()); } } // Current window change, update menubar // See comments in slotWindowRegistered() for why we get importers here void AppMenuModule::slotActiveWindowChanged(WId id) { KWindowInfo info(id, NET::WMWindowType); NET::WindowTypes mask = NET::AllTypesMask; m_currentScreen = currentScreen(); if (id == 0) {// Ignore root window return; } else if (info.windowType(mask) & NET::Dock) { // Hide immediatly menubar for docks (krunner) hideMenubar(); return; } if (!m_menuImporter->serviceExist(id)) { // No menu exist, check for another menu for application WId recursiveId = m_menuImporter->recursiveMenuId(id); if (recursiveId) { id = recursiveId; } } KDBusMenuImporter *importer = getImporter(id); if (!importer) { hideMenubar(); return; } #if 0 QMenu *menu = importer->menu(); if(menu) { showMenuBar(menu); m_menubar->setParentWid(id); } else { hideMenubar(); } #endif } void AppMenuModule::slotShowCurrentWindowMenu() { slotActiveWindowChanged(KWindowSystem::self()->activeWindow()); } void AppMenuModule::slotCurrentScreenChanged() { if (m_currentScreen != currentScreen()) { #if 0 if (m_menubar) { m_menubar->setParentWid(0); } #endif slotActiveWindowChanged(KWindowSystem::self()->activeWindow()); } } void AppMenuModule::slotBarNeedResize() { #if 0 if (m_menubar) { m_menubar->updateSize(); m_menubar->move(centeredMenubarPos()); } #endif } // reload settings void AppMenuModule::reconfigure() { KConfig config( QStringLiteral("kdeglobals"), KConfig::FullConfig ); KConfigGroup configGroup = config.group("Appmenu Style"); m_menuStyle = configGroup.readEntry("Style", "InApplication"); m_waitingAction = 0; // hide menubar if exist hideMenubar(); - if (m_menubar) { - delete m_menubar; - m_menubar = 0; - } +#if 0 + delete m_menubar; + m_menubar = 0; +#endif slotAboutToHide(); // hide vertical menu if exist // Disconnect all options specifics signals disconnect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModule::slotActiveWindowChanged); disconnect(KWindowSystem::self(), &KWindowSystem::workAreaChanged, this, &AppMenuModule::slotShowCurrentWindowMenu); disconnect(m_screenTimer, &QTimer::timeout, this, &AppMenuModule::slotCurrentScreenChanged); m_screenTimer->stop(); // Tell kwin to clean its titlebar emit clearMenus(); if (m_menuStyle == QLatin1String("InApplication")) { if (m_menuImporter) { delete m_menuImporter; m_menuImporter = 0; } return; } // Setup a menu importer if needed if (!m_menuImporter) { m_menuImporter = new MenuImporter(m_parent); connect(m_menuImporter, &MenuImporter::WindowRegistered, this, &AppMenuModule::slotWindowRegistered); connect(m_menuImporter, &MenuImporter::WindowUnregistered, this, &AppMenuModule::slotWindowUnregistered); m_menuImporter->connectToBus(); } if( m_menuStyle == QLatin1String("ButtonVertical") ) { foreach(WId id, m_menuImporter->ids()) { emit menuAvailable(id); } } // Setup top menubar if needed if (m_menuStyle == QLatin1String("TopMenuBar")) { #if 0 m_menubar = new TopMenuBar(); connect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId)), this, SLOT(slotActiveWindowChanged(WId))); connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(slotShowCurrentWindowMenu())); connect(m_screenTimer, SIGNAL(timeout()), this, SLOT(slotCurrentScreenChanged())); connect(m_menubar, SIGNAL(needResize()), SLOT(slotBarNeedResize())); m_screenTimer->start(1000); slotShowCurrentWindowMenu(); #endif } } KDBusMenuImporter* AppMenuModule::getImporter(WId id) { KDBusMenuImporter* importer = 0; if (m_importers.contains(id)) { // importer already exist importer = m_importers.value(id); } else if (m_menuImporter->serviceExist(id)) { // get importer importer = new KDBusMenuImporter(id, m_menuImporter->serviceForWindow(id), &m_icons, m_menuImporter->pathForWindow(id), this); if (importer) { QMetaObject::invokeMethod(importer, "updateMenu", Qt::DirectConnection); connect(importer, &DBusMenuImporter::actionActivationRequested, this, &AppMenuModule::slotActionActivationRequested); m_importers.insert(id, importer); } } return importer; } void AppMenuModule::showMenuBar(QMenu *menu) { #if 0 if (!menu) { return; } m_menubar->setMenu(menu); if (menu->actions().length()) { m_menubar->enableMouseTracking(); } #endif } void AppMenuModule::hideMenubar() { #if 0 if (!m_menubar) { return; } m_menubar->enableMouseTracking(false); if (m_menubar->isVisible()) { m_menubar->hide(); } #endif } int AppMenuModule::currentScreen() { KWindowInfo info(KWindowSystem::self()->activeWindow(), NET::WMGeometry); int x = info.geometry().x(); int y = info.geometry().y(); QDesktopWidget *desktop = QApplication::desktop(); return desktop->screenNumber(QPoint(x,y)); } QPoint AppMenuModule::centeredMenubarPos() { QDesktopWidget *desktop = QApplication::desktop(); m_currentScreen = currentScreen(); QRect screen = desktop->availableGeometry(m_currentScreen); #if 0 int x = screen.center().x() - m_menubar->sizeHint().width()/2; return QPoint(x, screen.topLeft().y()); #else return QPoint(screen.center().x(), screen.topLeft().y()); #endif } #include "appmenu.moc" diff --git a/components/shellprivate/interactiveconsole/interactiveconsole.cpp b/components/shellprivate/interactiveconsole/interactiveconsole.cpp index 12f0bb00..3abeac94 100644 --- a/components/shellprivate/interactiveconsole/interactiveconsole.cpp +++ b/components/shellprivate/interactiveconsole/interactiveconsole.cpp @@ -1,604 +1,604 @@ /* * Copyright 2009 Aaron Seigo * 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 "interactiveconsole.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 #include #include #include #include //TODO: // interative help? static const QString s_autosaveFileName(QStringLiteral("interactiveconsoleautosave.js")); static const QString s_kwinService = QStringLiteral("org.kde.KWin"); InteractiveConsole::InteractiveConsole(QWidget *parent) : QDialog(parent), m_splitter(new QSplitter(Qt::Vertical, this)), m_editorPart(0), m_editor(0), m_output(0), m_loadAction(KStandardAction::open(this, SLOT(openScriptFile()), this)), m_saveAction(KStandardAction::saveAs(this, SLOT(saveScript()), this)), m_clearAction(KStandardAction::clear(this, SLOT(clearEditor()), this)), m_executeAction(new QAction(QIcon::fromTheme(QStringLiteral("system-run")), i18n("&Execute"), this)), m_plasmaAction(new QAction(QIcon::fromTheme(QStringLiteral("plasma")), i18nc("Toolbar Button to switch to Plasma Scripting Mode", "Plasma"), this)), m_kwinAction(new QAction(QIcon::fromTheme(QStringLiteral("kwin")), i18nc("Toolbar Button to switch to KWin Scripting Mode", "KWin"), this)), m_snippetsMenu(new QMenu(i18n("Templates"), this)), m_fileDialog(0), m_closeWhenCompleted(false), m_mode(PlasmaConsole) { addAction(KStandardAction::close(this, SLOT(close()), this)); addAction(m_saveAction); addAction(m_clearAction); setWindowTitle(i18n("Desktop Shell Scripting Console")); setAttribute(Qt::WA_DeleteOnClose); //setButtons(QDialog::None); QWidget *widget = new QWidget(m_splitter); QVBoxLayout *editorLayout = new QVBoxLayout(widget); QLabel *label = new QLabel(i18n("Editor"), widget); QFont f = label->font(); f.setBold(true); label->setFont(f); editorLayout->addWidget(label); connect(m_snippetsMenu, &QMenu::aboutToShow, this, &InteractiveConsole::populateTemplatesMenu); QToolButton *loadTemplateButton = new QToolButton(this); loadTemplateButton->setPopupMode(QToolButton::InstantPopup); loadTemplateButton->setMenu(m_snippetsMenu); loadTemplateButton->setText(i18n("Load")); connect(loadTemplateButton, &QToolButton::triggered, this, &InteractiveConsole::loadTemplate); QToolButton *useTemplateButton = new QToolButton(this); useTemplateButton->setPopupMode(QToolButton::InstantPopup); useTemplateButton->setMenu(m_snippetsMenu); useTemplateButton->setText(i18n("Use")); connect(useTemplateButton, &QToolButton::triggered, this, &InteractiveConsole::useTemplate); QActionGroup *modeGroup = new QActionGroup(this); modeGroup->addAction(m_plasmaAction); modeGroup->addAction(m_kwinAction); m_plasmaAction->setCheckable(true); m_kwinAction->setCheckable(true); m_plasmaAction->setChecked(true); connect(modeGroup, &QActionGroup::triggered, this, &InteractiveConsole::modeSelectionChanged); KToolBar *toolBar = new KToolBar(this, true, false); toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBar->addAction(m_loadAction); toolBar->addAction(m_saveAction); toolBar->addAction(m_clearAction); toolBar->addAction(m_executeAction); toolBar->addAction(m_plasmaAction); toolBar->addAction(m_kwinAction); toolBar->addWidget(loadTemplateButton); toolBar->addWidget(useTemplateButton); editorLayout->addWidget(toolBar); - KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("KTextEditor/Document")); - foreach (const KService::Ptr service, offers) { + const KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("KTextEditor/Document")); + foreach (const KService::Ptr &service, offers) { m_editorPart = service->createInstance(widget); if (m_editorPart) { m_editorPart->setHighlightingMode(QStringLiteral("JavaScript/PlasmaDesktop")); KTextEditor::View * view = m_editorPart->createView(widget); view->setContextMenu(view->defaultContextMenu()); KTextEditor::ConfigInterface *config = qobject_cast(view); if (config) { config->setConfigValue(QStringLiteral("line-numbers"), true); config->setConfigValue(QStringLiteral("dynamic-word-wrap"), true); } editorLayout->addWidget(view); connect(m_editorPart, &KTextEditor::Document::textChanged, this, &InteractiveConsole::scriptTextChanged); break; } } if (!m_editorPart) { m_editor = new KTextEdit(widget); editorLayout->addWidget(m_editor); connect(m_editor, &QTextEdit::textChanged, this, &InteractiveConsole::scriptTextChanged); } m_splitter->addWidget(widget); widget = new QWidget(m_splitter); QVBoxLayout *outputLayout = new QVBoxLayout(widget); label = new QLabel(i18n("Output"), widget); f = label->font(); f.setBold(true); label->setFont(f); outputLayout->addWidget(label); KToolBar *outputToolBar = new KToolBar(widget, true, false); outputToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); QAction *clearOutputAction = KStandardAction::clear(this, SLOT(clearOutput()), this); outputToolBar->addAction(clearOutputAction); outputLayout->addWidget(outputToolBar); m_output = new QTextBrowser(widget); outputLayout->addWidget(m_output); m_splitter->addWidget(widget); QVBoxLayout *l = new QVBoxLayout(this); l->addWidget(m_splitter); KConfigGroup cg(KSharedConfig::openConfig(), "InteractiveConsole"); restoreGeometry(cg.readEntry("Geometry", QByteArray())); m_splitter->setStretchFactor(0, 10); m_splitter->restoreState(cg.readEntry("SplitterState", QByteArray())); scriptTextChanged(); connect(m_executeAction, &QAction::triggered, this, &InteractiveConsole::evaluateScript); m_executeAction->setShortcut(Qt::CTRL + Qt::Key_E); const QString autosave = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + s_autosaveFileName; if (QFile::exists(autosave)) { loadScript(autosave); } } InteractiveConsole::~InteractiveConsole() { KConfigGroup cg(KSharedConfig::openConfig(), "InteractiveConsole"); cg.writeEntry("Geometry", saveGeometry()); cg.writeEntry("SplitterState", m_splitter->saveState()); } void InteractiveConsole::setMode(const QString &mode) { if (mode.toLower() == QLatin1String("desktop")) { m_plasmaAction->setChecked(true); } else if (mode.toLower() == QLatin1String("windowmanager")) { m_kwinAction->setChecked(true); } } void InteractiveConsole::modeSelectionChanged() { if (m_plasmaAction->isChecked()) { m_mode = PlasmaConsole; } else if (m_kwinAction->isChecked()) { m_mode = KWinConsole; } emit modeChanged(); } QString InteractiveConsole::mode() const { if (m_mode == KWinConsole) { return QStringLiteral("windowmanager"); } return QStringLiteral("desktop"); } void InteractiveConsole::setScriptInterface(QObject *obj) { if (m_scriptEngine != obj) { if (m_scriptEngine) { disconnect(m_scriptEngine, 0, this, 0); } m_scriptEngine = obj; connect(m_scriptEngine, SIGNAL(print(QString)), this, SLOT(print(QString))); connect(m_scriptEngine, SIGNAL(printError(QString)), this, SLOT(print(QString))); emit scriptEngineChanged(); } } QObject *InteractiveConsole::scriptEngine() const { return m_scriptEngine; } void InteractiveConsole::loadScript(const QString &script) { if (m_editorPart) { m_editorPart->closeUrl(false); if (m_editorPart->openUrl(QUrl::fromLocalFile(script))) { m_editorPart->setHighlightingMode(QStringLiteral("JavaScript/PlasmaDesktop")); return; } } else { QFile file(KShell::tildeExpand(script)); if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { m_editor->setText(file.readAll()); return; } } m_output->append(i18n("Unable to load script file %1", script)); } void InteractiveConsole::showEvent(QShowEvent *) { if (m_editorPart) { m_editorPart->views().first()->setFocus(); } else { m_editor->setFocus(); } KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop()); emit visibleChanged(true); } void InteractiveConsole::closeEvent(QCloseEvent *event) { // need to save first! const QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + s_autosaveFileName; m_closeWhenCompleted = true; saveScript(QUrl::fromLocalFile(path)); QDialog::closeEvent(event); emit visibleChanged(false); } void InteractiveConsole::reject() { QDialog::reject(); close(); } void InteractiveConsole::print(const QString &string) { m_output->append(string); } void InteractiveConsole::scriptTextChanged() { const bool enable = m_editorPart ? !m_editorPart->isEmpty() : !m_editor->document()->isEmpty(); m_saveAction->setEnabled(enable); m_clearAction->setEnabled(enable); m_executeAction->setEnabled(enable); } void InteractiveConsole::openScriptFile() { delete m_fileDialog; m_fileDialog = new QFileDialog(); m_fileDialog->setAcceptMode(QFileDialog::AcceptOpen); m_fileDialog->setWindowTitle(i18n("Open Script File")); QStringList mimetypes; mimetypes << QStringLiteral("application/javascript"); m_fileDialog->setMimeTypeFilters(mimetypes); connect(m_fileDialog, &QDialog::finished, this, &InteractiveConsole::openScriptUrlSelected); m_fileDialog->show(); } void InteractiveConsole::openScriptUrlSelected(int result) { if (!m_fileDialog) { return; } if (result == QDialog::Accepted) { const QUrl url = m_fileDialog->selectedUrls().first(); if (!url.isEmpty()) { loadScriptFromUrl(url); } } m_fileDialog->deleteLater(); m_fileDialog = 0; } void InteractiveConsole::loadScriptFromUrl(const QUrl &url) { if (m_editorPart) { m_editorPart->closeUrl(false); m_editorPart->openUrl(url); m_editorPart->setHighlightingMode(QStringLiteral("JavaScript/PlasmaDesktop")); } else { m_editor->clear(); m_editor->setEnabled(false); if (m_job) { m_job.data()->kill(); } m_job = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); connect(m_job.data(), SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(scriptFileDataRecvd(KIO::Job*,QByteArray))); connect(m_job.data(), &KJob::result, this, &InteractiveConsole::reenableEditor); } } void InteractiveConsole::populateTemplatesMenu() { m_snippetsMenu->clear(); QMap sorted; const QString constraint = QStringLiteral("[X-Plasma-Shell] == '%1'") .arg(qApp->applicationName()); KService::List templates = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/LayoutTemplate"), constraint); foreach (const KService::Ptr &service, templates) { sorted.insert(service->name(), service); } QMapIterator it(sorted); KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); while (it.hasNext()) { it.next(); KPluginInfo info(it.value()); const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation , package.defaultPackageRoot() + '/' + info.pluginName() + '/'); if (!path.isEmpty()) { package.setPath(info.pluginName()); const QString scriptFile = package.filePath("mainscript"); if (!scriptFile.isEmpty()) { QAction *action = m_snippetsMenu->addAction(info.name()); action->setData(info.pluginName()); } } } } void InteractiveConsole::loadTemplate(QAction *action) { KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); const QString pluginName = action->data().toString(); const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation , package.defaultPackageRoot() + '/' + pluginName + '/'); if (!path.isEmpty()) { package.setPath(pluginName); const QString scriptFile = package.filePath("mainscript"); if (!scriptFile.isEmpty()) { loadScriptFromUrl(QUrl::fromLocalFile(scriptFile)); } } } void InteractiveConsole::useTemplate(QAction *action) { QString code("var template = loadTemplate('" + action->data().toString() + "')"); if (m_editorPart) { const QList views = m_editorPart->views(); if (views.isEmpty()) { m_editorPart->insertLines(m_editorPart->lines(), QStringList() << code); } else { KTextEditor::Cursor cursor = views.at(0)->cursorPosition(); m_editorPart->insertLines(cursor.line(), QStringList() << code); cursor.setLine(cursor.line() + 1); views.at(0)->setCursorPosition(cursor); } } else { m_editor->insertPlainText(code); } } void InteractiveConsole::scriptFileDataRecvd(KIO::Job *job, const QByteArray &data) { Q_ASSERT(m_editor); if (job == m_job.data()) { m_editor->insertPlainText(data); } } void InteractiveConsole::saveScript() { if (m_editorPart) { m_editorPart->documentSaveAs(); return; } delete m_fileDialog; m_fileDialog = new QFileDialog(); m_fileDialog->setAcceptMode(QFileDialog::AcceptSave); m_fileDialog->setWindowTitle(i18n("Save Script File")); QStringList mimetypes; mimetypes << QStringLiteral("application/javascript"); m_fileDialog->setMimeTypeFilters(mimetypes); connect(m_fileDialog, &QDialog::finished, this, &InteractiveConsole::saveScriptUrlSelected); m_fileDialog->show(); } void InteractiveConsole::saveScriptUrlSelected(int result) { if (!m_fileDialog) { return; } if (result == QDialog::Accepted) { const QUrl url = m_fileDialog->selectedUrls().first(); if (!url.isEmpty()) { saveScript(url); } } m_fileDialog->deleteLater(); m_fileDialog = 0; } void InteractiveConsole::saveScript(const QUrl &url) { //create the folder to save if doesn't exists QFileInfo info(url.path()); QDir dir; dir.mkpath(info.absoluteDir().absolutePath()); if (m_editorPart) { m_editorPart->saveAs(url); } else { m_editor->setEnabled(false); if (m_job) { m_job.data()->kill(); } m_job = KIO::put(url, -1, KIO::HideProgressInfo); connect(m_job.data(), SIGNAL(dataReq(KIO::Job*,QByteArray&)), this, SLOT(scriptFileDataReq(KIO::Job*,QByteArray&))); connect(m_job.data(), &KJob::result, this, &InteractiveConsole::reenableEditor); } } void InteractiveConsole::scriptFileDataReq(KIO::Job *job, QByteArray &data) { Q_ASSERT(m_editor); if (!m_job || m_job.data() != job) { return; } data.append(m_editor->toPlainText().toLocal8Bit()); m_job.clear(); } void InteractiveConsole::reenableEditor(KJob* job) { Q_ASSERT(m_editor); if (m_closeWhenCompleted && job->error() != 0) { close(); } m_closeWhenCompleted = false; m_editor->setEnabled(true); } void InteractiveConsole::evaluateScript() { //qDebug() << "evaluating" << m_editor->toPlainText(); const QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + s_autosaveFileName; saveScript(QUrl::fromLocalFile(path)); m_output->moveCursor(QTextCursor::End); QTextCursor cursor = m_output->textCursor(); m_output->setTextCursor(cursor); QTextCharFormat format; format.setFontWeight(QFont::Bold); format.setFontUnderline(true); if (cursor.position() > 0) { cursor.insertText(QStringLiteral("\n\n")); } QDateTime dt = QDateTime::currentDateTime(); cursor.insertText(i18n("Executing script at %1", QLocale().toString(dt))); format.setFontWeight(QFont::Normal); format.setFontUnderline(false); QTextBlockFormat block = cursor.blockFormat(); block.setLeftMargin(10); cursor.insertBlock(block, format); QTime t; t.start(); if (m_mode == PlasmaConsole) { if (m_scriptEngine) { const QString script = m_editorPart ? m_editorPart->text() : m_editor->toPlainText(); QMetaObject::invokeMethod(m_scriptEngine, "evaluateScript", Q_ARG(QString, script)); } } else if (m_mode == KWinConsole) { QDBusMessage message = QDBusMessage::createMethodCall(s_kwinService, QStringLiteral("/Scripting"), QString(), QStringLiteral("loadScript")); QList arguments; arguments << QVariant(path); message.setArguments(arguments); QDBusMessage reply = QDBusConnection::sessionBus().call(message); if (reply.type() == QDBusMessage::ErrorMessage) { print(reply.errorMessage()); } else { const int id = reply.arguments().first().toInt(); QDBusConnection::sessionBus().connect(s_kwinService, "/" + QString::number(id), QString(), QStringLiteral("print"), this, SLOT(print(QString))); QDBusConnection::sessionBus().connect(s_kwinService, "/" + QString::number(id), QString(), QStringLiteral("printError"), this, SLOT(print(QString))); message = QDBusMessage::createMethodCall(s_kwinService, "/" + QString::number(id), QString(), QStringLiteral("run")); reply = QDBusConnection::sessionBus().call(message); if (reply.type() == QDBusMessage::ErrorMessage) { print(reply.errorMessage()); } } } cursor.insertText(QStringLiteral("\n\n")); format.setFontWeight(QFont::Bold); // xgettext:no-c-format cursor.insertText(i18n("Runtime: %1ms", QString::number(t.elapsed())), format); block.setLeftMargin(0); cursor.insertBlock(block); m_output->ensureCursorVisible(); } void InteractiveConsole::clearEditor() { if (m_editorPart) { m_editorPart->clear(); } else { m_editor->clear(); } } void InteractiveConsole::clearOutput() { m_output->clear(); } diff --git a/components/shellprivate/widgetexplorer/kcategorizeditemsviewmodels.cpp b/components/shellprivate/widgetexplorer/kcategorizeditemsviewmodels.cpp index 2560d5da..2a1bebb6 100644 --- a/components/shellprivate/widgetexplorer/kcategorizeditemsviewmodels.cpp +++ b/components/shellprivate/widgetexplorer/kcategorizeditemsviewmodels.cpp @@ -1,253 +1,253 @@ /* * Copyright (C) 2007 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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/Lesser 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 "kcategorizeditemsviewmodels_p.h" #include #define COLUMN_COUNT 4 namespace KCategorizedItemsViewModels { // AbstractItem QString AbstractItem::name() const { return text(); } QString AbstractItem::id() const { QString plugin = data().toMap()[QStringLiteral("pluginName")].toString(); if (plugin.isEmpty()) { return name(); } return plugin; } QString AbstractItem::description() const { return QLatin1String(""); } bool AbstractItem::isFavorite() const { return passesFiltering(Filter(QStringLiteral("favorite"), true)); } int AbstractItem::running() const { return 0; } bool AbstractItem::matches(const QString &pattern) const { return name().contains(pattern, Qt::CaseInsensitive) || description().contains(pattern, Qt::CaseInsensitive); } // DefaultFilterModel DefaultFilterModel::DefaultFilterModel(QObject *parent) : QStandardItemModel(0, 1, parent) { setHeaderData(1, Qt::Horizontal, i18n("Filters")); connect(this, &QAbstractItemModel::modelReset, this, &DefaultFilterModel::countChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &DefaultFilterModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &DefaultFilterModel::countChanged); } QHash DefaultFilterModel::roleNames() const { static QHash newRoleNames; if (newRoleNames.isEmpty()) { newRoleNames = QAbstractItemModel::roleNames(); newRoleNames[FilterTypeRole] = "filterType"; newRoleNames[FilterDataRole] = "filterData"; newRoleNames[SeparatorRole] = "separator"; } return newRoleNames; } void DefaultFilterModel::addFilter(const QString &caption, const Filter &filter, const QIcon &icon) { QList newRow; QStandardItem *item = new QStandardItem(caption); item->setData(qVariantFromValue(filter)); if (!icon.isNull()) { item->setIcon(icon); } item->setData(filter.first, FilterTypeRole); item->setData(filter.second, FilterDataRole); newRow << item; appendRow(newRow); } void DefaultFilterModel::addSeparator(const QString &caption) { QList newRow; QStandardItem *item = new QStandardItem(caption); item->setEnabled(false); item->setData(true, SeparatorRole); newRow << item; appendRow(newRow); } QVariantHash DefaultFilterModel::get(int row) const { QModelIndex idx = index(row, 0); QVariantHash hash; - QHash::const_iterator i; - for (i = roleNames().constBegin(); i != roleNames().constEnd(); ++i) { + const QHash roles = roleNames(); + for (QHash::const_iterator i = roles.constBegin(); i != roles.constEnd(); ++i) { hash[i.value()] = data(idx, i.key()); } return hash; } // DefaultItemFilterProxyModel DefaultItemFilterProxyModel::DefaultItemFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } void DefaultItemFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { QStandardItemModel *model = qobject_cast(sourceModel); if (!model) { qWarning() << "Expecting a QStandardItemModel!"; return; } QSortFilterProxyModel::setSourceModel(model); connect(this, &QAbstractItemModel::modelReset, this, &DefaultItemFilterProxyModel::countChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &DefaultItemFilterProxyModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &DefaultItemFilterProxyModel::countChanged); } QAbstractItemModel *DefaultItemFilterProxyModel::sourceModel() const { return QSortFilterProxyModel::sourceModel(); } int DefaultItemFilterProxyModel::columnCount(const QModelIndex &index) const { Q_UNUSED(index); return COLUMN_COUNT; } QVariant DefaultItemFilterProxyModel::data(const QModelIndex &index, int role) const { return QSortFilterProxyModel::data(index, role); } bool DefaultItemFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QStandardItemModel *model = (QStandardItemModel *) sourceModel(); QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); AbstractItem *item = (AbstractItem *) model->itemFromIndex(index); //qDebug() << "ITEM " << (item ? "IS NOT " : "IS") << " NULL\n"; return item && (m_filter.first.isEmpty() || item->passesFiltering(m_filter)) && (m_searchPattern.isEmpty() || item->matches(m_searchPattern)); } QVariantHash DefaultItemFilterProxyModel::get(int row) const { QModelIndex idx = index(row, 0); QVariantHash hash; - QHash::const_iterator i; - for (i = roleNames().constBegin(); i != roleNames().constEnd(); ++i) { + const QHash roles = roleNames(); + for (QHash::const_iterator i = roles.constBegin(); i != roles.constEnd(); ++i) { hash[i.value()] = data(idx, i.key()); } return hash; } bool DefaultItemFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { return sourceModel()->data(left).toString().localeAwareCompare( sourceModel()->data(right).toString()) < 0; } void DefaultItemFilterProxyModel::setSearchTerm(const QString &pattern) { m_searchPattern = pattern; invalidateFilter(); emit searchTermChanged(pattern); } QString DefaultItemFilterProxyModel::searchTerm() const { return m_searchPattern; } void DefaultItemFilterProxyModel::setFilter(const Filter &filter) { m_filter = filter; invalidateFilter(); emit filterChanged(); } void DefaultItemFilterProxyModel::setFilterType(const QString type) { m_filter.first = type; invalidateFilter(); emit filterChanged(); } QString DefaultItemFilterProxyModel::filterType() const { return m_filter.first; } void DefaultItemFilterProxyModel::setFilterQuery(const QVariant query) { m_filter.second = query; invalidateFilter(); emit filterChanged(); } QVariant DefaultItemFilterProxyModel::filterQuery() const { return m_filter.second; } } diff --git a/components/shellprivate/widgetexplorer/widgetexplorer.cpp b/components/shellprivate/widgetexplorer/widgetexplorer.cpp index be5c9dc8..c2b38a87 100644 --- a/components/shellprivate/widgetexplorer/widgetexplorer.cpp +++ b/components/shellprivate/widgetexplorer/widgetexplorer.cpp @@ -1,485 +1,485 @@ /* * Copyright (C) 2007 by Ivan Cukic * Copyright (C) 2009 by Ana Cecília Martins * 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/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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/Lesser 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 "widgetexplorer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kcategorizeditemsviewmodels_p.h" #include "plasmaappletitemmodel_p.h" #include "openwidgetassistant_p.h" #include "config-workspace.h" using namespace KActivities; using namespace KCategorizedItemsViewModels; using namespace Plasma; WidgetAction::WidgetAction(QObject *parent) : QAction(parent) { } WidgetAction::WidgetAction(const QIcon &icon, const QString &text, QObject *parent) : QAction(icon, text, parent) { } class WidgetExplorerPrivate { public: WidgetExplorerPrivate(WidgetExplorer *w) : q(w), containment(0), itemModel(w), filterModel(w), activitiesConsumer(new KActivities::Consumer()) { QObject::connect(activitiesConsumer.data(), &Consumer::currentActivityChanged, q, [this] { initRunningApplets(); }); } void initFilters(); void initRunningApplets(); void containmentDestroyed(); void addContainment(Containment *containment); /** * Tracks a new running applet */ void appletAdded(Plasma::Applet *applet); /** * A running applet is no more */ void appletRemoved(Plasma::Applet *applet); WidgetExplorer *q; QString application; Plasma::Containment *containment; QHash runningApplets; // applet name => count //extra hash so we can look up the names of deleted applets QHash appletNames; QPointer openAssistant; KPackage::Package *package; PlasmaAppletItemModel itemModel; KCategorizedItemsViewModels::DefaultFilterModel filterModel; DefaultItemFilterProxyModel filterItemModel; QPointer newStuffDialog; QScopedPointer activitiesConsumer; }; void WidgetExplorerPrivate::initFilters() { filterModel.addFilter(i18n("All Widgets"), KCategorizedItemsViewModels::Filter(), QIcon::fromTheme(QStringLiteral("plasma"))); // Filters: Special filterModel.addFilter(i18n("Running"), KCategorizedItemsViewModels::Filter(QStringLiteral("running"), true), QIcon::fromTheme(QStringLiteral("dialog-ok"))); filterModel.addFilter(i18n("Uninstallable"), KCategorizedItemsViewModels::Filter(QStringLiteral("local"), true), QIcon::fromTheme(QStringLiteral("list-remove"))); filterModel.addSeparator(i18n("Categories:")); typedef QPair catPair; QMap categories; QSet existingCategories = itemModel.categories(); //foreach (const QString &category, Plasma::Applet::listCategories(application)) { QStringList cats; const QList list = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/Applet"), QStringLiteral("plasma/plasmoids")); - for (auto data : list) { + for (auto& data : list) { const KPluginInfo info(data); if (!info.isValid()) { continue; } if (info.property(QStringLiteral("NoDisplay")).toBool() || info.category() == QLatin1String("Containments") || info.category().isEmpty()) { // we don't want to show the hidden category continue; } const QString c = info.category(); if (-1 == cats.indexOf(c)) { cats << c; } } qWarning() << "TODO: port listCategories()"; foreach (const QString &category, cats) { const QString lowerCaseCat = category.toLower(); if (existingCategories.contains(lowerCaseCat)) { const QString trans = i18nd("libplasma5", category.toLocal8Bit()); categories.insert(trans.toLower(), qMakePair(trans, lowerCaseCat)); } } foreach (const catPair &category, categories) { filterModel.addFilter(category.first, KCategorizedItemsViewModels::Filter(QStringLiteral("category"), category.second)); } } QObject *WidgetExplorer::widgetsModel() const { return &d->filterItemModel; } QObject *WidgetExplorer::filterModel() const { return &d->filterModel; } QList WidgetExplorer::widgetsMenuActions() { QList actionList; QSignalMapper *mapper = new QSignalMapper(this); QObject::connect(mapper, SIGNAL(mapped(QString)), this, SLOT(downloadWidgets(QString))); WidgetAction *action = new WidgetAction(QIcon::fromTheme(QStringLiteral("applications-internet")), i18n("Download New Plasma Widgets"), this); QObject::connect(action, SIGNAL(triggered(bool)), mapper, SLOT(map())); mapper->setMapping(action, QString()); actionList << action; action = new WidgetAction(this); action->setSeparator(true); actionList << action; action = new WidgetAction(QIcon::fromTheme(QStringLiteral("package-x-generic")), i18n("Install Widget From Local File..."), this); QObject::connect(action, &QAction::triggered, this, &WidgetExplorer::openWidgetFile); actionList << action; return actionList; } QList WidgetExplorer::extraActions() const { QList actionList; // foreach (QAction *action, actions()) { // FIXME: where did actions() come from? // actionList << action; // } qWarning() << "extraactions needs reimplementation"; return actionList; } void WidgetExplorerPrivate::initRunningApplets() { //get applets from corona, count them, send results to model if (!containment) { return; } Plasma::Corona *c = containment->corona(); //we've tried our best to get a corona //we don't want just one containment, we want them all if (!c) { qWarning() << "WidgetExplorer failed to find corona"; return; } appletNames.clear(); runningApplets.clear(); const QList containments = c->containments(); for (Containment *containment : containments) { if (containment->containmentType() == Plasma::Types::DesktopContainment && containment->activity() != activitiesConsumer->currentActivity()) { continue; } addContainment(containment); } //qDebug() << runningApplets; itemModel.setRunningApplets(runningApplets); } void WidgetExplorerPrivate::addContainment(Containment *containment) { QObject::connect(containment, SIGNAL(appletAdded(Plasma::Applet*)), q, SLOT(appletAdded(Plasma::Applet*))); QObject::connect(containment, SIGNAL(appletRemoved(Plasma::Applet*)), q, SLOT(appletRemoved(Plasma::Applet*))); foreach (Applet *applet, containment->applets()) { if (applet->pluginInfo().isValid()) { Containment *childContainment = applet->property("containment").value(); if (childContainment) { addContainment(childContainment); } runningApplets[applet->pluginInfo().pluginName()]++; } else { qDebug() << "Invalid plugininfo. :("; } } } void WidgetExplorerPrivate::containmentDestroyed() { containment = 0; } void WidgetExplorerPrivate::appletAdded(Plasma::Applet *applet) { if (!applet->pluginInfo().isValid()) { return; } QString name = applet->pluginInfo().pluginName(); runningApplets[name]++; appletNames.insert(applet, name); itemModel.setRunningApplets(name, runningApplets[name]); } void WidgetExplorerPrivate::appletRemoved(Plasma::Applet *applet) { QString name = appletNames.take(applet); int count = 0; if (runningApplets.contains(name)) { count = runningApplets[name] - 1; if (count < 1) { runningApplets.remove(name); } else { runningApplets[name] = count; } } itemModel.setRunningApplets(name, count); } //WidgetExplorer WidgetExplorer::WidgetExplorer(QObject *parent) : QObject(parent), d(new WidgetExplorerPrivate(this)) { //FIXME: delay setApplication(); d->initRunningApplets(); d->filterItemModel.setSortCaseSensitivity(Qt::CaseInsensitive); d->filterItemModel.setDynamicSortFilter(true); d->filterItemModel.setSourceModel(&d->itemModel); d->filterItemModel.sort(0); } WidgetExplorer::~WidgetExplorer() { delete d; } void WidgetExplorer::setApplication(const QString &app) { if (d->application == app && !app.isEmpty()) { return; } d->application = app; d->itemModel.setApplication(app); d->initFilters(); d->itemModel.setRunningApplets(d->runningApplets); emit applicationChanged(); } QString WidgetExplorer::application() { return d->application; } QStringList WidgetExplorer::provides() const { return d->itemModel.provides(); } void WidgetExplorer::setProvides(const QStringList &provides) { if (d->itemModel.provides() == provides) { return; } d->itemModel.setProvides(provides); emit providesChanged(); } void WidgetExplorer::setContainment(Plasma::Containment *containment) { if (d->containment != containment) { if (d->containment) { d->containment->disconnect(this); } d->containment = containment; if (d->containment) { connect(d->containment, SIGNAL(destroyed(QObject*)), this, SLOT(containmentDestroyed())); connect(d->containment, &Applet::immutabilityChanged, this, &WidgetExplorer::immutabilityChanged); } d->initRunningApplets(); emit containmentChanged(); } } Containment *WidgetExplorer::containment() const { return d->containment; } Plasma::Corona *WidgetExplorer::corona() const { if (d->containment) { return d->containment->corona(); } return 0; } void WidgetExplorer::addApplet(const QString &pluginName) { const QString p = PLASMA_RELATIVE_DATA_INSTALL_DIR "/plasmoids/"+pluginName; qWarning() << "--------> load applet: " << pluginName << " relpath: " << p; QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, p, QStandardPaths::LocateDirectory); qDebug() << " .. pathes: " << dirs; if (!dirs.count()) { qWarning() << "Failed to find plasmoid path for " << pluginName; return; } if (d->containment) { d->containment->createApplet(dirs.first()); } } void WidgetExplorer::immutabilityChanged(Plasma::Types::ImmutabilityType type) { if (type != Plasma::Types::Mutable) { emit shouldClose(); } } void WidgetExplorer::downloadWidgets(const QString &type) { Q_UNUSED(type); if (!d->newStuffDialog) { d->newStuffDialog = new KNS3::DownloadDialog( QLatin1String("plasmoids.knsrc") ); d->newStuffDialog.data()->setWindowTitle(i18n("Download New Plasma Widgets")); connect(d->newStuffDialog.data(), SIGNAL(accepted()), SLOT(newStuffFinished())); } d->newStuffDialog.data()->show(); emit shouldClose(); } void WidgetExplorer::openWidgetFile() { Plasma::OpenWidgetAssistant *assistant = d->openAssistant.data(); if (!assistant) { assistant = new Plasma::OpenWidgetAssistant(0); d->openAssistant = assistant; } KWindowSystem::setOnDesktop(assistant->winId(), KWindowSystem::currentDesktop()); assistant->setAttribute(Qt::WA_DeleteOnClose, true); assistant->show(); assistant->raise(); assistant->setFocus(); emit shouldClose(); } void WidgetExplorer::uninstall(const QString &pluginName) { static const QString packageRoot = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + PLASMA_RELATIVE_DATA_INSTALL_DIR "/plasmoids/"; KPackage::PackageStructure *structure = KPackage::PackageLoader::self()->loadPackageStructure(QStringLiteral("Plasma/Applet")); KPackage::Package pkg(structure); pkg.uninstall(pluginName, packageRoot); //FIXME: moreefficient way rather a linear scan? for (int i = 0; i < d->itemModel.rowCount(); ++i) { QStandardItem *item = d->itemModel.item(i); if (item->data(PlasmaAppletItemModel::PluginNameRole).toString() == pluginName) { d->itemModel.takeRow(i); break; } } // now remove all instances of that applet if (corona()) { const auto &containments = corona()->containments(); foreach (Containment *c, containments) { const auto &applets = c->applets(); foreach (Applet *applet, applets) { const auto &appletInfo = applet->pluginInfo(); if (appletInfo.isValid() && appletInfo.pluginName() == pluginName) { applet->destroy(); } } } } } #include "moc_widgetexplorer.cpp" diff --git a/dataengines/apps/appsengine.cpp b/dataengines/apps/appsengine.cpp index 81e8ce50..e1579722 100644 --- a/dataengines/apps/appsengine.cpp +++ b/dataengines/apps/appsengine.cpp @@ -1,93 +1,93 @@ /* * Copyright 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 version 2 as * published by the Free Software Foundation * * 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 "appsengine.h" #include "appsource.h" #include AppsEngine::AppsEngine(QObject *parent, const QVariantList &args) : Plasma::DataEngine(parent, args) { Q_UNUSED(args); init(); } AppsEngine::~AppsEngine() { } void AppsEngine::init() { addGroup(KServiceGroup::root()); connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(sycocaChanged(QStringList))); } void AppsEngine::sycocaChanged(const QStringList &changes) { if (changes.contains(QStringLiteral("apps")) || changes.contains(QStringLiteral("xdgdata-apps"))) { removeAllSources(); addGroup(KServiceGroup::root()); } } Plasma::Service *AppsEngine::serviceForSource(const QString &name) { AppSource *source = dynamic_cast(containerForSource(name)); // if source does not exist, return null service if (!source) { return Plasma::DataEngine::serviceForSource(name); } // if source represents a group or something, return null service if (!source->isApp()) { return Plasma::DataEngine::serviceForSource(name); } // if source represent a proper app, return real service Plasma::Service *service = source->createService(); service->setParent(this); return service; } void AppsEngine::addGroup(KServiceGroup::Ptr group) { if (!(group && group->isValid())) { return; } AppSource *appSource = new AppSource(group, this); //TODO listen for changes addSource(appSource); //do children - foreach (const KServiceGroup::Ptr subGroup, group->groupEntries(KServiceGroup::NoOptions)) { + foreach (const KServiceGroup::Ptr &subGroup, group->groupEntries(KServiceGroup::NoOptions)) { addGroup(subGroup); } - foreach (const KService::Ptr app, group->serviceEntries(KServiceGroup::NoOptions)) { + foreach (const KService::Ptr &app, group->serviceEntries(KServiceGroup::NoOptions)) { addApp(app); } } void AppsEngine::addApp(KService::Ptr app) { AppSource *appSource = new AppSource(app, this); //TODO listen for changes addSource(appSource); } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(apps, AppsEngine, "plasma-dataengine-apps.json") #include "appsengine.moc" diff --git a/dataengines/geolocation/geolocation.cpp b/dataengines/geolocation/geolocation.cpp index 38e106c9..a1d2db9e 100644 --- a/dataengines/geolocation/geolocation.cpp +++ b/dataengines/geolocation/geolocation.cpp @@ -1,154 +1,154 @@ /* * Copyright (C) 2009 Petri Damsten * * 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, see . */ #include "geolocation.h" #include #include #include #include #include static const char SOURCE[] = "location"; Geolocation::Geolocation(QObject* parent, const QVariantList& args) : Plasma::DataEngine(parent, args) { Q_UNUSED(args) setMinimumPollingInterval(500); connect(NetworkManager::notifier(), &NetworkManager::Notifier::networkingEnabledChanged, this, &Geolocation::networkStatusChanged); connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessEnabledChanged, this, &Geolocation::networkStatusChanged); m_updateTimer.setInterval(100); m_updateTimer.setSingleShot(true); connect(&m_updateTimer, &QTimer::timeout, this, &Geolocation::actuallySetData); m_networkChangedTimer.setInterval(100); m_networkChangedTimer.setSingleShot(true); connect(&m_networkChangedTimer, &QTimer::timeout, this, [this] { updatePlugins(GeolocationProvider::NetworkConnected); } ); init(); } void Geolocation::init() { //TODO: should this be delayed even further, e.g. when the source is requested? const KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/GeolocationProvider")); QVariantList args; - Q_FOREACH (const KService::Ptr service, offers) { + Q_FOREACH (const KService::Ptr &service, offers) { QString error; GeolocationProvider *plugin = service->createInstance(0, args, &error); if (plugin) { m_plugins << plugin; plugin->init(&m_data, &m_accuracy); connect(plugin, &GeolocationProvider::updated, this, &Geolocation::pluginUpdated); connect(plugin, &GeolocationProvider::availabilityChanged, this, &Geolocation::pluginAvailabilityChanged); } else { qDebug() << "Failed to load GeolocationProvider:" << error; } } } Geolocation::~Geolocation() { qDeleteAll(m_plugins); } QStringList Geolocation::sources() const { return QStringList() << SOURCE; } bool Geolocation::updateSourceEvent(const QString &name) { //qDebug() << name; if (name == SOURCE) { return updatePlugins(GeolocationProvider::SourceEvent); } return false; } bool Geolocation::updatePlugins(GeolocationProvider::UpdateTriggers triggers) { bool changed = false; Q_FOREACH (GeolocationProvider *plugin, m_plugins) { changed = plugin->requestUpdate(triggers) || changed; } if (changed) { m_updateTimer.start(); } return changed; } bool Geolocation::sourceRequestEvent(const QString &name) { qDebug() << name; if (name == SOURCE) { updatePlugins(GeolocationProvider::ForcedUpdate); setData(SOURCE, m_data); return true; } return false; } void Geolocation::networkStatusChanged(bool isOnline) { qDebug() << "network status changed"; if (isOnline) { m_networkChangedTimer.start(); } } void Geolocation::pluginAvailabilityChanged(GeolocationProvider *provider) { m_data.clear(); m_accuracy.clear(); provider->requestUpdate(GeolocationProvider::ForcedUpdate); bool changed = false; Q_FOREACH (GeolocationProvider *plugin, m_plugins) { changed = plugin->populateSharedData() || changed; } if (changed) { m_updateTimer.start(); } } void Geolocation::pluginUpdated() { m_updateTimer.start(); } void Geolocation::actuallySetData() { setData(SOURCE, m_data); } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(geolocation, Geolocation, "plasma-dataengine-geolocation.json") #include "geolocation.moc" diff --git a/dataengines/share/shareengine.cpp b/dataengines/share/shareengine.cpp index 039777d8..deab4734 100644 --- a/dataengines/share/shareengine.cpp +++ b/dataengines/share/shareengine.cpp @@ -1,122 +1,120 @@ /*************************************************************************** * Copyright 2010 Artur Duque de Souza * * * * 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 #include #include #include #include "shareengine.h" #include "shareservice.h" ShareEngine::ShareEngine(QObject *parent, const QVariantList &args) : Plasma::DataEngine(parent, args) { Q_UNUSED(args); init(); } void ShareEngine::init() { connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(updatePlugins(QStringList))); updatePlugins(QStringList() << QStringLiteral("services")); } void ShareEngine::updatePlugins(const QStringList &changes) { if (!changes.contains(QStringLiteral("services"))) { return; } removeAllSources(); KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/ShareProvider")); QMultiMap sortedServices; foreach (KService::Ptr service, services) { sortedServices.insert(service->property(QStringLiteral("X-KDE-Priority")).toInt(), service); } QMapIterator it(sortedServices); it.toBack(); QHash mimetypes; while (it.hasPrevious()) { it.previous(); KService::Ptr service = it.value(); const QString pluginName = service->property(QStringLiteral("X-KDE-PluginInfo-Name"), QVariant::String).toString(); const QStringList pluginMimeTypes = service->property(QStringLiteral("X-KDE-PlasmaShareProvider-MimeType"), QVariant::StringList).toStringList(); - const QString storageId = service->storageId(); - if (pluginName.isEmpty() || pluginMimeTypes.isEmpty()) { continue; } // create the list of providers Plasma::DataEngine::Data data; data.insert(QStringLiteral("Name"), service->name()); data.insert(QStringLiteral("Service Id"), service->storageId()); data.insert(QStringLiteral("Mimetypes"), pluginMimeTypes); setData(pluginName, data); // create the list of providers by type foreach (const QString &pluginMimeType, pluginMimeTypes) { mimetypes[pluginMimeType].append(pluginName); } } QHashIterator it2(mimetypes); while (it2.hasNext()) { it2.next(); setData(QStringLiteral("Mimetypes"), it2.key(), it2.value()); } } Plasma::Service *ShareEngine::serviceForSource(const QString &source) { Plasma::DataContainer *data = containerForSource(source); if (!data) { return Plasma::DataEngine::serviceForSource(source); } if (source.compare(QLatin1String("mimetype"), Qt::CaseInsensitive) == 0) { return Plasma::DataEngine::serviceForSource(source); } const QString id = data->data().value(QStringLiteral("Service Id")).toString(); if (id.isEmpty()) { return Plasma::DataEngine::serviceForSource(source); } ShareService *service = new ShareService(this); service->setDestination(id); return service; } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(share, ShareEngine, "plasma-dataengine-share.json") #include "shareengine.moc" diff --git a/dataengines/weather/ions/noaa/ion_noaa.cpp b/dataengines/weather/ions/noaa/ion_noaa.cpp index 40dcd46a..290c6d0a 100644 --- a/dataengines/weather/ions/noaa/ion_noaa.cpp +++ b/dataengines/weather/ions/noaa/ion_noaa.cpp @@ -1,886 +1,886 @@ /*************************************************************************** * Copyright (C) 2007-2009 by Shawn Starr * * * * 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 * ***************************************************************************/ /* Ion for NOAA's National Weather Service XML data */ #include "ion_noaa.h" #include "ion_noaadebug.h" #include #include #include #include WeatherData::WeatherData() : stationLat(qQNaN()) , stationLon(qQNaN()) , temperature_F(qQNaN()) , temperature_C(qQNaN()) , humidity(qQNaN()) , windSpeed(qQNaN()) , windGust(qQNaN()) , pressure(qQNaN()) , dewpoint_F(qQNaN()) , dewpoint_C(qQNaN()) , heatindex_F(qQNaN()) , heatindex_C(qQNaN()) , windchill_F(qQNaN()) , windchill_C(qQNaN()) , visibility(qQNaN()) { } QMap NOAAIon::setupWindIconMappings() const { QMap windDir; windDir[QStringLiteral("north")] = N; windDir[QStringLiteral("northeast")] = NE; windDir[QStringLiteral("south")] = S; windDir[QStringLiteral("southwest")] = SW; windDir[QStringLiteral("east")] = E; windDir[QStringLiteral("southeast")] = SE; windDir[QStringLiteral("west")] = W; windDir[QStringLiteral("northwest")] = NW; windDir[QStringLiteral("calm")] = VR; return windDir; } QMap NOAAIon::setupConditionIconMappings() const { QMap conditionList; return conditionList; } QMap const& NOAAIon::conditionIcons() const { static QMap const condval = setupConditionIconMappings(); return condval; } QMap const& NOAAIon::windIcons() const { static QMap const wval = setupWindIconMappings(); return wval; } // ctor, dtor NOAAIon::NOAAIon(QObject *parent, const QVariantList &args) : IonInterface(parent, args) { // Get the real city XML URL so we can parse this getXMLSetup(); // not used while daytime not considered, see below // m_timeEngine = dataEngine(QStringLiteral("time")); } void NOAAIon::reset() { m_sourcesToReset = sources(); getXMLSetup(); } NOAAIon::~NOAAIon() { //seems necessary to avoid crash removeAllSources(); } QStringList NOAAIon::validate(const QString& source) const { QStringList placeList; QString station; QString sourceNormalized = source.toUpper(); QHash::const_iterator it = m_places.constBegin(); // If the source name might look like a station ID, check these too and return the name bool checkState = source.count() == 2; while (it != m_places.constEnd()) { if (checkState) { if (it.value().stateName == source) { placeList.append(QStringLiteral("place|").append(it.key())); } } else if (it.key().toUpper().contains(sourceNormalized)) { placeList.append(QStringLiteral("place|").append(it.key())); } else if (it.value().stationID == sourceNormalized) { station = QStringLiteral("place|").append(it.key()); } ++it; } placeList.sort(); if (!station.isEmpty()) { placeList.prepend(station); } return placeList; } bool NOAAIon::updateIonSource(const QString& source) { // We expect the applet to send the source in the following tokenization: // ionname:validate:place_name - Triggers validation of place // ionname:weather:place_name - Triggers receiving weather of place QStringList sourceAction = source.split(QLatin1Char('|')); // Guard: if the size of array is not 2 then we have bad data, return an error if (sourceAction.size() < 2) { setData(source, QStringLiteral("validate"), QStringLiteral("noaa|malformed")); return true; } if (sourceAction[1] == QLatin1String("validate") && sourceAction.size() > 2) { QStringList result = validate(sourceAction[2]); if (result.size() == 1) { setData(source, QStringLiteral("validate"), QStringLiteral("noaa|valid|single|").append(result.join(QLatin1Char('|')))); return true; } if (result.size() > 1) { setData(source, QStringLiteral("validate"), QStringLiteral("noaa|valid|multiple|").append(result.join(QLatin1Char('|')))); return true; } // result.size() == 0 setData(source, QStringLiteral("validate"), QStringLiteral("noaa|invalid|single|").append(sourceAction[2])); return true; } if (sourceAction[1] == QLatin1String("weather") && sourceAction.size() > 2) { getXMLData(source); return true; } setData(source, QStringLiteral("validate"), QStringLiteral("noaa|malformed")); return true; } // Parses city list and gets the correct city based on ID number void NOAAIon::getXMLSetup() const { const QUrl url(QStringLiteral("http://www.weather.gov/data/current_obs/index.xml")); KIO::TransferJob* getJob = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); connect(getJob, &KIO::TransferJob::data, this, &NOAAIon::setup_slotDataArrived); connect(getJob, &KJob::result, this, &NOAAIon::setup_slotJobFinished); } // Gets specific city XML data void NOAAIon::getXMLData(const QString& source) { foreach (const QString &fetching, m_jobList) { if (fetching == source) { // already getting this source and awaiting the data return; } } QString dataKey = source; dataKey.remove(QStringLiteral("noaa|weather|")); const QUrl url(m_places[dataKey].XMLurl); // If this is empty we have no valid data, send out an error and abort. if (url.url().isEmpty()) { setData(source, QStringLiteral("validate"), QStringLiteral("noaa|malformed")); return; } KIO::TransferJob* getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); m_jobXml.insert(getJob, new QXmlStreamReader); m_jobList.insert(getJob, source); connect(getJob, &KIO::TransferJob::data, this, &NOAAIon::slotDataArrived); connect(getJob, &KJob::result, this, &NOAAIon::slotJobFinished); } void NOAAIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job) if (data.isEmpty()) { return; } // Send to xml. m_xmlSetup.addData(data); } void NOAAIon::slotDataArrived(KIO::Job *job, const QByteArray &data) { if (data.isEmpty() || !m_jobXml.contains(job)) { return; } // Send to xml. m_jobXml[job]->addData(data); } void NOAAIon::slotJobFinished(KJob *job) { // Dual use method, if we're fetching location data to parse we need to do this first const QString source(m_jobList.value(job)); removeAllData(source); QXmlStreamReader *reader = m_jobXml.value(job); if (reader) { readXMLData(m_jobList[job], *reader); } // Now that we have the longitude and latitude, fetch the seven day forecast. getForecast(m_jobList[job]); m_jobList.remove(job); m_jobXml.remove(job); delete reader; } void NOAAIon::setup_slotJobFinished(KJob *job) { Q_UNUSED(job) const bool success = readXMLSetup(); setInitialized(success); foreach (const QString &source, m_sourcesToReset) { updateSourceEvent(source); } } void NOAAIon::parseFloat(float& value, const QString& string) { bool ok = false; const float result = string.toFloat(&ok); if (ok) { value = result; } } void NOAAIon::parseFloat(float& value, QXmlStreamReader& xml) { bool ok = false; const float result = xml.readElementText().toFloat(&ok); if (ok) { value = result; } } void NOAAIon::parseDouble(double& value, QXmlStreamReader& xml) { bool ok = false; const double result = xml.readElementText().toDouble(&ok); if (ok) { value = result; } } void NOAAIon::parseStationID() { QString state; QString stationName; QString stationID; QString xmlurl; while (!m_xmlSetup.atEnd()) { m_xmlSetup.readNext(); const QStringRef elementName = m_xmlSetup.name(); if (m_xmlSetup.isEndElement() && elementName == QLatin1String("station")) { if (!xmlurl.isEmpty()) { NOAAIon::XMLMapInfo info; info.stateName = state; info.stationName = stationName; info.stationID = stationID; info.XMLurl = xmlurl; QString tmp = stationName + QStringLiteral(", ") + state; // Build the key name. m_places[tmp] = info; } break; } if (m_xmlSetup.isStartElement()) { if (elementName == QLatin1String("station_id")) { stationID = m_xmlSetup.readElementText(); } else if (elementName == QLatin1String("state")) { state = m_xmlSetup.readElementText(); } else if (elementName == QLatin1String("station_name")) { stationName = m_xmlSetup.readElementText(); } else if (elementName == QLatin1String("xml_url")) { xmlurl = m_xmlSetup.readElementText().replace(QStringLiteral("http://"), QStringLiteral("http://www.")); } else { parseUnknownElement(m_xmlSetup); } } } } void NOAAIon::parseStationList() { while (!m_xmlSetup.atEnd()) { m_xmlSetup.readNext(); if (m_xmlSetup.isEndElement()) { break; } if (m_xmlSetup.isStartElement()) { if (m_xmlSetup.name() == QLatin1String("station")) { parseStationID(); } else { parseUnknownElement(m_xmlSetup); } } } } // Parse the city list and store into a QMap bool NOAAIon::readXMLSetup() { bool success = false; while (!m_xmlSetup.atEnd()) { m_xmlSetup.readNext(); if (m_xmlSetup.isStartElement()) { if (m_xmlSetup.name() == QLatin1String("wx_station_index")) { parseStationList(); success = true; } } } return (!m_xmlSetup.error() && success); } void NOAAIon::parseWeatherSite(WeatherData& data, QXmlStreamReader& xml) { data.temperature_C = qQNaN(); data.temperature_F = qQNaN(); data.dewpoint_C = qQNaN(); data.dewpoint_F = qQNaN(); data.weather = QStringLiteral("N/A"); data.stationID = i18n("N/A"); data.pressure = qQNaN(); data.visibility = qQNaN(); data.humidity = qQNaN(); data.windSpeed = qQNaN(); data.windGust = qQNaN(); data.windchill_F = qQNaN(); data.windchill_C = qQNaN(); data.heatindex_F = qQNaN(); data.heatindex_C = qQNaN(); while (!xml.atEnd()) { xml.readNext(); const QStringRef elementName = xml.name(); if (xml.isStartElement()) { if (elementName == QLatin1String("location")) { data.locationName = xml.readElementText(); } else if (elementName == QLatin1String("station_id")) { data.stationID = xml.readElementText(); } else if (elementName == QLatin1String("latitude")) { parseDouble(data.stationLat, xml); } else if (elementName == QLatin1String("longitude")) { parseDouble(data.stationLon, xml); } else if (elementName == QLatin1String("observation_time")) { data.observationTime = xml.readElementText(); QStringList tmpDateStr = data.observationTime.split(QLatin1Char(' ')); - data.observationTime = QStringLiteral("%1 %2").arg(tmpDateStr[6]).arg(tmpDateStr[7]); + data.observationTime = QStringLiteral("%1 %2").arg(tmpDateStr[6], tmpDateStr[7]); m_dateFormat = QDateTime::fromString(data.observationTime, QStringLiteral("h:mm ap")); data.iconPeriodHour = m_dateFormat.toString(QStringLiteral("HH")); data.iconPeriodAP = m_dateFormat.toString(QStringLiteral("ap")); } else if (elementName == QLatin1String("weather")) { const QString weather = xml.readElementText(); data.weather = (weather.isEmpty() || weather == QLatin1String("NA")) ? QStringLiteral("N/A") : weather; // Pick which icon set depending on period of day } else if (elementName == QLatin1String("temp_f")) { parseFloat(data.temperature_F, xml); } else if (elementName == QLatin1String("temp_c")) { parseFloat(data.temperature_C, xml); } else if (elementName == QLatin1String("relative_humidity")) { parseFloat(data.humidity, xml); } else if (elementName == QLatin1String("wind_dir")) { data.windDirection = xml.readElementText(); } else if (elementName == QLatin1String("wind_mph")) { const QString windSpeed = xml.readElementText(); if (windSpeed == QLatin1String("NA")) { data.windSpeed = 0.0; } else { parseFloat(data.windSpeed, windSpeed); } } else if (elementName == QLatin1String("wind_gust_mph")) { const QString windGust = xml.readElementText(); if (windGust == QLatin1String("NA") || windGust == QLatin1String("N/A")) { data.windGust = 0.0; } else { parseFloat(data.windGust, windGust); } } else if (elementName == QLatin1String("pressure_in")) { parseFloat(data.pressure, xml); } else if (elementName == QLatin1String("dewpoint_f")) { parseFloat(data.dewpoint_F, xml); } else if (elementName == QLatin1String("dewpoint_c")) { parseFloat(data.dewpoint_C, xml); } else if (elementName == QLatin1String("heat_index_f")) { parseFloat(data.heatindex_F, xml); } else if (elementName == QLatin1String("heat_index_c")) { parseFloat(data.heatindex_C, xml); } else if (elementName == QLatin1String("windchill_f")) { parseFloat(data.windchill_F, xml); } else if (elementName == QLatin1String("windchill_c")) { parseFloat(data.windchill_C, xml); } else if (elementName == QLatin1String("visibility_mi")) { parseFloat(data.visibility, xml); } else { parseUnknownElement(xml); } } } } // Parse Weather data main loop, from here we have to decend into each tag pair bool NOAAIon::readXMLData(const QString& source, QXmlStreamReader& xml) { WeatherData data; while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } if (xml.isStartElement()) { if (xml.name() == QLatin1String("current_observation")) { parseWeatherSite(data, xml); } else { parseUnknownElement(xml); } } } m_weatherData[source] = data; return !xml.error(); } // handle when no XML tag is found void NOAAIon::parseUnknownElement(QXmlStreamReader& xml) const { while (!xml.atEnd()) { xml.readNext(); if (xml.isEndElement()) { break; } if (xml.isStartElement()) { parseUnknownElement(xml); } } } void NOAAIon::updateWeather(const QString& source) { const WeatherData& weatherData = m_weatherData[source]; Plasma::DataEngine::Data data; data.insert(QStringLiteral("Country"), QStringLiteral("USA")); data.insert(QStringLiteral("Place"), weatherData.locationName); data.insert(QStringLiteral("Station"), weatherData.stationID); const double lat = weatherData.stationLat; const double lon = weatherData.stationLon; if (!qIsNaN(lat) && !qIsNaN(lon)) { data.insert(QStringLiteral("Latitude"), lat); data.insert(QStringLiteral("Longitude"), lon); } // Real weather - Current conditions data.insert(QStringLiteral("Observation Period"), weatherData.observationTime); const QString conditionI18n = weatherData.weather == QLatin1String("N/A") ? i18n("N/A") : i18nc("weather condition", weatherData.weather.toUtf8().data()); data.insert(QStringLiteral("Current Conditions"), conditionI18n); qCDebug(IONENGINE_NOAA) << "i18n condition string: " << qPrintable(conditionI18n); //TODO: Port to Plasma2 #if 0 // Determine the weather icon based on the current time and computed sunrise/sunset time. const Plasma::DataEngine::Data timeData = m_timeEngine->query( QString("Local|Solar|Latitude=%1|Longitude=%2") .arg(latitude(source)).arg(longitude(source))); QTime sunriseTime = timeData["Sunrise"].toDateTime().time(); QTime sunsetTime = timeData["Sunset"].toDateTime().time(); QTime currentTime = QDateTime::currentDateTime().time(); // Provide mapping for the condition-type to the icons to display if (currentTime > sunriseTime && currentTime < sunsetTime) { #endif // Day QString weather = weatherData.weather.toLower(); ConditionIcons condition = getConditionIcon(weather, true); data.insert(QStringLiteral("Condition Icon"), getWeatherIcon(condition)); qCDebug(IONENGINE_NOAA) << "Using daytime icons\n"; #if 0 } else { // Night QString weather = weatherData.weather.toLower(); ConditionIcons condition = getConditionIcon(weather, false); data.insert("Condition Icon", getWeatherIcon(condition)); qCDebug(IONENGINE_NOAA) << "Using nighttime icons\n"; } #endif if (!qIsNaN(weatherData.temperature_F)) { data.insert(QStringLiteral("Temperature"), weatherData.temperature_F); } // Used for all temperatures data.insert(QStringLiteral("Temperature Unit"), KUnitConversion::Fahrenheit); if (!qIsNaN(weatherData.windchill_F)) { data.insert(QStringLiteral("Windchill"), weatherData.windchill_F); } if (!qIsNaN(weatherData.heatindex_F)) { data.insert(QStringLiteral("Heat Index"), weatherData.heatindex_F); } if (!qIsNaN(weatherData.dewpoint_F)) { data.insert(QStringLiteral("Dewpoint"), weatherData.dewpoint_F); } if (!qIsNaN(weatherData.pressure)) { data.insert(QStringLiteral("Pressure"), weatherData.pressure); data.insert(QStringLiteral("Pressure Unit"), KUnitConversion::InchesOfMercury); } if (!qIsNaN(weatherData.visibility)) { data.insert(QStringLiteral("Visibility"), weatherData.visibility); data.insert(QStringLiteral("Visibility Unit"), KUnitConversion::Mile); } if (!qIsNaN(weatherData.humidity)) { data.insert(QStringLiteral("Humidity"), weatherData.humidity); data.insert(QStringLiteral("Humidity Unit"), KUnitConversion::Percent); } if (!qIsNaN(weatherData.windSpeed)) { data.insert(QStringLiteral("Wind Speed"), weatherData.windSpeed); } if (!qIsNaN(weatherData.windSpeed) || !qIsNaN(weatherData.windGust)) { data.insert(QStringLiteral("Wind Speed Unit"), KUnitConversion::MilePerHour); } if (!qIsNaN(weatherData.windGust)) { data.insert(QStringLiteral("Wind Gust"), weatherData.windGust); } if (!qIsNaN(weatherData.windSpeed) && static_cast(weatherData.windSpeed) == 0) { data.insert(QStringLiteral("Wind Direction"), QStringLiteral("VR")); // Variable/calm } else if (!weatherData.windDirection.isEmpty()) { data.insert(QStringLiteral("Wind Direction"), getWindDirectionIcon(windIcons(), weatherData.windDirection.toLower())); } // Set number of forecasts per day/night supported data.insert(QStringLiteral("Total Weather Days"), weatherData.forecasts.size()); int i = 0; foreach(const WeatherData::Forecast &forecast, weatherData.forecasts) { ConditionIcons icon = getConditionIcon(forecast.summary.toLower(), true); QString iconName = getWeatherIcon(icon); /* Sometimes the forecast for the later days is unavailable, if so skip remianing days * since their forecast data is probably unavailable. */ if (forecast.low.isEmpty() || forecast.high.isEmpty()) { break; } // Get the short day name for the forecast data.insert(QStringLiteral("Short Forecast Day %1").arg(i), QStringLiteral("%1|%2|%3|%4|%5|%6") .arg(forecast.day) .arg(iconName) .arg(i18nc("weather forecast", forecast.summary.toUtf8().data())) .arg(forecast.high) .arg(forecast.low) .arg(QStringLiteral())); ++i; } data.insert(QStringLiteral("Credit"), i18nc("credit line, keep string short)", "Data from NOAA National\302\240Weather\302\240Service")); setData(source, data); } /** * Determine the condition icon based on the list of possible NOAA weather conditions as defined at * and * Since the number of NOAA weather conditions need to be fitted into the narowly defined groups in IonInterface::ConditionIcons, we * try to group the NOAA conditions as best as we can based on their priorities/severity. */ IonInterface::ConditionIcons NOAAIon::getConditionIcon(const QString& weather, bool isDayTime) const { IonInterface::ConditionIcons result; // Consider any type of storm, tornado or funnel to be a thunderstorm. if (weather.contains(QStringLiteral("thunderstorm")) || weather.contains(QStringLiteral("funnel")) || weather.contains(QStringLiteral("tornado")) || weather.contains(QStringLiteral("storm")) || weather.contains(QStringLiteral("tstms"))) { if (weather.contains(QStringLiteral("vicinity")) || weather.contains(QStringLiteral("chance"))) { result = isDayTime ? IonInterface::ChanceThunderstormDay : IonInterface::ChanceThunderstormNight; } else { result = IonInterface::Thunderstorm; } } else if (weather.contains(QStringLiteral("pellets")) || weather.contains(QStringLiteral("crystals")) || weather.contains(QStringLiteral("hail"))) { result = IonInterface::Hail; } else if (((weather.contains(QStringLiteral("rain")) || weather.contains(QStringLiteral("drizzle")) || weather.contains(QStringLiteral("showers"))) && weather.contains(QStringLiteral("snow"))) || weather.contains(QStringLiteral("wintry mix"))) { result = IonInterface::RainSnow; } else if (weather.contains(QStringLiteral("snow")) && weather.contains(QStringLiteral("light"))) { result = IonInterface::LightSnow; } else if (weather.contains(QStringLiteral("snow"))) { if (weather.contains(QStringLiteral("vicinity")) || weather.contains(QStringLiteral("chance"))) { result = isDayTime ? IonInterface::ChanceSnowDay : IonInterface::ChanceSnowNight; } else { result = IonInterface::Snow; } } else if (weather.contains(QStringLiteral("freezing rain"))) { result = IonInterface::FreezingRain; } else if (weather.contains(QStringLiteral("freezing drizzle"))) { result = IonInterface::FreezingDrizzle; } else if (weather.contains(QStringLiteral("showers"))) { if (weather.contains(QStringLiteral("vicinity")) || weather.contains(QStringLiteral("chance"))) { result = isDayTime ? IonInterface::ChanceShowersDay : IonInterface::ChanceShowersNight; } else { result = IonInterface::Showers; } } else if (weather.contains(QStringLiteral("light rain")) || weather.contains(QStringLiteral("drizzle"))) { result = IonInterface::LightRain; } else if (weather.contains(QStringLiteral("rain"))) { result = IonInterface::Rain; } else if (weather.contains(QStringLiteral("few clouds")) || weather.contains(QStringLiteral("mostly sunny")) || weather.contains(QStringLiteral("mostly clear")) || weather.contains(QStringLiteral("increasing clouds")) || weather.contains(QStringLiteral("becoming cloudy")) || weather.contains(QStringLiteral("clearing")) || weather.contains(QStringLiteral("decreasing clouds")) || weather.contains(QStringLiteral("becoming sunny"))) { result = isDayTime ? IonInterface::FewCloudsDay : IonInterface::FewCloudsNight; } else if (weather.contains(QStringLiteral("partly cloudy")) || weather.contains(QStringLiteral("partly sunny")) || weather.contains(QStringLiteral("partly clear"))) { result = isDayTime ? IonInterface::PartlyCloudyDay : IonInterface::PartlyCloudyNight; } else if (weather.contains(QStringLiteral("overcast")) || weather.contains(QStringLiteral("cloudy"))) { result = IonInterface::Overcast; } else if (weather.contains(QStringLiteral("haze")) || weather.contains(QStringLiteral("smoke")) || weather.contains(QStringLiteral("dust")) || weather.contains(QStringLiteral("sand"))) { result = IonInterface::Haze; } else if (weather.contains(QStringLiteral("fair")) || weather.contains(QStringLiteral("clear")) || weather.contains(QStringLiteral("sunny"))) { result = isDayTime ? IonInterface::ClearDay : IonInterface::ClearNight; } else if (weather.contains(QStringLiteral("fog"))) { result = IonInterface::Mist; } else { result = IonInterface::NotAvailable; } return result; } void NOAAIon::getForecast(const QString& source) { const double lat = m_weatherData[source].stationLat; const double lon = m_weatherData[source].stationLon; if (qIsNaN(lat) || qIsNaN(lon)) { return; } /* Assuming that we have the latitude and longitude data at this point, get the 7-day * forecast. */ const QUrl url(QLatin1String("http://www.weather.gov/forecasts/xml/sample_products/browser_interface/" "ndfdBrowserClientByDay.php?lat=") + QString::number(lat) + QLatin1String("&lon=") + QString::number(lon) + QLatin1String("&format=24+hourly&numDays=7")); KIO::TransferJob* getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); m_jobXml.insert(getJob, new QXmlStreamReader); m_jobList.insert(getJob, source); connect(getJob, &KIO::TransferJob::data, this, &NOAAIon::forecast_slotDataArrived); connect(getJob, &KJob::result, this, &NOAAIon::forecast_slotJobFinished); } void NOAAIon::forecast_slotDataArrived(KIO::Job *job, const QByteArray &data) { if (data.isEmpty() || !m_jobXml.contains(job)) { return; } // Send to xml. m_jobXml[job]->addData(data); } void NOAAIon::forecast_slotJobFinished(KJob *job) { QXmlStreamReader *reader = m_jobXml.value(job); const QString source = m_jobList.value(job); if (reader) { readForecast(source, *reader); updateWeather(source); } m_jobList.remove(job); delete m_jobXml[job]; m_jobXml.remove(job); if (m_sourcesToReset.contains(source)) { m_sourcesToReset.removeAll(source); // so the weather engine updates it's data forceImmediateUpdateOfAllVisualizations(); // update the clients of our engine emit forceUpdate(this, source); } } void NOAAIon::readForecast(const QString& source, QXmlStreamReader& xml) { QList& forecasts = m_weatherData[source].forecasts; // Clear the current forecasts forecasts.clear(); while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { /* Read all reported days from . We check for existence of a specific * which indicates the separate day listings. The schema defines it to be * the first item before the day listings. */ if (xml.name() == QLatin1String("layout-key") && xml.readElementText() == QLatin1String("k-p24h-n7-1")) { // Read days until we get to end of parent ()tag while (! (xml.isEndElement() && xml.name() == QLatin1String("time-layout"))) { xml.readNext(); if (xml.name() == QLatin1String("start-valid-time")) { QString data = xml.readElementText(); QDateTime date = QDateTime::fromString(data, Qt::ISODate); WeatherData::Forecast forecast; forecast.day = QLocale().toString(date.date().day()); forecasts.append(forecast); //qCDebug(IONENGINE_NOAA) << forecast.day; } } } else if (xml.name() == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("type")) == QLatin1String("maximum")) { // Read max temps until we get to end tag int i = 0; while (! (xml.isEndElement() && xml.name() == QLatin1String("temperature")) && i < forecasts.count()) { xml.readNext(); if (xml.name() == QLatin1String("value")) { forecasts[i].high = xml.readElementText(); //qCDebug(IONENGINE_NOAA) << forecasts[i].high; i++; } } } else if (xml.name() == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("type")) == QLatin1String("minimum")) { // Read min temps until we get to end tag int i = 0; while (! (xml.isEndElement() && xml.name() == QLatin1String("temperature")) && i < forecasts.count()) { xml.readNext(); if (xml.name() == QLatin1String("value")) { forecasts[i].low = xml.readElementText(); //qCDebug(IONENGINE_NOAA) << forecasts[i].low; i++; } } } else if (xml.name() == QLatin1String("weather")) { // Read weather conditions until we get to end tag int i = 0; while (! (xml.isEndElement() && xml.name() == QLatin1String("weather")) && i < forecasts.count()) { xml.readNext(); if (xml.name() == QLatin1String("weather-conditions") && xml.isStartElement()) { QString summary = xml.attributes().value(QStringLiteral("weather-summary")).toString(); forecasts[i].summary = summary; //qCDebug(IONENGINE_NOAA) << forecasts[i].summary; qCDebug(IONENGINE_NOAA) << "i18n summary string: " << i18nc("weather forecast", forecasts[i].summary.toUtf8().data()); i++; } } } } } } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(noaa, NOAAIon, "ion-noaa.json") #include "ion_noaa.moc" diff --git a/drkonqi/parser/backtraceparser.h b/drkonqi/parser/backtraceparser.h index 9728e209..5e127cde 100644 --- a/drkonqi/parser/backtraceparser.h +++ b/drkonqi/parser/backtraceparser.h @@ -1,99 +1,99 @@ /* Copyright (C) 2009-2010 George Kiagiadakis 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. */ #ifndef BACKTRACEPARSER_H #define BACKTRACEPARSER_H #include "backtraceline.h" #include #include #include #include class BacktraceParserPrivate; class BacktraceParser : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(BacktraceParser) - Q_ENUMS(Usefulness) public: enum Usefulness { InvalidUsefulness, Useless, ProbablyUseless, MayBeUseful, ReallyUseful }; + Q_ENUM(Usefulness) static BacktraceParser *newParser(const QString & debuggerName, QObject *parent = 0); ~BacktraceParser() override; /*! Connects the parser to the backtrace generator. * Any QObject that defines the starting() and newLine(QString) signals will do. */ void connectToGenerator(QObject *generator); /*! Returns the parsed backtrace. Any garbage that should not be shown to the user is removed. */ virtual QString parsedBacktrace() const; /*! Same as parsedBacktrace(), but the backtrace here is returned as a list of * BacktraceLine objects, which provide extra information on each line. */ virtual QList parsedBacktraceLines() const; /*! Returns a simplified version of the backtrace. This backtrace: * \li Starts from the first useful function * \li Has maximum 5 lines * \li Replaces garbage with [...] */ virtual QString simplifiedBacktrace() const; /*! Returns a value that indicates how much useful is the backtrace that we got */ virtual Usefulness backtraceUsefulness() const; /*! Returns a short list of the first good functions that appear in the backtrace * (in the crashing thread). This is used for quering for duplicate reports. */ virtual QStringList firstValidFunctions() const; /*! Returns a list of libraries/executables that are missing debug symbols. */ virtual QSet librariesWithMissingDebugSymbols() const; private Q_SLOTS: void resetState(); protected Q_SLOTS: /*! Called every time there is a new line from the generator. Subclasses should parse * the line here and insert it in the m_linesList field of BacktraceParserPrivate. * If the line is useful for rating as well, it should also be inserted in the m_linesToRate * field, so that calculateRatingData() can use it. */ virtual void newLine(const QString & lineStr) = 0; protected: explicit BacktraceParser(QObject *parent = 0); /*! Subclasses should override to provide their own BacktraceParserPrivate instance */ virtual BacktraceParserPrivate *constructPrivate() const; /*! This method should fill the m_usefulness, m_simplifiedBacktrace, m_firstValidFunctions * and m_librariesWithMissingDebugSymbols members of the BacktraceParserPrivate instance. * The default implementation uses the lines inserted in m_linesToRate and applies a * generic algorithm that should work for many debuggers. */ virtual void calculateRatingData(); BacktraceParserPrivate *d_ptr; }; Q_DECLARE_METATYPE(BacktraceParser::Usefulness) #endif // BACKTRACEPARSER_H diff --git a/klipper/clipcommandprocess.cpp b/klipper/clipcommandprocess.cpp index b6631b52..f126a6d8 100644 --- a/klipper/clipcommandprocess.cpp +++ b/klipper/clipcommandprocess.cpp @@ -1,83 +1,83 @@ /* Copyright 2009 Esben Mose Hansen 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 "clipcommandprocess.h" #include #include "history.h" #include "historystringitem.h" #include "urlgrabber.h" ClipCommandProcess::ClipCommandProcess(const ClipAction& action, const ClipCommand& command, const QString& clip, History* history, HistoryItemConstPtr original_item) : KProcess(), m_history(history), m_historyItem(original_item), m_newhistoryItem() { QHash map; map.insert( 's', clip ); // support %u, %U (indicates url param(s)) and %f, %F (file param(s)) map.insert( 'u', clip ); map.insert( 'U', clip ); map.insert( 'f', clip ); map.insert( 'F', clip ); const QStringList matches = action.regExpMatches(); // support only %0 and the first 9 matches... const int numMatches = qMin(10, matches.count()); for ( int i = 0; i < numMatches; ++i ) { map.insert( QChar( '0' + i ), matches.at( i ) ); } setOutputChannelMode(OnlyStdoutChannel); setShellCommand(KMacroExpander::expandMacrosShellQuote( command.command, map ).trimmed()); connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotFinished(int,QProcess::ExitStatus))); if (command.output != ClipCommand::IGNORE) { connect(this, &QIODevice::readyRead, this, &ClipCommandProcess::slotStdOutputAvailable); } if (command.output != ClipCommand::REPLACE) { m_historyItem.clear(); } } void ClipCommandProcess::slotFinished(int /*exitCode*/, QProcess::ExitStatus /*newState*/) { if (m_history) { // If an history item was provided, remove it so that the new item can replace it if (m_historyItem) { m_history->remove(m_historyItem); } if (!m_newhistoryItem.isEmpty()) { m_history->insert(HistoryItemPtr(new HistoryStringItem(m_newhistoryItem))); } } deleteLater(); } void ClipCommandProcess::slotStdOutputAvailable() { - m_newhistoryItem.append(QString::fromLocal8Bit(this->readAllStandardOutput().data())); + m_newhistoryItem.append(QString::fromLocal8Bit(this->readAllStandardOutput())); } diff --git a/klipper/editactiondialog.cpp b/klipper/editactiondialog.cpp index afd7d3e9..c001f1cc 100644 --- a/klipper/editactiondialog.cpp +++ b/klipper/editactiondialog.cpp @@ -1,390 +1,390 @@ /* This file is part of the KDE project Copyright (C) 2009 by Dmitry Suzdalev 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "editactiondialog.h" #include #include #include "klipper_debug.h" #include #include #include #include #include "urlgrabber.h" #include "ui_editactiondialog.h" namespace { static QString output2text(ClipCommand::Output output) { switch(output) { case ClipCommand::IGNORE: return QString(i18n("Ignore")); case ClipCommand::REPLACE: return QString(i18n("Replace Clipboard")); case ClipCommand::ADD: return QString(i18n("Add to Clipboard")); } return QString(); } } /** * Show dropdown of editing Output part of commands */ class ActionOutputDelegate : public QItemDelegate { public: ActionOutputDelegate(QObject* parent = 0) : QItemDelegate(parent){ } QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const override { QComboBox* editor = new QComboBox(parent); editor->setInsertPolicy(QComboBox::NoInsert); editor->addItem(output2text(ClipCommand::IGNORE), QVariant::fromValue(ClipCommand::IGNORE)); editor->addItem(output2text(ClipCommand::REPLACE), QVariant::fromValue(ClipCommand::REPLACE)); editor->addItem(output2text(ClipCommand::ADD), QVariant::fromValue(ClipCommand::ADD)); return editor; } void setEditorData(QWidget* editor, const QModelIndex& index) const override { QComboBox* ed = static_cast(editor); QVariant data(index.model()->data(index, Qt::EditRole)); ed->setCurrentIndex(static_cast(data.value())); } void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override { QComboBox* ed = static_cast(editor); model->setData(index, ed->itemData(ed->currentIndex())); } void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const override { editor->setGeometry(option.rect); } }; class ActionDetailModel : public QAbstractTableModel { public: ActionDetailModel(ClipAction* action, QObject* parent = 0); QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; const QList& commands() const { return m_commands; } void addCommand(const ClipCommand& command); void removeCommand(const QModelIndex& index); private: enum column_t { COMMAND_COL = 0, OUTPUT_COL = 1, DESCRIPTION_COL = 2 }; QList m_commands; QVariant displayData(ClipCommand* command, column_t colunm) const; QVariant editData(ClipCommand* command, column_t column) const; QVariant decorationData(ClipCommand* command, column_t column) const; void setIconForCommand(ClipCommand& cmd); }; ActionDetailModel::ActionDetailModel(ClipAction* action, QObject* parent): QAbstractTableModel(parent), m_commands(action->commands()) { } Qt::ItemFlags ActionDetailModel::flags(const QModelIndex& /*index*/) const { return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; } void ActionDetailModel::setIconForCommand(ClipCommand& cmd) { // let's try to update icon of the item according to command QString command = cmd.command; if ( command.contains( ' ' ) ) { // get first word command = command.section( ' ', 0, 0 ); } QPixmap iconPix = KIconLoader::global()->loadIcon( command, KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(), 0, true /* canReturnNull */ ); if ( !iconPix.isNull() ) { cmd.icon = command; } else { cmd.icon.clear(); } } bool ActionDetailModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == Qt::EditRole) { ClipCommand cmd = m_commands.at(index.row()); switch (static_cast(index.column())) { case COMMAND_COL: - cmd.command = value.value(); + cmd.command = value.toString(); setIconForCommand(cmd); break; case OUTPUT_COL: cmd.output = value.value(); break; case DESCRIPTION_COL: - cmd.description = value.value(); + cmd.description = value.toString(); break; } m_commands.replace(index.row(), cmd); emit dataChanged(index, index); return true; } return false; } int ActionDetailModel::columnCount(const QModelIndex& /*parent*/) const { return 3; } int ActionDetailModel::rowCount(const QModelIndex&) const { return m_commands.count(); } QVariant ActionDetailModel::displayData(ClipCommand* command, ActionDetailModel::column_t column) const { switch (column) { case COMMAND_COL: return command->command; case OUTPUT_COL: return output2text(command->output); case DESCRIPTION_COL: return command->description; } return QVariant(); } QVariant ActionDetailModel::decorationData(ClipCommand* command, ActionDetailModel::column_t column) const { switch (column) { case COMMAND_COL: return command->icon.isEmpty() ? QIcon::fromTheme( QStringLiteral("system-run") ) : QIcon::fromTheme( command->icon ); case OUTPUT_COL: case DESCRIPTION_COL: break; } return QVariant(); } QVariant ActionDetailModel::editData(ClipCommand* command, ActionDetailModel::column_t column) const { switch (column) { case COMMAND_COL: return command->command; case OUTPUT_COL: return QVariant::fromValue(command->output); case DESCRIPTION_COL: return command->description; } return QVariant(); } QVariant ActionDetailModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch(static_cast(section)) { case COMMAND_COL: return i18n("Command"); case OUTPUT_COL: return i18n("Output Handling"); case DESCRIPTION_COL: return i18n("Description"); } } return QAbstractTableModel::headerData(section, orientation, role); } QVariant ActionDetailModel::data(const QModelIndex& index, int role) const { const int column = index.column(); const int row = index.row(); ClipCommand cmd = m_commands.at(row); switch (role) { case Qt::DisplayRole: return displayData(&cmd, static_cast(column)); case Qt::DecorationRole: return decorationData(&cmd, static_cast(column)); case Qt::EditRole: return editData(&cmd, static_cast(column)); } return QVariant(); } void ActionDetailModel::addCommand(const ClipCommand& command) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_commands << command; endInsertRows(); } void ActionDetailModel::removeCommand(const QModelIndex& index) { int row = index.row(); beginRemoveRows(QModelIndex(), row, row); m_commands.removeAt(row); endRemoveRows(); } EditActionDialog::EditActionDialog(QWidget* parent) : QDialog(parent) { setWindowTitle(i18n("Action Properties")); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); buttons->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttons, &QDialogButtonBox::accepted, this, &EditActionDialog::slotAccepted); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); QWidget* dlgWidget = new QWidget(this); m_ui = new Ui::EditActionDialog; m_ui->setupUi(dlgWidget); m_ui->leRegExp->setClearButtonShown(true); m_ui->leDescription->setClearButtonShown(true); m_ui->pbAddCommand->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_ui->pbRemoveCommand->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); // For some reason, the default row height is 30 pixel. Set it to the minimum sectionSize instead, // which is the font height+struts. m_ui->twCommandList->verticalHeader()->setDefaultSectionSize(m_ui->twCommandList->verticalHeader()->minimumSectionSize()); m_ui->twCommandList->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(dlgWidget); layout->addWidget(buttons); connect(m_ui->pbAddCommand, &QPushButton::clicked, this, &EditActionDialog::onAddCommand); connect(m_ui->pbRemoveCommand, &QPushButton::clicked, this, &EditActionDialog::onRemoveCommand); const KConfigGroup grp = KSharedConfig::openConfig()->group("EditActionDialog"); KWindowConfig::restoreWindowSize(windowHandle(), grp); QByteArray hdrState = grp.readEntry("ColumnState", QByteArray()); if (!hdrState.isEmpty()) { qCDebug(KLIPPER_LOG) << "Restoring column state"; m_ui->twCommandList->horizontalHeader()->restoreState(QByteArray::fromBase64(hdrState)); } // do this after restoreState() m_ui->twCommandList->horizontalHeader()->setHighlightSections(false); } EditActionDialog::~EditActionDialog() { delete m_ui; } void EditActionDialog::setAction(ClipAction* act, int commandIdxToSelect) { m_action = act; m_model = new ActionDetailModel(act, this); m_ui->twCommandList->setModel(m_model); m_ui->twCommandList->setItemDelegateForColumn(1, new ActionOutputDelegate); connect(m_ui->twCommandList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditActionDialog::onSelectionChanged); updateWidgets( commandIdxToSelect ); } void EditActionDialog::updateWidgets(int commandIdxToSelect) { if (!m_action) { qCDebug(KLIPPER_LOG) << "no action to edit was set"; return; } m_ui->leRegExp->setText(m_action->regExp()); m_ui->automatic->setChecked(m_action->automatic()); m_ui->leDescription->setText(m_action->description()); if (commandIdxToSelect != -1) { m_ui->twCommandList->setCurrentIndex( m_model->index( commandIdxToSelect ,0 ) ); } // update Remove button onSelectionChanged(); } void EditActionDialog::saveAction() { if (!m_action) { qCDebug(KLIPPER_LOG) << "no action to edit was set"; return; } m_action->setRegExp( m_ui->leRegExp->text() ); m_action->setDescription( m_ui->leDescription->text() ); m_action->setAutomatic( m_ui->automatic->isChecked() ); m_action->clearCommands(); foreach ( const ClipCommand& cmd, m_model->commands() ){ m_action->addCommand( cmd ); } } void EditActionDialog::slotAccepted() { saveAction(); qCDebug(KLIPPER_LOG) << "Saving dialogue state"; KConfigGroup grp = KSharedConfig::openConfig()->group("EditActionDialog"); KWindowConfig::saveWindowSize(windowHandle(), grp); grp.writeEntry("ColumnState", m_ui->twCommandList->horizontalHeader()->saveState().toBase64()); accept(); } void EditActionDialog::onAddCommand() { m_model->addCommand(ClipCommand(i18n( "new command" ), i18n( "Command Description" ), true, QLatin1String("") )); m_ui->twCommandList->edit( m_model->index( m_model->rowCount()-1, 0 )); } void EditActionDialog::onRemoveCommand() { m_model->removeCommand(m_ui->twCommandList->selectionModel()->currentIndex()); } void EditActionDialog::onSelectionChanged() { m_ui->pbRemoveCommand->setEnabled( m_ui->twCommandList->selectionModel() && m_ui->twCommandList->selectionModel()->hasSelection() ); } diff --git a/klipper/klipper.cpp b/klipper/klipper.cpp index 525ef876..79060d81 100644 --- a/klipper/klipper.cpp +++ b/klipper/klipper.cpp @@ -1,1042 +1,1042 @@ /* This file is part of the KDE project Copyright (C) by Andrew Stanley-Jones Copyright (C) 2000 by Carsten Pfeiffer Copyright (C) 2004 Esben Mose Hansen Copyright (C) 2008 by Dmitry Suzdalev 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "klipper.h" #include #include "klipper_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "configdialog.h" #include "klippersettings.h" #include "urlgrabber.h" #include "history.h" #include "historyitem.h" #include "historymodel.h" #include "historystringitem.h" #include "klipperpopup.h" #ifdef HAVE_PRISON #include #endif #include #if HAVE_X11 #include #include #endif //#define NOISY_KLIPPER namespace { /** * Use this when manipulating the clipboard * from within clipboard-related signals. * * This avoids issues such as mouse-selections that immediately * disappear. * pattern: Resource Acqusition is Initialisation (RAII) * * (This is not threadsafe, so don't try to use such in threaded * applications). */ struct Ignore { Ignore(int& locklevel) : locklevelref(locklevel) { locklevelref++; } ~Ignore() { locklevelref--; } private: int& locklevelref; }; } // config == KGlobal::config for process, otherwise applet Klipper::Klipper(QObject* parent, const KSharedConfigPtr& config, KlipperMode mode) : QObject( parent ) , m_overflowCounter( 0 ) , m_locklevel( 0 ) , m_config( config ) , m_pendingContentsCheck( false ) , m_mode(mode) { if (m_mode == KlipperMode::Standalone) { setenv("KSNI_NO_DBUSMENU", "1", 1); QDBusConnection::sessionBus().registerObject(QStringLiteral("/klipper"), this, QDBusConnection::ExportScriptableSlots); } updateTimestamp(); // read initial X user time m_clip = qApp->clipboard(); connect( m_clip, &QClipboard::changed, this, &Klipper::newClipData ); connect( &m_overflowClearTimer, &QTimer::timeout, this, &Klipper::slotClearOverflow); m_pendingCheckTimer.setSingleShot( true ); connect( &m_pendingCheckTimer, &QTimer::timeout, this, &Klipper::slotCheckPending); m_history = new History( this ); m_popup = new KlipperPopup(m_history); m_popup->setShowHelp(m_mode == KlipperMode::Standalone); connect(m_history, &History::changed, m_popup, &KlipperPopup::slotHistoryChanged); // we need that collection, otherwise KToggleAction is not happy :} m_collection = new KActionCollection( this ); m_toggleURLGrabAction = new KToggleAction( this ); m_collection->addAction( QStringLiteral("clipboard_action"), m_toggleURLGrabAction ); m_toggleURLGrabAction->setText(i18n("Enable Clipboard Actions")); KGlobalAccel::setGlobalShortcut(m_toggleURLGrabAction, QKeySequence(Qt::ALT+Qt::CTRL+Qt::Key_X)); connect( m_toggleURLGrabAction, &QAction::toggled, this, &Klipper::setURLGrabberEnabled); /* * Create URL grabber */ m_myURLGrabber = new URLGrabber(m_history); connect( m_myURLGrabber, &URLGrabber::sigPopup, this, &Klipper::showPopupMenu ); connect( m_myURLGrabber, &URLGrabber::sigDisablePopup, this, &Klipper::disableURLGrabber ); /* * Load configuration settings */ loadSettings(); // load previous history if configured if (m_bKeepContents) { loadHistory(); } m_clearHistoryAction = m_collection->addAction( QStringLiteral("clear-history") ); m_clearHistoryAction->setIcon( QIcon::fromTheme(QStringLiteral("edit-clear-history")) ); m_clearHistoryAction->setText( i18n("C&lear Clipboard History") ); KGlobalAccel::setGlobalShortcut(m_clearHistoryAction, QKeySequence()); connect(m_clearHistoryAction, &QAction::triggered, this, &Klipper::slotAskClearHistory); QString CONFIGURE=QStringLiteral("configure"); m_configureAction = m_collection->addAction( CONFIGURE ); m_configureAction->setIcon( QIcon::fromTheme(CONFIGURE) ); m_configureAction->setText( i18n("&Configure Klipper...") ); connect(m_configureAction, &QAction::triggered, this, &Klipper::slotConfigure); m_quitAction = m_collection->addAction( QStringLiteral("quit") ); m_quitAction->setIcon( QIcon::fromTheme(QStringLiteral("application-exit")) ); m_quitAction->setText( i18nc("@item:inmenu Quit Klipper", "&Quit") ); connect(m_quitAction, &QAction::triggered, this, &Klipper::slotQuit); m_repeatAction = m_collection->addAction(QStringLiteral("repeat_action")); m_repeatAction->setText(i18n("Manually Invoke Action on Current Clipboard")); KGlobalAccel::setGlobalShortcut(m_repeatAction, QKeySequence(Qt::ALT+Qt::CTRL+Qt::Key_R)); connect(m_repeatAction, &QAction::triggered, this, &Klipper::slotRepeatAction); // add an edit-possibility m_editAction = m_collection->addAction(QStringLiteral("edit_clipboard")); m_editAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); m_editAction->setText(i18n("&Edit Contents...")); m_editAction->setVisible(m_mode == KlipperMode::Standalone); KGlobalAccel::setGlobalShortcut(m_editAction, QKeySequence()); connect(m_editAction, &QAction::triggered, this, [this]() { editData(m_history->first()); } ); #ifdef HAVE_PRISON // add barcode for mobile phones m_showBarcodeAction = m_collection->addAction(QStringLiteral("show-barcode")); m_showBarcodeAction->setText(i18n("&Show Barcode...")); KGlobalAccel::setGlobalShortcut(m_showBarcodeAction, QKeySequence()); connect(m_showBarcodeAction, &QAction::triggered, this, [this]() { showBarcode(m_history->first()); } ); #endif // Cycle through history m_cycleNextAction = m_collection->addAction(QStringLiteral("cycleNextAction")); m_cycleNextAction->setText(i18n("Next History Item")); KGlobalAccel::setGlobalShortcut(m_cycleNextAction, QKeySequence()); connect(m_cycleNextAction, &QAction::triggered, this, &Klipper::slotCycleNext); m_cyclePrevAction = m_collection->addAction(QStringLiteral("cyclePrevAction")); m_cyclePrevAction->setText(i18n("Previous History Item")); KGlobalAccel::setGlobalShortcut(m_cyclePrevAction, QKeySequence()); connect(m_cyclePrevAction, &QAction::triggered, this, &Klipper::slotCyclePrev); // Action to show Klipper popup on mouse position m_showOnMousePos = m_collection->addAction(QStringLiteral("show-on-mouse-pos")); m_showOnMousePos->setText(i18n("Open Klipper at Mouse Position")); KGlobalAccel::setGlobalShortcut(m_showOnMousePos, QKeySequence()); connect(m_showOnMousePos, &QAction::triggered, this, &Klipper::slotPopupMenu); connect ( history(), &History::topChanged, this, &Klipper::slotHistoryTopChanged ); connect( m_popup, &QMenu::aboutToShow, this, &Klipper::slotStartShowTimer ); if (m_mode == KlipperMode::Standalone) { m_popup->plugAction( m_toggleURLGrabAction ); m_popup->plugAction( m_clearHistoryAction ); m_popup->plugAction( m_configureAction ); m_popup->plugAction( m_repeatAction ); m_popup->plugAction( m_editAction ); #ifdef HAVE_PRISON m_popup->plugAction( m_showBarcodeAction ); #endif m_popup->plugAction( m_quitAction ); } // session manager interaction if (m_mode == KlipperMode::Standalone) { connect(qApp, &QGuiApplication::commitDataRequest, this, &Klipper::saveSession); } } Klipper::~Klipper() { delete m_myURLGrabber; } // DBUS QString Klipper::getClipboardContents() { return getClipboardHistoryItem(0); } void Klipper::showKlipperPopupMenu() { slotPopupMenu(); } void Klipper::showKlipperManuallyInvokeActionMenu() { slotRepeatAction(); } // DBUS - don't call from Klipper itself void Klipper::setClipboardContents(QString s) { if (s.isEmpty()) return; Ignore lock( m_locklevel ); updateTimestamp(); HistoryItemPtr item(HistoryItemPtr(new HistoryStringItem(s))); setClipboard( *item, Clipboard | Selection); history()->insert( item ); } // DBUS - don't call from Klipper itself void Klipper::clearClipboardContents() { updateTimestamp(); slotClearClipboard(); } // DBUS - don't call from Klipper itself void Klipper::clearClipboardHistory() { updateTimestamp(); slotClearClipboard(); history()->slotClear(); saveSession(); } // DBUS - don't call from Klipper itself void Klipper::saveClipboardHistory() { if ( m_bKeepContents ) { // save the clipboard eventually saveHistory(); } } void Klipper::slotStartShowTimer() { m_showTimer.start(); } void Klipper::loadSettings() { // Security bug 142882: If user has save clipboard turned off, old data should be deleted from disk static bool firstrun = true; if (!firstrun && m_bKeepContents && !KlipperSettings::keepClipboardContents()) { saveHistory(true); } firstrun=false; m_bKeepContents = KlipperSettings::keepClipboardContents(); m_bReplayActionInHistory = KlipperSettings::replayActionInHistory(); m_bNoNullClipboard = KlipperSettings::preventEmptyClipboard(); // 0 is the id of "Ignore selection" radiobutton m_bIgnoreSelection = KlipperSettings::ignoreSelection(); m_bIgnoreImages = KlipperSettings::ignoreImages(); m_bSynchronize = KlipperSettings::syncClipboards(); // NOTE: not used atm - kregexpeditor is not ported to kde4 m_bUseGUIRegExpEditor = KlipperSettings::useGUIRegExpEditor(); m_bSelectionTextOnly = KlipperSettings::selectionTextOnly(); m_bURLGrabber = KlipperSettings::uRLGrabberEnabled(); // this will cause it to loadSettings too setURLGrabberEnabled(m_bURLGrabber); history()->setMaxSize( KlipperSettings::maxClipItems() ); // Convert 4.3 settings if (KlipperSettings::synchronize() != 3) { // 2 was the id of "Ignore selection" radiobutton m_bIgnoreSelection = KlipperSettings::synchronize() == 2; // 0 was the id of "Synchronize contents" radiobutton m_bSynchronize = KlipperSettings::synchronize() == 0; KConfigSkeletonItem* item = KlipperSettings::self()->findItem(QStringLiteral("SyncClipboards")); item->setProperty(m_bSynchronize); item = KlipperSettings::self()->findItem(QStringLiteral("IgnoreSelection")); item->setProperty(m_bIgnoreSelection); item = KlipperSettings::self()->findItem(QStringLiteral("Synchronize")); // Mark property as converted. item->setProperty(3); KlipperSettings::self()->save(); KlipperSettings::self()->load(); } if (m_bKeepContents && !m_saveFileTimer) { m_saveFileTimer = new QTimer(this); m_saveFileTimer->setSingleShot(true); m_saveFileTimer->setInterval(5000); connect(m_saveFileTimer, &QTimer::timeout, this, [this] { QtConcurrent::run(this, &Klipper::saveHistory, false); } ); connect(m_history, &History::changed, m_saveFileTimer, static_cast(&QTimer::start)); } else { delete m_saveFileTimer; m_saveFileTimer = nullptr; } } void Klipper::saveSettings() const { m_myURLGrabber->saveSettings(); KlipperSettings::self()->setVersion(QStringLiteral(KLIPPER_VERSION_STRING)); KlipperSettings::self()->save(); // other settings should be saved automatically by KConfigDialog } void Klipper::showPopupMenu( QMenu* menu ) { Q_ASSERT( menu != 0L ); QSize size = menu->sizeHint(); // geometry is not valid until it's shown QPoint pos = QCursor::pos(); // ### We can't know where the systray icon is (since it can be hidden or shown // in several places), so the cursor position is the only option. if ( size.height() < pos.y() ) pos.ry() -= size.height(); menu->popup(pos); } bool Klipper::loadHistory() { static const char failed_load_warning[] = "Failed to load history resource. Clipboard history cannot be read."; // don't use "appdata", klipper is also a kicker applet QFile history_file(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("klipper/history2.lst"))); if ( !history_file.exists() ) { qCWarning(KLIPPER_LOG) << failed_load_warning << ": " << "History file does not exist" ; return false; } if ( !history_file.open( QIODevice::ReadOnly ) ) { qCWarning(KLIPPER_LOG) << failed_load_warning << ": " << history_file.errorString() ; return false; } QDataStream file_stream( &history_file ); if( file_stream.atEnd()) { qCWarning(KLIPPER_LOG) << failed_load_warning << ": " << "Error in reading data" ; return false; } QByteArray data; quint32 crc; file_stream >> crc >> data; if( crc32( 0, reinterpret_cast( data.data() ), data.size() ) != crc ) { qCWarning(KLIPPER_LOG) << failed_load_warning << ": " << "CRC checksum does not match" ; return false; } QDataStream history_stream( &data, QIODevice::ReadOnly ); char* version; history_stream >> version; delete[] version; // The list needs to be reversed, as it is saved // youngest-first to keep the most important clipboard // items at the top, but the history is created oldest // first. - QList reverseList; + QVector reverseList; for ( HistoryItemPtr item = HistoryItem::create( history_stream ); !item.isNull(); item = HistoryItem::create( history_stream ) ) { reverseList.prepend( item ); } history()->slotClear(); for ( auto it = reverseList.constBegin(); it != reverseList.constEnd(); ++it ) { history()->forceInsert(*it); } if ( !history()->empty() ) { setClipboard( *history()->first(), Clipboard | Selection ); } return true; } void Klipper::saveHistory(bool empty) { QMutexLocker lock(m_history->model()->mutex()); static const char failed_save_warning[] = "Failed to save history. Clipboard history cannot be saved."; // don't use "appdata", klipper is also a kicker applet QString history_file_name(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("klipper/history2.lst"))); if ( history_file_name.isNull() || history_file_name.isEmpty() ) { // try creating the file QDir dir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); if (!dir.mkpath(QStringLiteral("klipper"))) { qCWarning(KLIPPER_LOG) << failed_save_warning ; return; } history_file_name = dir.absoluteFilePath(QStringLiteral("klipper/history2.lst")); } if ( history_file_name.isNull() || history_file_name.isEmpty() ) { qCWarning(KLIPPER_LOG) << failed_save_warning ; return; } QSaveFile history_file( history_file_name ); if (!history_file.open(QIODevice::WriteOnly)) { qCWarning(KLIPPER_LOG) << failed_save_warning ; return; } QByteArray data; QDataStream history_stream( &data, QIODevice::WriteOnly ); history_stream << KLIPPER_VERSION_STRING; // const char* if (!empty) { HistoryItemConstPtr item = history()->first(); if (item) { do { history_stream << item.data(); item = HistoryItemConstPtr(history()->find(item->next_uuid())); } while (item != history()->first()); } } quint32 crc = crc32( 0, reinterpret_cast( data.data() ), data.size() ); QDataStream ds ( &history_file ); ds << crc << data; if (!history_file.commit()) { qCWarning(KLIPPER_LOG) << failed_save_warning ; } } // save session on shutdown. Don't simply use the c'tor, as that may not be called. void Klipper::saveSession() { if ( m_bKeepContents ) { // save the clipboard eventually saveHistory(); } saveSettings(); } void Klipper::disableURLGrabber() { KMessageBox::information( 0L, i18n( "You can enable URL actions later by left-clicking on the " "Klipper icon and selecting 'Enable Clipboard Actions'" ) ); setURLGrabberEnabled( false ); } void Klipper::slotConfigure() { if (KConfigDialog::showDialog(QStringLiteral("preferences"))) { return; } ConfigDialog *dlg = new ConfigDialog( 0, KlipperSettings::self(), this, m_collection ); connect(dlg, &KConfigDialog::settingsChanged, this, &Klipper::loadSettings); dlg->show(); } void Klipper::slotQuit() { // If the menu was just opened, likely the user // selected quit by accident while attempting to // click the Klipper icon. if ( m_showTimer.elapsed() < 300 ) { return; } saveSession(); int autoStart = KMessageBox::questionYesNoCancel(0, i18n("Should Klipper start automatically when you login?"), i18n("Automatically Start Klipper?"), KGuiItem(i18n("Start")), KGuiItem(i18n("Do Not Start")), KStandardGuiItem::cancel(), QStringLiteral("StartAutomatically")); KConfigGroup config( KSharedConfig::openConfig(), "General"); if ( autoStart == KMessageBox::Yes ) { config.writeEntry("AutoStart", true); } else if ( autoStart == KMessageBox::No) { config.writeEntry("AutoStart", false); } else // cancel chosen don't quit return; config.sync(); qApp->quit(); } void Klipper::slotPopupMenu() { m_popup->ensureClean(); m_popup->slotSetTopActive(); showPopupMenu( m_popup ); } void Klipper::slotRepeatAction() { auto top = qSharedPointerCast( history()->first() ); if ( top ) { m_myURLGrabber->invokeAction( top ); } } void Klipper::setURLGrabberEnabled( bool enable ) { if (enable != m_bURLGrabber) { m_bURLGrabber = enable; m_lastURLGrabberTextSelection.clear(); m_lastURLGrabberTextClipboard.clear(); KlipperSettings::setURLGrabberEnabled(enable); } m_toggleURLGrabAction->setChecked( enable ); // make it update its settings m_myURLGrabber->loadSettings(); } void Klipper::slotHistoryTopChanged() { if ( m_locklevel ) { return; } auto topitem = history()->first(); if ( topitem ) { setClipboard( *topitem, Clipboard | Selection ); } if ( m_bReplayActionInHistory && m_bURLGrabber ) { slotRepeatAction(); } } void Klipper::slotClearClipboard() { Ignore lock( m_locklevel ); m_clip->clear(QClipboard::Selection); m_clip->clear(QClipboard::Clipboard); } HistoryItemPtr Klipper::applyClipChanges( const QMimeData* clipData ) { if ( m_locklevel ) { return HistoryItemPtr(); } Ignore lock( m_locklevel ); HistoryItemPtr item = HistoryItem::create( clipData ); history()->insert( item ); return item; } void Klipper::newClipData( QClipboard::Mode mode ) { if ( m_locklevel ) { return; } if( mode == QClipboard::Selection && blockFetchingNewData()) return; checkClipData( mode == QClipboard::Selection ? true : false ); } // Protection against too many clipboard data changes. Lyx responds to clipboard data // requests with setting new clipboard data, so if Lyx takes over clipboard, // Klipper notices, requests this data, this triggers "new" clipboard contents // from Lyx, so Klipper notices again, requests this data, ... you get the idea. const int MAX_CLIPBOARD_CHANGES = 10; // max changes per second bool Klipper::blockFetchingNewData() { #if HAVE_X11 // Hacks for #85198 and #80302. // #85198 - block fetching new clipboard contents if Shift is pressed and mouse is not, // this may mean the user is doing selection using the keyboard, in which case // it's possible the app sets new clipboard contents after every change - Klipper's // history would list them all. // #80302 - OOo (v1.1.3 at least) has a bug that if Klipper requests its clipboard contents // while the user is doing a selection using the mouse, OOo stops updating the clipboard // contents, so in practice it's like the user has selected only the part which was // selected when Klipper asked first. // Use XQueryPointer rather than QApplication::mouseButtons()/keyboardModifiers(), because // Klipper needs the very current state. if (!KWindowSystem::isPlatformX11()) { return false; } xcb_connection_t *c = QX11Info::connection(); const xcb_query_pointer_cookie_t cookie = xcb_query_pointer_unchecked(c, QX11Info::appRootWindow()); QScopedPointer queryPointer(xcb_query_pointer_reply(c, cookie, nullptr)); if (queryPointer.isNull()) { return false; } if (((queryPointer->mask & (XCB_KEY_BUT_MASK_SHIFT | XCB_KEY_BUT_MASK_BUTTON_1)) == XCB_KEY_BUT_MASK_SHIFT) // BUG: 85198 || ((queryPointer->mask & XCB_KEY_BUT_MASK_BUTTON_1) == XCB_KEY_BUT_MASK_BUTTON_1)) { // BUG: 80302 m_pendingContentsCheck = true; m_pendingCheckTimer.start( 100 ); return true; } m_pendingContentsCheck = false; if ( m_overflowCounter == 0 ) m_overflowClearTimer.start( 1000 ); if( ++m_overflowCounter > MAX_CLIPBOARD_CHANGES ) return true; #endif return false; } void Klipper::slotCheckPending() { if( !m_pendingContentsCheck ) return; m_pendingContentsCheck = false; // blockFetchingNewData() will be called again updateTimestamp(); newClipData( QClipboard::Selection ); // always selection } void Klipper::checkClipData( bool selectionMode ) { if ( ignoreClipboardChanges() ) // internal to klipper, ignoring QSpinBox selections { // keep our old clipboard, thanks // This won't quite work, but it's close enough for now. // The trouble is that the top selection =! top clipboard // but we don't track that yet. We will.... auto top = history()->first(); if ( top ) { setClipboard( *top, selectionMode ? Selection : Clipboard); } return; } // debug code #ifdef NOISY_KLIPPER qCDebug(KLIPPER_LOG) << "Checking clip data"; if ( sender() ) { qCDebug(KLIPPER_LOG) << "sender=" << sender()->objectName(); } else { qCDebug(KLIPPER_LOG) << "no sender"; } qCDebug(KLIPPER_LOG) << "\nselectionMode=" << selectionMode << "\nowning (sel,cli)=(" << m_clip->ownsSelection() << "," << m_clip->ownsClipboard() << ")" << "\ntext=" << m_clip->text( selectionMode ? QClipboard::Selection : QClipboard::Clipboard) << endl; #endif const QMimeData* data = m_clip->mimeData( selectionMode ? QClipboard::Selection : QClipboard::Clipboard ); if ( !data ) { qCWarning(KLIPPER_LOG) << "No data in clipboard. This not not supposed to happen."; return; } bool changed = true; // ### FIXME (only relevant under polling, might be better to simply remove polling and rely on XFixes) bool clipEmpty = data->formats().isEmpty(); if (clipEmpty) { // Might be a timeout. Try again clipEmpty = data->formats().isEmpty(); #ifdef NOISY_KLIPPER qCDebug(KLIPPER_LOG) << "was empty. Retried, now " << (clipEmpty?" still empty":" no longer empty"); #endif } if ( changed && clipEmpty && m_bNoNullClipboard ) { auto top = history()->first(); if ( top ) { // keep old clipboard after someone set it to null #ifdef NOISY_KLIPPER qCDebug(KLIPPER_LOG) << "Resetting clipboard (Prevent empty clipboard)"; #endif setClipboard( *top, selectionMode ? Selection : Clipboard ); } return; } // this must be below the "bNoNullClipboard" handling code! // XXX: I want a better handling of selection/clipboard in general. // XXX: Order sensitive code. Must die. if ( selectionMode && m_bIgnoreSelection ) return; if( selectionMode && m_bSelectionTextOnly && !data->hasText()) return; if( data->hasUrls() ) ; // ok else if( data->hasText() ) ; // ok else if( data->hasImage() ) { if( m_bIgnoreImages ) return; } else // unknown, ignore return; HistoryItemPtr item = applyClipChanges( data ); if (changed) { #ifdef NOISY_KLIPPER qCDebug(KLIPPER_LOG) << "Synchronize?" << m_bSynchronize; #endif if ( m_bSynchronize && item ) { setClipboard( *item, selectionMode ? Clipboard : Selection ); } } QString& lastURLGrabberText = selectionMode ? m_lastURLGrabberTextSelection : m_lastURLGrabberTextClipboard; if( m_bURLGrabber && item && data->hasText()) { m_myURLGrabber->checkNewData( qSharedPointerConstCast(item) ); // Make sure URLGrabber doesn't repeat all the time if klipper reads the same // text all the time (e.g. because XFixes is not available and the application // has broken TIMESTAMP target). Using most recent history item may not always // work. if ( item->text() != lastURLGrabberText ) { lastURLGrabberText = item->text(); } } else { lastURLGrabberText.clear(); } } void Klipper::setClipboard( const HistoryItem& item, int mode ) { Ignore lock( m_locklevel ); Q_ASSERT( ( mode & 1 ) == 0 ); // Warn if trying to pass a boolean as a mode. if ( mode & Selection ) { #ifdef NOISY_KLIPPER qCDebug(KLIPPER_LOG) << "Setting selection to <" << item.text() << ">"; #endif m_clip->setMimeData( item.mimeData(), QClipboard::Selection ); } if ( mode & Clipboard ) { #ifdef NOISY_KLIPPER qCDebug(KLIPPER_LOG) << "Setting clipboard to <" << item.text() << ">"; #endif m_clip->setMimeData( item.mimeData(), QClipboard::Clipboard ); } } void Klipper::slotClearOverflow() { m_overflowClearTimer.stop(); if( m_overflowCounter > MAX_CLIPBOARD_CHANGES ) { qCDebug(KLIPPER_LOG) << "App owning the clipboard/selection is lame"; // update to the latest data - this unfortunately may trigger the problem again newClipData( QClipboard::Selection ); // Always the selection. } m_overflowCounter = 0; } QStringList Klipper::getClipboardHistoryMenu() { QStringList menu; auto item = history()->first(); if (item) { do { menu << item->text(); item = history()->find(item->next_uuid()); } while (item != history()->first()); } return menu; } QString Klipper::getClipboardHistoryItem(int i) { auto item = history()->first(); if (item) { do { if (i-- == 0) { return item->text(); } item = history()->find(item->next_uuid()); } while (item != history()->first()); } return QString(); } // // changing a spinbox in klipper's config-dialog causes the lineedit-contents // of the spinbox to be selected and hence the clipboard changes. But we don't // want all those items in klipper's history. See #41917 // bool Klipper::ignoreClipboardChanges() const { QWidget *focusWidget = qApp->focusWidget(); if ( focusWidget ) { if ( focusWidget->inherits( "QSpinBox" ) || (focusWidget->parentWidget() && focusWidget->inherits("QLineEdit") && focusWidget->parentWidget()->inherits("QSpinWidget")) ) { return true; } } return false; } void Klipper::updateTimestamp() { #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { QX11Info::setAppTime(QX11Info::getTimestamp()); } #endif } void Klipper::editData(const QSharedPointer< const HistoryItem > &item) { QPointer dlg(new QDialog()); dlg->setWindowTitle( i18n("Edit Contents") ); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dlg); buttons->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttons, &QDialogButtonBox::accepted, dlg.data(), &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, dlg.data(), &QDialog::reject); connect(dlg.data(), &QDialog::finished, dlg.data(), [this, dlg, item](int result) { emit editFinished(item, result); dlg->deleteLater(); } ); KTextEdit *edit = new KTextEdit( dlg ); if (item) { edit->setText( item->text() ); } edit->setFocus(); edit->setMinimumSize( 300, 40 ); QVBoxLayout *layout = new QVBoxLayout(dlg); layout->addWidget(edit); layout->addWidget(buttons); dlg->adjustSize(); connect(dlg.data(), &QDialog::accepted, this, [this, edit, item]() { QString text = edit->toPlainText(); if (item) { m_history->remove( item ); } m_history->insert(HistoryItemPtr(new HistoryStringItem(text))); if (m_myURLGrabber) { m_myURLGrabber->checkNewData(HistoryItemConstPtr(m_history->first())); } }); if (m_mode == KlipperMode::Standalone) { dlg->setModal(true); dlg->exec(); } else if (m_mode == KlipperMode::DataEngine) { dlg->open(); } } #ifdef HAVE_PRISON class BarcodeLabel : public QLabel { public: BarcodeLabel(Prison::AbstractBarcode *barcode, QWidget *parent = nullptr) : QLabel(parent) , m_barcode(barcode) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); setPixmap(QPixmap::fromImage(m_barcode->toImage(size()))); } protected: void resizeEvent(QResizeEvent *event) override { QLabel::resizeEvent(event); setPixmap(QPixmap::fromImage(m_barcode->toImage(event->size()))); } private: QScopedPointer m_barcode; }; void Klipper::showBarcode(const QSharedPointer< const HistoryItem > &item) { using namespace Prison; QPointer dlg(new QDialog()); dlg->setWindowTitle( i18n("Mobile Barcode") ); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok, dlg); buttons->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttons, &QDialogButtonBox::accepted, dlg.data(), &QDialog::accept); connect(dlg.data(), &QDialog::finished, dlg.data(), &QDialog::deleteLater); QWidget* mw = new QWidget(dlg); QHBoxLayout* layout = new QHBoxLayout(mw); AbstractBarcode *qrCode = createBarcode(QRCode); AbstractBarcode *dataMatrix = createBarcode(DataMatrix); if (item) { qrCode->setData(item->text()); dataMatrix->setData(item->text()); } BarcodeLabel *qrCodeLabel = new BarcodeLabel(qrCode, mw); BarcodeLabel *dataMatrixLabel = new BarcodeLabel(dataMatrix, mw); layout->addWidget(qrCodeLabel); layout->addWidget(dataMatrixLabel); mw->setFocus(); QVBoxLayout *vBox = new QVBoxLayout(dlg); vBox->addWidget(mw); vBox->addWidget(buttons); dlg->adjustSize(); if (m_mode == KlipperMode::Standalone) { dlg->setModal(true); dlg->exec(); } else if (m_mode == KlipperMode::DataEngine) { dlg->open(); } } #endif //HAVE_PRISON void Klipper::slotAskClearHistory() { int clearHist = KMessageBox::questionYesNo(0, i18n("Really delete entire clipboard history?"), i18n("Delete clipboard history?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("really_clear_history"), KMessageBox::Dangerous); if (clearHist == KMessageBox::Yes) { history()->slotClear(); slotClearClipboard(); saveHistory(); } } void Klipper::slotCycleNext() { //do cycle and show popup only if we have something in clipboard if (m_history->first()) { m_history->cycleNext(); emit passivePopup(i18n("Clipboard history"), cycleText()); } } void Klipper::slotCyclePrev() { //do cycle and show popup only if we have something in clipboard if (m_history->first()) { m_history->cyclePrev(); emit passivePopup(i18n("Clipboard history"), cycleText()); } } QString Klipper::cycleText() const { const int WIDTH_IN_PIXEL = 400; auto itemprev = m_history->prevInCycle(); auto item = m_history->first(); auto itemnext = m_history->nextInCycle(); QFontMetrics font_metrics(m_popup->fontMetrics()); QString result(QStringLiteral("")); if (itemprev) { result += QLatin1String(""); } result += QLatin1String(""); if (itemnext) { result += QLatin1String(""); } result += QLatin1String("
"); result += i18n("up"); result += QLatin1String(""); result += font_metrics.elidedText(itemprev->text().simplified().toHtmlEscaped(), Qt::ElideMiddle, WIDTH_IN_PIXEL); result += QLatin1String("
"); result += i18n("current"); result += QLatin1String(""); result += font_metrics.elidedText(item->text().simplified().toHtmlEscaped(), Qt::ElideMiddle, WIDTH_IN_PIXEL); result += QLatin1String("
"); result += i18n("down"); result += QLatin1String(""); result += font_metrics.elidedText(itemnext->text().simplified().toHtmlEscaped(), Qt::ElideMiddle, WIDTH_IN_PIXEL); result += QLatin1String("
"); return result; } diff --git a/krunner/dialog.h b/krunner/dialog.h index a34152a5..a757308e 100644 --- a/krunner/dialog.h +++ b/krunner/dialog.h @@ -1,205 +1,205 @@ /*************************************************************************** * Copyright 2011 Marco Martin * * 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 . * ***************************************************************************/ #ifndef DIALOG_PROXY_P #define DIALOG_PROXY_P #include #include #include #include #include #include // // W A R N I N G // ------------- // // This file is not part of the public Plasma API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // class QQuickItem; class QScreen; namespace PlasmaQuick { class DialogPrivate; /** * QML wrapper for dialogs * * Exposed as `PlasmaCore.Dialog` in QML. */ class Dialog : public QQuickWindow, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) /** * The main QML item that will be displayed in the Dialog */ Q_PROPERTY(QQuickItem *mainItem READ mainItem WRITE setMainItem NOTIFY mainItemChanged) /** * The main QML item that will be displayed in the Dialog */ Q_PROPERTY(QQuickItem *visualParent READ visualParent WRITE setVisualParent NOTIFY visualParentChanged) /** * Margins of the dialog around the mainItem. * @see DialogMargins */ Q_PROPERTY(QObject *margins READ margins CONSTANT) /** * Plasma Location of the dialog window. Useful if this dialog is a popup for a panel */ Q_PROPERTY(Plasma::Types::Location location READ location WRITE setLocation NOTIFY locationChanged) /** * Type of the window */ Q_PROPERTY(WindowType type READ type WRITE setType NOTIFY typeChanged) /** * Whether the dialog should be hidden when the dialog loses focus. * * The default value is @c false. **/ Q_PROPERTY(bool hideOnWindowDeactivate READ hideOnWindowDeactivate WRITE setHideOnWindowDeactivate NOTIFY hideOnWindowDeactivateChanged) /** * Whether the dialog is output only. Default value is @c false. If it is @c true * the dialog does not accept input and all pointer events are not accepted, thus the dialog * is click through. * * This property is currently only supported on the X11 platform. On any other platform the * property has no effect. **/ Q_PROPERTY(bool outputOnly READ isOutputOnly WRITE setOutputOnly NOTIFY outputOnlyChanged) /** * This property holds the window flags of the window. * The window flags control the window's appearance in the windowing system, * whether it's a dialog, popup, or a regular window, and whether it should * have a title bar, etc. * Regardless to what the user sets, the flags will always have the * FramelessWindowHint flag set */ Q_PROPERTY(Qt::WindowFlags flags READ flags WRITE setFramelessFlags NOTIFY flagsChanged) Q_CLASSINFO("DefaultProperty", "mainItem") public: enum WindowType { Normal = NET::Normal, Dock = NET::Dock, DialogWindow = NET::Dialog, PopupMenu = NET::PopupMenu, Tooltip = NET::Tooltip, Notification = NET::Notification }; - Q_ENUMS(WindowType) + Q_ENUM(WindowType) Dialog(QQuickItem *parent = 0); ~Dialog() override; //PROPERTIES ACCESSORS QQuickItem *mainItem() const; void setMainItem(QQuickItem *mainItem); QQuickItem *visualParent() const; void setVisualParent(QQuickItem *visualParent); Plasma::Types::Location location() const; void setLocation(Plasma::Types::Location location); QObject *margins() const; void setFramelessFlags(Qt::WindowFlags flags); void setType(WindowType type); WindowType type() const; bool hideOnWindowDeactivate() const; void setHideOnWindowDeactivate(bool hide); void setOutputOnly(bool outputOnly); bool isOutputOnly() const; /** * @returns The suggested screen position for the popup * @arg item the item the popup has to be positioned relatively to. if null, the popup will be positioned in the center of the window * @arg alignment alignment of the popup compared to the item */ virtual QPoint popupPosition(QQuickItem *item, const QSize &size); Q_SIGNALS: void mainItemChanged(); void locationChanged(); void visualParentChanged(); void typeChanged(); void hideOnWindowDeactivateChanged(); void outputOnlyChanged(); void flagsChanged(); protected: /* * set the dialog position. subclasses may change it. ToolTipDialog adjusts the position in an animated way */ virtual void adjustGeometry(const QRect &geom); //Reimplementations void classBegin() override; void componentComplete() override; void resizeEvent(QResizeEvent *re) override; void focusInEvent(QFocusEvent *ev) override; void focusOutEvent(QFocusEvent *ev) override; void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; bool event(QEvent *event) override; private: friend class DialogPrivate; DialogPrivate *const d; Q_PRIVATE_SLOT(d, void syncBorders()) Q_PRIVATE_SLOT(d, void updateContrast()) Q_PRIVATE_SLOT(d, void updateVisibility(bool visible)) Q_PRIVATE_SLOT(d, void updateMinimumWidth()) Q_PRIVATE_SLOT(d, void updateMinimumHeight()) Q_PRIVATE_SLOT(d, void updateMaximumWidth()) Q_PRIVATE_SLOT(d, void updateMaximumHeight()) Q_PRIVATE_SLOT(d, void syncMainItemToSize()) Q_PRIVATE_SLOT(d, void syncToMainItemSize()) Q_PRIVATE_SLOT(d, void requestSyncToMainItemSize(bool delayed)) }; } #endif diff --git a/kuiserver/progresslistdelegate.h b/kuiserver/progresslistdelegate.h index 0fa3d6f8..a075ee62 100644 --- a/kuiserver/progresslistdelegate.h +++ b/kuiserver/progresslistdelegate.h @@ -1,65 +1,64 @@ /* * This file is part of the KDE project * Copyright (C) 2006-2008 Rafael Fernández López * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * 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 PROGRESSLISTDELEGATE_H #define PROGRESSLISTDELEGATE_H #include #include class QListView; class ProgressListDelegate : public KWidgetItemDelegate { Q_OBJECT - Q_ENUMS(ProgressItemRole) public: explicit ProgressListDelegate(QObject *parent = 0, QListView *listView = 0); ~ProgressListDelegate() override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setSeparatorPixels(int separatorPixels); void setLeftMargin(int leftMargin); void setRightMargin(int rightMargin); void setMinimumItemHeight(int minimumItemHeight); void setMinimumContentWidth(int minimumContentWidth); void setEditorHeight(int editorHeight); protected: QList createItemWidgets(const QModelIndex &index) const override; void updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override; private Q_SLOTS: void slotPauseResumeClicked(); void slotCancelClicked(); void slotClearClicked(); private: class Private; Private *d; }; #endif // PROGRESSLISTDELEGATE_H diff --git a/libtaskmanager/abstracttasksmodel.h b/libtaskmanager/abstracttasksmodel.h index cb6e74f5..b0b10dbd 100644 --- a/libtaskmanager/abstracttasksmodel.h +++ b/libtaskmanager/abstracttasksmodel.h @@ -1,280 +1,279 @@ /******************************************************************** Copyright 2016 Eike Hein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef ABSTRACTTASKSMODEL_H #define ABSTRACTTASKSMODEL_H #include "abstracttasksmodeliface.h" #include #include "taskmanager_export.h" namespace TaskManager { /** * @short An abstract base class for (flat) tasks models. * * This class serves as abstract base class for flat tasks model implementations. * It provides data roles and no-op default implementations of methods in the * AbstractTasksModelIface interface. * * @author Eike Hein **/ class TASKMANAGER_EXPORT AbstractTasksModel : public QAbstractListModel, public AbstractTasksModelIface { Q_OBJECT - Q_ENUMS(AdditionalRoles) - public: enum AdditionalRoles { AppId = Qt::UserRole + 1, /**< KService storage id (.desktop name sans extension). */ AppName, /**< Application name. */ GenericName, /**< Generic application name. */ LauncherUrl, /**< URL that can be used to launch this application (.desktop or executable). */ LauncherUrlWithoutIcon, /**< Special path to get a launcher URL while skipping fallback icon encoding. Used as speed optimization. */ LegacyWinIdList, /**< X11 window ids. Stopgap until we have something better. */ MimeType, /**< MIME type for this task (window, window group), needed for DND. */ MimeData, /**< Data for MimeType. */ IsWindow, /**< This is a window task. */ IsStartup, /**< This is a startup task. */ IsLauncher, /**< This is a launcher task. */ IsGroupParent, /**< This is a parent item for a group of child tasks. */ IsGroupable, /**< Whether this task is being ignored by grouping or not. */ IsActive, /**< This is the currently active task. */ IsClosable, /**< requestClose (see below) available. */ IsMovable, /**< requestMove (see below) available. */ IsResizable, /**< requestResize (see below) available. */ IsMaximizable, /**< requestToggleMaximize (see below) available. */ IsMaximized, /**< Task (i.e. window) is maximized. */ IsMinimizable, /**< requestToggleMinimize (see below) available. */ IsMinimized, /**< Task (i.e. window) is minimized. */ IsKeepAbove, /**< Task (i.e. window) is keep-above. */ IsKeepBelow, /**< Task (i.e. window) is keep-below. */ IsFullScreenable, /**< requestToggleFullScreen (see below) available. */ IsFullScreen, /**< Task (i.e. window) is fullscreen. */ IsShadeable, /**< requestToggleShade (see below) available. */ IsShaded, /**< Task (i.e. window) is shaded. */ IsVirtualDesktopChangeable, /**< requestVirtualDesktop (see below) available. */ VirtualDesktop, /**< Virtual desktop for the task (i.e. window). */ IsOnAllVirtualDesktops, /**< Task is on all virtual desktops. */ Geometry, /**< The task's geometry (i.e. the window's). */ ScreenGeometry, /**< Screen geometry for the task (i.e. the window's screen). */ Activities, /**< Activities for the task (i.e. window). */ IsDemandingAttention, /**< Task is demanding attention. */ SkipTaskbar, /**< Task should not be shown in a 'task bar' user interface. */ SkipPager, /**< Task should not to be shown in a 'pager' user interface. */ }; + Q_ENUM(AdditionalRoles) explicit AbstractTasksModel(QObject *parent = nullptr); virtual ~AbstractTasksModel(); QHash roleNames() const override; virtual QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; /** * Request activation of the task at the given index. Derived classes are * free to interpret the meaning of "activate" themselves depending on * the nature and state of the task, e.g. launch or raise a window task. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestActivate(const QModelIndex &index) override; /** * Request an additional instance of the application backing the task * at the given index. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestNewInstance(const QModelIndex &index) override; /** * Requests to open the given URLs with the application backing the task * at the given index. * * This base implementation does nothing. * * @param index An index in this tasks model. * @param urls The URLs to be passed to the application. **/ - virtual void requestOpenUrls(const QModelIndex &index, const QList &urls); + virtual void requestOpenUrls(const QModelIndex &index, const QList &urls) override; /** * Request the task at the given index be closed. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestClose(const QModelIndex &index) override; /** * Request starting an interactive move for the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestMove(const QModelIndex &index) override; /** * Request starting an interactive resize for the task at the given index. * * This is meant for tasks that have an associated window, and may be a * no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestResize(const QModelIndex &index) override; /** * Request toggling the minimized state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestToggleMinimized(const QModelIndex &index) override; /** * Request toggling the maximized state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestToggleMaximized(const QModelIndex &index) override; /** * Request toggling the keep-above state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestToggleKeepAbove(const QModelIndex &index) override; /** * Request toggling the keep-below state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestToggleKeepBelow(const QModelIndex &index) override; /** * Request toggling the fullscreen state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestToggleFullScreen(const QModelIndex &index) override; /** * Request toggling the shaded state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ virtual void requestToggleShaded(const QModelIndex &index) override; /** * Request moving the task at the given index to the specified virtual * desktop. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. * @param desktop A virtual desktop number. **/ virtual void requestVirtualDesktop(const QModelIndex &index, qint32 desktop) override; /** * Request moving the task at the given index to the specified activities. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. * @param activities The new list of activities. **/ virtual void requestActivities(const QModelIndex &index, const QStringList &activities) override; /** * Request informing the window manager of new geometry for a visual * delegate for the task at the given index. The geometry should be in * screen coordinates. * * This base implementation does nothing. * * @param index An index in this tasks model. * @param geometry Visual delegate geometry in screen coordinates. * @param delegate The delegate. Implementations are on their own with * regard to extracting information from this, and should take care to * reject invalid objects. **/ virtual void requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate = nullptr) override; }; } #endif diff --git a/libtaskmanager/abstracttasksproxymodeliface.h b/libtaskmanager/abstracttasksproxymodeliface.h index 4c627f13..13c5f64a 100644 --- a/libtaskmanager/abstracttasksproxymodeliface.h +++ b/libtaskmanager/abstracttasksproxymodeliface.h @@ -1,209 +1,209 @@ /******************************************************************** Copyright 2016 Eike Hein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef ABSTRACTASKSPROXYMODELIFACE_H #define ABSTRACTASKSPROXYMODELIFACE_H #include #include "taskmanager_export.h" #include "abstracttasksmodeliface.h" namespace TaskManager { /** * @short Pure method interface for tasks model implementations. * * This is the pure method interface implemented by AbstractTasksModel, * as well as other model classes in this library which cannot inherit from * AbstractTasksModel. * * @author Eike Hein **/ class TASKMANAGER_EXPORT AbstractTasksProxyModelIface : public AbstractTasksModelIface { public: ~AbstractTasksProxyModelIface() {} /** * Request activation of the task at the given index. Implementing classes * are free to interpret the meaning of "activate" themselves depending on * the nature and state of the task, e.g. launch or raise a window task. * * @param index An index in this tasks model. **/ void requestActivate(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request an additional instance of the application backing the task * at the given index. * * @param index An index in this tasks model. **/ void requestNewInstance(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Requests to open the given URLs with the application backing the task * at the given index. * * @param index An index in this tasks model. * @param urls The URLs to be passed to the application. **/ - virtual void requestOpenUrls(const QModelIndex &index, const QList &urls); + virtual void requestOpenUrls(const QModelIndex &index, const QList &urls) Q_DECL_OVERRIDE; /** * Request the task at the given index be closed. * * @param index An index in this tasks model. **/ void requestClose(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request starting an interactive move for the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestMove(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request starting an interactive resize for the task at the given index. * * This is meant for tasks that have an associated window, and may be a * no-op when there is no window. * * @param index An index in this tasks model. **/ void requestResize(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request toggling the minimized state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleMinimized(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request toggling the maximized state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleMaximized(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request toggling the keep-above state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleKeepAbove(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request toggling the keep-below state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleKeepBelow(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request toggling the fullscreen state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleFullScreen(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request toggling the shaded state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleShaded(const QModelIndex &index) Q_DECL_OVERRIDE; /** * Request moving the task at the given index to the specified virtual * desktop. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. * @param desktop A virtual desktop number. **/ void requestVirtualDesktop(const QModelIndex &index, qint32 desktop = -1) Q_DECL_OVERRIDE; /** * Request moving the task at the given index to the specified activities. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. * @param activities The new list of activities. **/ void requestActivities(const QModelIndex &index, const QStringList &activities) Q_DECL_OVERRIDE; /** * Request informing the window manager of new geometry for a visual * delegate for the task at the given index. The geometry should be in * screen coordinates. * * @param index An index in this tasks model. * @param geometry Visual delegate geometry in screen coordinates. * @param delegate The delegate. Implementations are on their own with * regard to extracting information from this, and should take care to * reject invalid objects. **/ void requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate = nullptr) Q_DECL_OVERRIDE; protected: /* * Map the passed QModelIndex to the source model index * so that AbstractTasksModelIface methods can be passed on * Subclasses should override this. */ virtual QModelIndex mapIfaceToSource(const QModelIndex &index) const = 0; }; } #endif diff --git a/libtaskmanager/activityinfo.cpp b/libtaskmanager/activityinfo.cpp index 8427cfd6..c003ca2f 100644 --- a/libtaskmanager/activityinfo.cpp +++ b/libtaskmanager/activityinfo.cpp @@ -1,103 +1,99 @@ /******************************************************************** Copyright 2016 Eike Hein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "activityinfo.h" #include namespace TaskManager { class ActivityInfo::Private { public: Private(ActivityInfo *q); ~Private(); static int instanceCount; static KActivities::Consumer* activityConsumer; - -private: - ActivityInfo *q; }; int ActivityInfo::Private::instanceCount = 0; KActivities::Consumer* ActivityInfo::Private::activityConsumer = nullptr; -ActivityInfo::Private::Private(ActivityInfo *q) - : q(q) +ActivityInfo::Private::Private(ActivityInfo *) { ++instanceCount; } ActivityInfo::Private::~Private() { --instanceCount; if (!instanceCount) { delete activityConsumer; activityConsumer = nullptr; } } ActivityInfo::ActivityInfo(QObject *parent) : QObject(parent) , d(new Private(this)) { if (!d->activityConsumer) { d->activityConsumer = new KActivities::Consumer(); } connect(d->activityConsumer, &KActivities::Consumer::currentActivityChanged, this, &ActivityInfo::currentActivityChanged); connect(d->activityConsumer, &KActivities::Consumer::runningActivitiesChanged, this, &ActivityInfo::numberOfRunningActivitiesChanged); } ActivityInfo::~ActivityInfo() { } QString ActivityInfo::currentActivity() const { return d->activityConsumer->currentActivity(); } int ActivityInfo::numberOfRunningActivities() const { return d->activityConsumer->activities(KActivities::Info::State::Running).count(); } QStringList ActivityInfo::runningActivities() const { return d->activityConsumer->activities(KActivities::Info::State::Running); } QString ActivityInfo::activityName(const QString &id) { KActivities::Info info(id); if (info.state() != KActivities::Info::Invalid) { return info.name(); } return QString(); } } diff --git a/libtaskmanager/flattentaskgroupsproxymodel.cpp b/libtaskmanager/flattentaskgroupsproxymodel.cpp index 8b02d571..26ac9967 100644 --- a/libtaskmanager/flattentaskgroupsproxymodel.cpp +++ b/libtaskmanager/flattentaskgroupsproxymodel.cpp @@ -1,64 +1,60 @@ /******************************************************************** Copyright 2016 Eike Hein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "flattentaskgroupsproxymodel.h" namespace TaskManager { class FlattenTaskGroupsProxyModel::Private { public: Private(FlattenTaskGroupsProxyModel *q); AbstractTasksModelIface *sourceTasksModel = nullptr; - -private: - FlattenTaskGroupsProxyModel *q; }; -FlattenTaskGroupsProxyModel::Private::Private(FlattenTaskGroupsProxyModel *q) - : q(q) +FlattenTaskGroupsProxyModel::Private::Private(FlattenTaskGroupsProxyModel *) { } FlattenTaskGroupsProxyModel::FlattenTaskGroupsProxyModel(QObject *parent) : KDescendantsProxyModel(parent) , d(new Private(this)) { } FlattenTaskGroupsProxyModel::~FlattenTaskGroupsProxyModel() { } void FlattenTaskGroupsProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { d->sourceTasksModel = dynamic_cast(sourceModel); KDescendantsProxyModel::setSourceModel(sourceModel); } QModelIndex FlattenTaskGroupsProxyModel::mapIfaceToSource(const QModelIndex &index) const { return mapToSource(index); } } diff --git a/libtaskmanager/taskfilterproxymodel.cpp b/libtaskmanager/taskfilterproxymodel.cpp index b2cd2b1d..c1017c0f 100644 --- a/libtaskmanager/taskfilterproxymodel.cpp +++ b/libtaskmanager/taskfilterproxymodel.cpp @@ -1,313 +1,309 @@ /******************************************************************** Copyright 2016 Eike Hein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "taskfilterproxymodel.h" #include "abstracttasksmodel.h" namespace TaskManager { class TaskFilterProxyModel::Private { public: Private(TaskFilterProxyModel *q); AbstractTasksModelIface *sourceTasksModel = nullptr; uint virtualDesktop = 0; QRect screenGeometry; QString activity; bool filterByVirtualDesktop = false; bool filterByScreen = false; bool filterByActivity = false; bool filterNotMinimized = false; bool filterSkipTaskbar = true; bool filterSkipPager = false; bool demandingAttentionSkipsFilters = true; - -private: - TaskFilterProxyModel *q; }; -TaskFilterProxyModel::Private::Private(TaskFilterProxyModel *q) - : q(q) +TaskFilterProxyModel::Private::Private(TaskFilterProxyModel *) { } TaskFilterProxyModel::TaskFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) , d(new Private(this)) { } TaskFilterProxyModel::~TaskFilterProxyModel() { } void TaskFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { d->sourceTasksModel = dynamic_cast(sourceModel); QSortFilterProxyModel::setSourceModel(sourceModel); } uint TaskFilterProxyModel::virtualDesktop() const { return d->virtualDesktop; } void TaskFilterProxyModel::setVirtualDesktop(uint virtualDesktop) { if (d->virtualDesktop != virtualDesktop) { d->virtualDesktop = virtualDesktop; if (d->filterByVirtualDesktop) { invalidateFilter(); } emit virtualDesktopChanged(); } } QRect TaskFilterProxyModel::screenGeometry() const { return d->screenGeometry; } void TaskFilterProxyModel::setScreenGeometry(const QRect &geometry) { if (d->screenGeometry != geometry) { d->screenGeometry = geometry; if (d->filterByScreen) { invalidateFilter(); } emit screenGeometryChanged(); } } QString TaskFilterProxyModel::activity() const { return d->activity; } void TaskFilterProxyModel::setActivity(const QString &activity) { if (d->activity != activity) { d->activity = activity; if (d->filterByActivity) { invalidateFilter(); } emit activityChanged(); } } bool TaskFilterProxyModel::filterByVirtualDesktop() const { return d->filterByVirtualDesktop; } void TaskFilterProxyModel::setFilterByVirtualDesktop(bool filter) { if (d->filterByVirtualDesktop != filter) { d->filterByVirtualDesktop = filter; invalidateFilter(); emit filterByVirtualDesktopChanged(); } } bool TaskFilterProxyModel::filterByScreen() const { return d->filterByScreen; } void TaskFilterProxyModel::setFilterByScreen(bool filter) { if (d->filterByScreen != filter) { d->filterByScreen = filter; invalidateFilter(); emit filterByScreenChanged(); } } bool TaskFilterProxyModel::filterByActivity() const { return d->filterByActivity; } void TaskFilterProxyModel::setFilterByActivity(bool filter) { if (d->filterByActivity != filter) { d->filterByActivity = filter; invalidateFilter(); emit filterByActivityChanged(); } } bool TaskFilterProxyModel::filterNotMinimized() const { return d->filterNotMinimized; } void TaskFilterProxyModel::setFilterNotMinimized(bool filter) { if (d->filterNotMinimized != filter) { d->filterNotMinimized = filter; invalidateFilter(); emit filterNotMinimizedChanged(); } } bool TaskFilterProxyModel::filterSkipTaskbar() const { return d->filterSkipTaskbar; } void TaskFilterProxyModel::setFilterSkipTaskbar(bool filter) { if (d->filterSkipTaskbar != filter) { d->filterSkipTaskbar = filter; invalidateFilter(); emit filterSkipTaskbarChanged(); } } bool TaskFilterProxyModel::filterSkipPager() const { return d->filterSkipPager; } void TaskFilterProxyModel::setFilterSkipPager(bool filter) { if (d->filterSkipPager != filter) { d->filterSkipPager = filter; invalidateFilter(); emit filterSkipPagerChanged(); } } bool TaskFilterProxyModel::demandingAttentionSkipsFilters() const { return d->demandingAttentionSkipsFilters; } void TaskFilterProxyModel::setDemandingAttentionSkipsFilters(bool skip) { if (d->demandingAttentionSkipsFilters != skip) { d->demandingAttentionSkipsFilters = skip; invalidateFilter(); emit demandingAttentionSkipsFiltersChanged(); } } QModelIndex TaskFilterProxyModel::mapIfaceToSource(const QModelIndex &index) const { return mapToSource(index); } bool TaskFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { Q_UNUSED(sourceParent) const QModelIndex &sourceIdx = sourceModel()->index(sourceRow, 0); // Filter tasks that are not to be shown on the task bar. if (d->filterSkipTaskbar && sourceIdx.data(AbstractTasksModel::SkipTaskbar).toBool()) { return false; } // Filter tasks that are not to be shown on the pager. if (d->filterSkipPager && sourceIdx.data(AbstractTasksModel::SkipPager).toBool()) { return false; } // Filter by virtual desktop. if (d->filterByVirtualDesktop && d->virtualDesktop != 0) { if (!sourceIdx.data(AbstractTasksModel::IsOnAllVirtualDesktops).toBool() && (!d->demandingAttentionSkipsFilters || !sourceIdx.data(AbstractTasksModel::IsDemandingAttention).toBool())) { const QVariant &virtualDesktop = sourceIdx.data(AbstractTasksModel::VirtualDesktop); if (!virtualDesktop.isNull()) { bool ok = false; const uint i = virtualDesktop.toUInt(&ok); if (ok && i != d->virtualDesktop) { return false; } } } } // Filter by screen. if (d->filterByScreen && d->screenGeometry.isValid()) { const QRect &screenGeometry = sourceIdx.data(AbstractTasksModel::ScreenGeometry).toRect(); if (screenGeometry.isValid() && screenGeometry != d->screenGeometry) { return false; } } // Filter by activity. if (d->filterByActivity && !d->activity.isEmpty()) { if (!d->demandingAttentionSkipsFilters || !sourceIdx.data(AbstractTasksModel::IsDemandingAttention).toBool()) { const QVariant &activities = sourceIdx.data(AbstractTasksModel::Activities); if (!activities.isNull()) { const QStringList l = activities.toStringList(); if (!l.isEmpty() && !l.contains(d->activity)) { return false; } } } } // Filter not minimized. if (d->filterNotMinimized) { bool isMinimized = sourceIdx.data(AbstractTasksModel::IsMinimized).toBool(); if (!isMinimized) { return false; } } return true; } } diff --git a/libtaskmanager/taskgroupingproxymodel.h b/libtaskmanager/taskgroupingproxymodel.h index 1c58d2af..c6d247f2 100644 --- a/libtaskmanager/taskgroupingproxymodel.h +++ b/libtaskmanager/taskgroupingproxymodel.h @@ -1,397 +1,397 @@ /******************************************************************** Copyright 2016 Eike Hein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef TASKGROUPINGPROXYMODEL_H #define TASKGROUPINGPROXYMODEL_H #include #include "abstracttasksmodeliface.h" #include "tasksmodel.h" #include "taskmanager_export.h" namespace TaskManager { /** * @short A proxy tasks model for grouping tasks, forming a tree. * * This proxy model groups tasks in its source tasks model, forming a tree * of tasks. Gouping behavior is influenced by various properties set on * the proxy model instance. * * @author Eike Hein **/ class TASKMANAGER_EXPORT TaskGroupingProxyModel : public QAbstractProxyModel, public AbstractTasksModelIface { Q_OBJECT Q_PROPERTY(TasksModel::GroupMode groupMode READ groupMode WRITE setGroupMode NOTIFY groupModeChanged) Q_PROPERTY(bool groupDemandingAttention READ groupDemandingAttention WRITE setGroupDemandingAttention NOTIFY groupDemandingAttentionChanged) Q_PROPERTY(int windowTasksThreshold READ windowTasksThreshold WRITE setWindowTasksThreshold NOTIFY windowTasksThresholdChanged) Q_PROPERTY(QStringList blacklistedAppIds READ blacklistedAppIds WRITE setBlacklistedAppIds NOTIFY blacklistedAppIdsChanged) Q_PROPERTY(QStringList blacklistedLauncherUrls READ blacklistedLauncherUrls WRITE setBlacklistedLauncherUrls NOTIFY blacklistedLauncherUrlsChanged) public: explicit TaskGroupingProxyModel(QObject *parent = 0); virtual ~TaskGroupingProxyModel(); QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &proxyIndex, int role) const override; void setSourceModel(QAbstractItemModel *sourceModel) override; /** * Returns the current group mode, i.e. the criteria by which tasks should * be grouped. * * Defaults to TasksModel::GroupApplication, which groups tasks backed by * the same application. * * If the group mode is TasksModel::GroupDisabled, no grouping is done. * * @see TasksModel * @see setGroupMode * @returns the active group mode. **/ TasksModel::GroupMode groupMode() const; /** * Sets the group mode, i.e. the criteria by which tasks should be grouped. * * The group mode can be set to TasksModel::GroupDisabled to disable grouping * entirely, breaking apart any existing groups. * * @see TasksModel * @see groupMode * @param mode A TasksModel group mode. **/ void setGroupMode(TasksModel::GroupMode mode); /** * Whether new tasks which demand attention * (AbstractTasksModel::IsDemandingAttention) should be grouped immediately, * or only once they have stopped demanding attention. Defaults to @c false. * * @see setGroupDemandingAttention * @returns whether tasks which demand attention are grouped immediately. **/ bool groupDemandingAttention() const; /** * Sets whether new tasks which demand attention * (AbstractTasksModel::IsDemandingAttention) should be grouped immediately, * or only once they have stopped demanding attention. * * @see groupDemandingAttention * @param group Whether tasks with demand attention should be grouped immediately. **/ void setGroupDemandingAttention(bool group); /** * As window tasks (AbstractTasksModel::IsWindow) come and go in the source * model, groups will be formed when this threshold value is exceeded, and * broken apart when it matches or falls below. * * Defaults to @c -1, which means grouping is done regardless of the number * of window tasks in the source model. * * @see setWindowTasksThreshold * @return the threshold number of source window tasks used in grouping * decisions. **/ int windowTasksThreshold() const; /** * Sets the number of source model window tasks (AbstractTasksModel::IsWindow) * above which groups will be formed, and at or below which groups will be broken * apart. * * If set to -1, grouping will be done regardless of the number of window tasks * in the source model. * * @see windowTasksThreshold * @param threshold A threshold number of source window tasks used in grouping * decisions. **/ void setWindowTasksThreshold(int threshold); /** * A blacklist of app ids (AbstractTasksModel::AppId) that is consulted before * grouping a task. If a task's app id is found on the blacklist, it is not * grouped. * * The default app id blacklist is empty. * * @see setBlacklistedAppIds * @returns the blacklist of app ids consulted before grouping a task. **/ QStringList blacklistedAppIds() const; /** * Sets the blacklist of app ids (AbstractTasksModel::AppId) that is consulted * before grouping a task. If a task's app id is found on the blacklist, it is * not grouped. * * When set, groups will be formed and broken apart as necessary. * * @see blacklistedAppIds * @param list a blacklist of app ids to be consulted before grouping a task. **/ void setBlacklistedAppIds(const QStringList &list); /** * A blacklist of launcher URLs (AbstractTasksModel::LauncherUrl) that is * consulted before grouping a task. If a task's launcher URL is found on the * blacklist, it is not grouped. * * The default launcher URL blacklist is empty. * * @see setBlacklistedLauncherUrls * @returns the blacklist of launcher URLs consulted before grouping a task. **/ QStringList blacklistedLauncherUrls() const; /** * Sets the blacklist of launcher URLs (AbstractTasksModel::LauncherUrl) that * is consulted before grouping a task. If a task's launcher URL is found on * the blacklist, it is not grouped. * * When set, groups will be formed and broken apart as necessary. * * @see blacklistedLauncherUrls * @param list a blacklist of launcher URLs to be consulted before grouping a task. **/ void setBlacklistedLauncherUrls(const QStringList &list); /** * Request activation of the task at the given index. Derived classes are * free to interpret the meaning of "activate" themselves depending on * the nature and state of the task, e.g. launch or raise a window task. * * @param index An index in this tasks model. **/ void requestActivate(const QModelIndex &index) override; /** * Request an additional instance of the application backing the task * at the given index. * * @param index An index in this tasks model. **/ void requestNewInstance(const QModelIndex &index) override; /** * Requests to open the given URLs with the application backing the task * at the given index. * * @param index An index in this tasks model. * @param urls The URLs to be passed to the application. **/ - virtual void requestOpenUrls(const QModelIndex &index, const QList &urls); + void requestOpenUrls(const QModelIndex &index, const QList &urls) override; /** * Request the task at the given index be closed. * * @param index An index in this tasks model. **/ void requestClose(const QModelIndex &index) override; /** * Request starting an interactive move for the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ void requestMove(const QModelIndex &index) override; /** * Request starting an interactive resize for the task at the given index. * * This is meant for tasks that have an associated window, and may be a * no-op when there is no window. * * @param index An index in this tasks model. **/ void requestResize(const QModelIndex &index) override; /** * Request toggling the minimized state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ void requestToggleMinimized(const QModelIndex &index) override; /** * Request toggling the maximized state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleMaximized(const QModelIndex &index) override; /** * Request toggling the keep-above state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleKeepAbove(const QModelIndex &index) override; /** * Request toggling the keep-below state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleKeepBelow(const QModelIndex &index) override; /** * Request toggling the fullscreen state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. **/ void requestToggleFullScreen(const QModelIndex &index) override; /** * Request toggling the shaded state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ void requestToggleShaded(const QModelIndex &index) override; /** * Request moving the task at the given index to the specified virtual * desktop. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. * @param desktop A virtual desktop number. **/ void requestVirtualDesktop(const QModelIndex &index, qint32 desktop) override; /** * Request moving the task at the given index to the specified activities. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. * @param activities The new list of activities. **/ virtual void requestActivities(const QModelIndex &index, const QStringList &activities) override; /** * Request informing the window manager of new geometry for a visual * delegate for the task at the given index. The geometry should be in * screen coordinates. * * If the task at the given index is a group parent, the geometry is * set for all of its children. If the task at the given index is a * group member, the geometry is set for all of its siblings. * * @param index An index in this tasks model. * @param geometry Visual delegate geometry in screen coordinates. * @param delegate The delegate. Implementations are on their own with * regard to extracting information from this, and should take care to * reject invalid objects. **/ void requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate = nullptr) override; /** * Request toggling whether the task at the given index, along with any * tasks matching its kind, should be grouped or not. Task groups will be * formed or broken apart as needed, along with affecting future grouping * decisions as new tasks appear in the source model. * * As grouping is toggled for a task, updates are made to the blacklisted* * properties of the model instance. * * @see blacklistedAppIds * @see blacklistedLauncherUrls * * @param index An index in this tasks model. **/ void requestToggleGrouping(const QModelIndex &index); Q_SIGNALS: void groupModeChanged() const; void groupDemandingAttentionChanged() const; void windowTasksThresholdChanged() const; void blacklistedAppIdsChanged() const; void blacklistedLauncherUrlsChanged() const; private: class Private; QScopedPointer d; Q_PRIVATE_SLOT(d, void sourceRowsAboutToBeInserted(const QModelIndex &parent, int first, int last)) Q_PRIVATE_SLOT(d, void sourceRowsInserted(const QModelIndex &parent, int start, int end)) Q_PRIVATE_SLOT(d, void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)) Q_PRIVATE_SLOT(d, void sourceRowsRemoved(const QModelIndex &parent, int start, int end)) Q_PRIVATE_SLOT(d, void sourceModelAboutToBeReset()) Q_PRIVATE_SLOT(d, void sourceModelReset()) Q_PRIVATE_SLOT(d, void sourceDataChanged(QModelIndex,QModelIndex,QVector)) }; } #endif diff --git a/libtaskmanager/tasksmodel.h b/libtaskmanager/tasksmodel.h index 1a7eb2ca..d70bafa9 100644 --- a/libtaskmanager/tasksmodel.h +++ b/libtaskmanager/tasksmodel.h @@ -1,811 +1,810 @@ /******************************************************************** Copyright 2016 Eike Hein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef TASKSMODEL_H #define TASKSMODEL_H #include #include "abstracttasksmodeliface.h" #include "taskmanager_export.h" namespace TaskManager { /** * @short A unified tasks model. * * This model presents tasks sourced from supplied launcher URLs, startup * notification data and window data retrieved from the windowing server * the host process is connected to. The underlying windowing system is * abstracted away. * * The source data is abstracted into a unified lifecycle for tasks * suitable for presentation in a user interface. * * Matching startup and window tasks replace launcher tasks. Startup * tasks are omitted when matching window tasks exist. Tasks that desire * not to be shown in a user interface are omitted. * * Tasks may be filtered, sorted or grouped by setting properties on the * model. * * Tasks may be interacted with by calling methods on the model. * * @author Eike Hein **/ class TASKMANAGER_EXPORT TasksModel : public QSortFilterProxyModel, public AbstractTasksModelIface { Q_OBJECT - Q_ENUMS(SortMode) - Q_ENUMS(GroupMode) - Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(int launcherCount READ launcherCount NOTIFY launcherCountChanged) Q_PROPERTY(QStringList launcherList READ launcherList WRITE setLauncherList NOTIFY launcherListChanged) Q_PROPERTY(bool anyTaskDemandsAttention READ anyTaskDemandsAttention NOTIFY anyTaskDemandsAttentionChanged) Q_PROPERTY(int virtualDesktop READ virtualDesktop WRITE setVirtualDesktop NOTIFY virtualDesktopChanged) Q_PROPERTY(QRect screenGeometry READ screenGeometry WRITE setScreenGeometry NOTIFY screenGeometryChanged) Q_PROPERTY(QString activity READ activity WRITE setActivity NOTIFY activityChanged) Q_PROPERTY(bool filterByVirtualDesktop READ filterByVirtualDesktop WRITE setFilterByVirtualDesktop NOTIFY filterByVirtualDesktopChanged) Q_PROPERTY(bool filterByScreen READ filterByScreen WRITE setFilterByScreen NOTIFY filterByScreenChanged) Q_PROPERTY(bool filterByActivity READ filterByActivity WRITE setFilterByActivity NOTIFY filterByActivityChanged) Q_PROPERTY(bool filterNotMinimized READ filterNotMinimized WRITE setFilterNotMinimized NOTIFY filterNotMinimizedChanged) Q_PROPERTY(SortMode sortMode READ sortMode WRITE setSortMode NOTIFY sortModeChanged) Q_PROPERTY(bool separateLaunchers READ separateLaunchers WRITE setSeparateLaunchers NOTIFY separateLaunchersChanged) Q_PROPERTY(bool launchInPlace READ launchInPlace WRITE setLaunchInPlace NOTIFY launchInPlaceChanged) Q_PROPERTY(GroupMode groupMode READ groupMode WRITE setGroupMode NOTIFY groupModeChanged) Q_PROPERTY(bool groupInline READ groupInline WRITE setGroupInline NOTIFY groupInlineChanged) Q_PROPERTY(int groupingWindowTasksThreshold READ groupingWindowTasksThreshold WRITE setGroupingWindowTasksThreshold NOTIFY groupingWindowTasksThresholdChanged) Q_PROPERTY(QStringList groupingAppIdBlacklist READ groupingAppIdBlacklist WRITE setGroupingAppIdBlacklist NOTIFY groupingAppIdBlacklistChanged) Q_PROPERTY(QStringList groupingLauncherUrlBlacklist READ groupingLauncherUrlBlacklist WRITE setGroupingLauncherUrlBlacklist NOTIFY groupingLauncherUrlBlacklistChanged) Q_PROPERTY(QModelIndex activeTask READ activeTask NOTIFY activeTaskChanged) public: enum SortMode { SortDisabled = 0, /**< No sorting is done. */ SortManual, /**< Tasks can be moved with move() and syncLaunchers(). */ SortAlpha, /**< Tasks are sorted alphabetically, by AbstractTasksModel::AppName and Qt::DisplayRole. */ SortVirtualDesktop, /**< Tasks are sorted by the virtual desktop they are on. */ SortActivity /**< Tasks are sorted by the number of tasks on the activities they're on. */ }; + Q_ENUM(SortMode) enum GroupMode { GroupDisabled = 0, /**< No grouping is done. */ GroupApplications /**< Tasks are grouped by the application backing them. */ }; + Q_ENUM(GroupMode) explicit TasksModel(QObject *parent = 0); virtual ~TasksModel(); QHash roleNames() const override; Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override; // Invokable. QVariant data(const QModelIndex &proxyIndex, int role) const override; /** * The number of launcher tasks in the tast list. * * @returns the number of launcher tasks in the task list. **/ int launcherCount() const; /** * The list of launcher URLs serialized to strings. * * @see setLauncherList * @returns the list of launcher URLs serialized to strings. **/ QStringList launcherList() const; /** * Replace the list of launcher URL strings. * * Invalid or empty URLs will be rejected. Duplicate URLs will be * collapsed. * * @see launcherList * @param launchers A list of launcher URL strings. **/ void setLauncherList(const QStringList &launchers); /** * Returns whether any task in the model currently demands attention * (AbstractTasksModel::IsDemandingAttention). * * @returns whether any task in the model currently demands attention. **/ bool anyTaskDemandsAttention() const; /** * The number of the virtual desktop used in filtering by virtual * desktop. Usually set to the number of the current virtual desktop. * Defaults to @c -1. * * @see setVirtualDesktop * @returns the number of the virtual desktop used in filtering. **/ int virtualDesktop() const; /** * Set the number of the virtual desktop to use in filtering by virtual * desktop. * * If set to @c -1, filtering by virtual desktop is disabled. * * @see virtualDesktop * @param virtualDesktop A virtual desktop number. **/ void setVirtualDesktop(int virtualDesktop); /** * The geometry of the screen used in filtering by screen. Defaults * to a null QRect. * * @see setGeometryScreen * @returns the geometry of the screen used in filtering. **/ QRect screenGeometry() const; /** * Set the geometry of the screen to use in filtering by screen. * * If set to an invalid QRect, filtering by screen is disabled. * * @see screenGeometry * @param geometry A screen geometry. **/ void setScreenGeometry(const QRect &geometry); /** * The id of the activity used in filtering by activity. Usually * set to the id of the current activity. Defaults to an empty id. * * @see setActivity * @returns the id of the activity used in filtering. **/ QString activity() const; /** * Set the id of the activity to use in filtering by activity. * * @see activity * @param activity An activity id. **/ void setActivity(const QString &activity); /** * Whether tasks should be filtered by virtual desktop. Defaults to * @c false. * * Filtering by virtual desktop only happens if a virtual desktop * number is set, even if this returns @c true. * * @see setFilterByVirtualDesktop * @see setVirtualDesktop * @returns @c true if tasks should be filtered by virtual desktop. **/ bool filterByVirtualDesktop() const; /** * Set whether tasks should be filtered by virtual desktop. * * Filtering by virtual desktop only happens if a virtual desktop * number is set, even if this is set to @c true. * * @see filterByVirtualDesktop * @see setVirtualDesktop * @param filter Whether tasks should be filtered by virtual desktop. **/ void setFilterByVirtualDesktop(bool filter); /** * Whether tasks should be filtered by screen. Defaults to @c false. * * Filtering by screen only happens if a screen number is set, even * if this returns @c true. * * @see setFilterByScreen * @see setScreen * @returns @c true if tasks should be filtered by screen. **/ bool filterByScreen() const; /** * Set whether tasks should be filtered by screen. * * Filtering by screen only happens if a screen number is set, even * if this is set to @c true. * * @see filterByScreen * @see setScreen * @param filter Whether tasks should be filtered by screen. **/ void setFilterByScreen(bool filter); /** * Whether tasks should be filtered by activity. Defaults to @c false. * * Filtering by activity only happens if an activity id is set, even * if this returns @c true. * * @see setFilterByActivity * @see setActivity * @returns @ctrue if tasks should be filtered by activity. **/ bool filterByActivity() const; /** * Set whether tasks should be filtered by activity. Defaults to * @c false. * * Filtering by virtual desktop only happens if an activity id is set, * even if this is set to @c true. * * @see filterByActivity * @see setActivity * @param filter Whether tasks should be filtered by activity. **/ void setFilterByActivity(bool filter); /** * Whether non-minimized tasks should be filtered. Defaults to * @c false. * * @see setFilterNotMinimized * @returns @c true if non-minimized tasks should be filtered. **/ bool filterNotMinimized() const; /** * Set whether non-minimized tasks should be filtered. * * @see filterNotMinimized * @param filter Whether non-minimized tasks should be filtered. **/ void setFilterNotMinimized(bool filter); /** * The sort mode used in sorting tasks. Defaults to SortAlpha. * * @see setSortMode * @returns the curent sort mode. **/ SortMode sortMode() const; /** * Sets the sort mode used in sorting tasks. * * @see sortMode * @param mode A sort mode. **/ void setSortMode(SortMode mode); /** * Whether launchers are kept separate from other kinds of tasks. * Defaults to @c true. * * When enabled, launcher tasks are sorted first in the tasks model * and move() disallows moving them below the last launcher task, * or moving a different kind of task above the first launcher. New * launcher tasks are inserted after the last launcher task. When * disabled, move() allows mixing, and new launcher tasks are * appended to the model. * * Further, when disabled, the model always behaves as if * launchInPlace is enabled: A window task takes the place of the * first matching launcher task. * * @see LauncherTasksModel * @see move * @see launchInPlace * @see setSeparateLaunchers * @return whether launcher tasks are kept separate. */ bool separateLaunchers() const; /** * Sets whether launchers are kept separate from other kinds of tasks. * * When enabled, launcher tasks are sorted first in the tasks model * and move() disallows moving them below the last launcher task, * or moving a different kind of task above the first launcher. New * launcher tasks are inserted after the last launcher task. When * disabled, move() allows mixing, and new launcher tasks are * appended to the model. * * Further, when disabled, the model always behaves as if * launchInPlace is enabled: A window task takes the place of the * first matching launcher task. * * @see LauncherTasksModel * @see move * @see launchInPlace * @see separateLaunchers * @param separate Whether to keep launcher tasks separate. */ void setSeparateLaunchers(bool separate); /** * Whether window tasks should be sorted as their associated launcher * tasks or separately. Defaults to @c false. * * @see setLaunchInPlace * @returns whether window tasks should be sorted as their associated * launcher tasks. **/ bool launchInPlace() const; /** * Sets whether window tasks should be sorted as their associated launcher * tasks or separately. * * @see launchInPlace * @param launchInPlace Whether window tasks should be sorted as their * associated launcher tasks. **/ void setLaunchInPlace(bool launchInPlace); /** * Returns the current group mode, i.e. the criteria by which tasks should * be grouped. * * Defaults to TasksModel::GroupApplication, which groups tasks backed by * the same application. * * If the group mode is TasksModel::GroupDisabled, no grouping is done. * * @see setGroupMode * @returns the current group mode. **/ TasksModel::GroupMode groupMode() const; /** * Sets the group mode, i.e. the criteria by which tasks should be grouped. * * The group mode can be set to TasksModel::GroupDisabled to disable grouping * entirely, breaking apart any existing groups. * * @see groupMode * @param mode A group mode. **/ void setGroupMode(TasksModel::GroupMode mode); /** * Returns whether grouping is done "inline" or not, i.e. whether groups * are maintained inside the flat, top-level list, or by forming a tree. * In inline grouping mode, move() on a group member will move all siblings * as well, and sorting is first done among groups, then group members. * * Further, in inline grouping mode, the groupingWindowTasksThreshold * setting is ignored: Grouping is always done. * * @see setGroupInline * @see move * @see groupingWindowTasksThreshold * @returns whether grouping is done inline or not. **/ bool groupInline() const; /** * Sets whether grouping is done "inline" or not, i.e. whether groups * are maintained inside the flat, top-level list, or by forming a tree. * In inline grouping mode, move() on a group member will move all siblings * as well, and sorting is first done among groups, then group members. * * @see groupInline * @see move * @see groupingWindowTasksThreshold * @param inline Whether to do grouping inline or not. **/ void setGroupInline(bool groupInline); /** * As window tasks (AbstractTasksModel::IsWindow) come and go, groups will * be formed when this threshold value is exceeded, and broken apart when * it matches or falls below. * * Defaults to @c -1, which means grouping is done regardless of the number * of window tasks. * * When the groupInline property is set to @c true, the threshold is ignored: * Grouping is always done. * * @see setGroupingWindowTasksThreshold * @see groupInline * @return the threshold number of window tasks used in grouping decisions. **/ int groupingWindowTasksThreshold() const; /** * Sets the number of window tasks (AbstractTasksModel::IsWindow) above which * groups will be formed, and at or below which groups will be broken apart. * * If set to -1, grouping will be done regardless of the number of window tasks * in the source model. * * When the groupInline property is set to @c true, the threshold is ignored: * Grouping is always done. * * @see groupingWindowTasksThreshold * @see groupInline * @param threshold A threshold number of window tasks used in grouping * decisions. **/ void setGroupingWindowTasksThreshold(int threshold); /** * A blacklist of app ids (AbstractTasksModel::AppId) that is consulted before * grouping a task. If a task's app id is found on the blacklist, it is not * grouped. * * The default app id blacklist is empty. * * @see setGroupingAppIdBlacklist * @returns the blacklist of app ids consulted before grouping a task. **/ QStringList groupingAppIdBlacklist() const; /** * Sets the blacklist of app ids (AbstractTasksModel::AppId) that is consulted * before grouping a task. If a task's app id is found on the blacklist, it is * not grouped. * * When set, groups will be formed and broken apart as necessary. * * @see groupingAppIdBlacklist * @param list a blacklist of app ids to be consulted before grouping a task. **/ void setGroupingAppIdBlacklist(const QStringList &list); /** * A blacklist of launcher URLs (AbstractTasksModel::LauncherUrl) that is * consulted before grouping a task. If a task's launcher URL is found on the * blacklist, it is not grouped. * * The default launcher URL blacklist is empty. * * @see setGroupingLauncherUrlBlacklist * @returns the blacklist of launcher URLs consulted before grouping a task. **/ QStringList groupingLauncherUrlBlacklist() const; /** * Sets the blacklist of launcher URLs (AbstractTasksModel::LauncherUrl) that * is consulted before grouping a task. If a task's launcher URL is found on * the blacklist, it is not grouped. * * When set, groups will be formed and broken apart as necessary. * * @see groupingLauncherUrlBlacklist * @param list a blacklist of launcher URLs to be consulted before grouping a task. **/ void setGroupingLauncherUrlBlacklist(const QStringList &list); /** * Finds the first active (AbstractTasksModel::IsActive) task in the model * and returns its QModelIndex, or a null QModelIndex if no active task is * found. * * @returns the model index for the first active task, if any. */ QModelIndex activeTask() const; /** * Request adding a launcher with the given URL. * * If this URL is already in the list, the request will fail. URLs are * compared for equality after removing the query string used to hold * metadata. * * @see launcherUrlsMatch * @param url A launcher URL. * @returns @c true if a launcher was added. */ Q_INVOKABLE bool requestAddLauncher(const QUrl &url); /** * Request removing the launcher with the given URL. * * If this URL is already in the list, the request will fail. URLs are * compared for equality after removing the query string used to hold * metadata. * * @see launcherUrlsMatch * @param url A launcher URL. * @returns @c true if the launcher was removed. */ Q_INVOKABLE bool requestRemoveLauncher(const QUrl &url); /** * Return the position of the launcher with the given URL. * * URLs are compared for equality after removing the query string used * to hold metadata. * * @see launcherUrlsMatch * @param url A launcher URL. * @returns @c -1 if no launcher exists for the given URL. */ Q_INVOKABLE int launcherPosition(const QUrl &url) const; /** * Request activation of the task at the given index. Derived classes are * free to interpret the meaning of "activate" themselves depending on * the nature and state of the task, e.g. launch or raise a window task. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestActivate(const QModelIndex &index) override; /** * Request an additional instance of the application backing the task * at the given index. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestNewInstance(const QModelIndex &index) override; /** * Requests to open the given URLs with the application backing the task * at the given index. * * @param index An index in this tasks model. * @param urls The URLs to be passed to the application. **/ - Q_INVOKABLE void requestOpenUrls(const QModelIndex &index, const QList &urls); + Q_INVOKABLE void requestOpenUrls(const QModelIndex &index, const QList &urls) override; /** * Request the task at the given index be closed. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestClose(const QModelIndex &index) override; /** * Request starting an interactive move for the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestMove(const QModelIndex &index) override; /** * Request starting an interactive resize for the task at the given index. * * This is meant for tasks that have an associated window, and may be a * no-op when there is no window. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestResize(const QModelIndex &index) override; /** * Request toggling the minimized state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestToggleMinimized(const QModelIndex &index) override; /** * Request toggling the maximized state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestToggleMaximized(const QModelIndex &index) override; /** * Request toggling the keep-above state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestToggleKeepAbove(const QModelIndex &index) override; /** * Request toggling the keep-below state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestToggleKeepBelow(const QModelIndex &index) override; /** * Request toggling the fullscreen state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestToggleFullScreen(const QModelIndex &index) override; /** * Request toggling the shaded state of the task at the given index. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestToggleShaded(const QModelIndex &index) override; /** * Request moving the task at the given index to the specified virtual * desktop. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * @param index An index in this tasks model. * @param desktop A virtual desktop number. **/ Q_INVOKABLE void requestVirtualDesktop(const QModelIndex &index, qint32 desktop) override; /** * Request moving the task at the given index to the specified activities. * * This is meant for tasks that have an associated window, and may be * a no-op when there is no window. * * This base implementation does nothing. * * @param index An index in this tasks model. * @param activities The new list of activities. **/ Q_INVOKABLE virtual void requestActivities(const QModelIndex &index, const QStringList &activities) override; /** * Request informing the window manager of new geometry for a visual * delegate for the task at the given index. The geometry should be in * screen coordinates. * * If the task at the given index is a group parent, the geometry is * set for all of its children. If the task at the given index is a * group member, the geometry is set for all of its siblings. * * @param index An index in this tasks model. * @param geometry Visual delegate geometry in screen coordinates. * @param delegate The delegate. Implementations are on their own with * regard to extracting information from this, and should take care to * reject invalid objects. **/ Q_INVOKABLE void requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate = nullptr) override; /** * Request toggling whether the task at the given index, along with any * tasks matching its kind, should be grouped or not. Task groups will be * formed or broken apart as needed, along with affecting future grouping * decisions as new tasks appear. * * As grouping is toggled for a task, updates are made to the * grouping*Blacklist properties of the model instance. * * @see groupingAppIdBlacklist * @see groupingLauncherUrlBlacklist * * @param index An index in this tasks model. **/ Q_INVOKABLE void requestToggleGrouping(const QModelIndex &index); /** * Moves a (top-level) task to a new position in the list. The insert * position is bounded to the list start and end. * * syncLaunchers() should be called after a set of move operations to * update the launcherList property to reflect the new order. * * When the groupInline property is set to @c true, a move request * for a group member will bring all siblings along. * * @see syncLaunchers * @see launcherList * @see setGroupInline * @param index An index in this tasks model. * @param newPos The new list position to move the task to. */ Q_INVOKABLE bool move(int row, int newPos); /** * Updates the launcher list to reflect the new order after calls to * move(), if needed. * * @see move * @see launcherList */ Q_INVOKABLE void syncLaunchers(); /** * Given a row in the model, returns a QModelIndex for it. To get an index * for a child in a task group, an optional child row may be passed as well. * * This easier to use from Qt Quick views than QAbstractItemModel::index is. * * @param row A row index in the model. * @param childRow A row index for a child of the task group at the given row. * @returns a model index for the task at the given row, or for one of its * child tasks. */ Q_INVOKABLE QModelIndex makeModelIndex(int row, int childRow = -1) const; Q_SIGNALS: void countChanged() const; void launcherCountChanged() const; void launcherListChanged() const; void anyTaskDemandsAttentionChanged() const; void virtualDesktopChanged() const; void screenGeometryChanged() const; void activityChanged() const; void filterByVirtualDesktopChanged() const; void filterByScreenChanged() const; void filterByActivityChanged() const; void filterNotMinimizedChanged() const; void sortModeChanged() const; void separateLaunchersChanged() const; void launchInPlaceChanged() const; void groupModeChanged() const; void groupInlineChanged() const; void groupingWindowTasksThresholdChanged() const; void groupingAppIdBlacklistChanged() const; void groupingLauncherUrlBlacklistChanged() const; void activeTaskChanged() const; protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; private: Q_INVOKABLE void updateLauncherCount(); class Private; class TasksModelLessThan; friend class TasksModelLessThan; QScopedPointer d; }; } #endif diff --git a/libtaskmanager/waylandtasksmodel.cpp b/libtaskmanager/waylandtasksmodel.cpp index 458a0c82..1f2dc2b7 100644 --- a/libtaskmanager/waylandtasksmodel.cpp +++ b/libtaskmanager/waylandtasksmodel.cpp @@ -1,544 +1,545 @@ /******************************************************************** Copyright 2016 Eike Hein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "waylandtasksmodel.h" #include "tasktools.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace TaskManager { class WaylandTasksModel::Private { public: Private(WaylandTasksModel *q); QList windows; QHash serviceCache; KWayland::Client::PlasmaWindowManagement *windowManagement = nullptr; void initWayland(); void addWindow(KWayland::Client::PlasmaWindow *window); void dataChanged(KWayland::Client::PlasmaWindow *window, int role); void dataChanged(KWayland::Client::PlasmaWindow *window, const QVector &roles); private: WaylandTasksModel *q; }; WaylandTasksModel::Private::Private(WaylandTasksModel *q) : q(q) { } void WaylandTasksModel::Private::initWayland() { if (!KWindowSystem::isPlatformWayland()) { return; } KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(q); if (!connection) { return; } KWayland::Client::Registry *registry = new KWayland::Client::Registry(q); registry->create(connection); QObject::connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, [this, registry] (quint32 name, quint32 version) { windowManagement = registry->createPlasmaWindowManagement(name, version, q); QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::interfaceAboutToBeReleased, q, [this] { q->beginResetModel(); windows.clear(); q->endResetModel(); } ); QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, q, [this](KWayland::Client::PlasmaWindow *window) { addWindow(window); } ); - for (auto it = windowManagement->windows().constBegin(); it != windowManagement->windows().constEnd(); ++it) { + const auto windows = windowManagement->windows(); + for (auto it = windows.constBegin(); it != windows.constEnd(); ++it) { addWindow(*it); } } ); registry->setup(); } void WaylandTasksModel::Private::addWindow(KWayland::Client::PlasmaWindow *window) { if (windows.indexOf(window) != -1) { return; } const int count = windows.count(); q->beginInsertRows(QModelIndex(), count, count); windows.append(window); KService::Ptr service = KService::serviceByStorageId(window->appId()); if (service) { serviceCache.insert(window, service); } q->endInsertRows(); auto removeWindow = [window, this] { const int row = windows.indexOf(window); if (row != -1) { q->beginRemoveRows(QModelIndex(), row, row); windows.removeAt(row); serviceCache.remove(window); q->endRemoveRows(); } }; QObject::connect(window, &KWayland::Client::PlasmaWindow::unmapped, q, removeWindow); QObject::connect(window, &QObject::destroyed, q, removeWindow); QObject::connect(window, &KWayland::Client::PlasmaWindow::titleChanged, q, [window, this] { dataChanged(window, Qt::DisplayRole); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::iconChanged, q, [window, this] { dataChanged(window, Qt::DecorationRole); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::appIdChanged, q, [window, this] { KService::Ptr service = KService::serviceByStorageId(window->appId()); if (service) { serviceCache.insert(window, service); } else { serviceCache.remove(window); } dataChanged(window, QVector{AppId, AppName, GenericName, LauncherUrl, LauncherUrlWithoutIcon}); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::activeChanged, q, [window, this] { this->dataChanged(window, IsActive); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::closeableChanged, q, [window, this] { this->dataChanged(window, IsClosable); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::movableChanged, q, [window, this] { this->dataChanged(window, IsMovable); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::resizableChanged, q, [window, this] { this->dataChanged(window, IsResizable); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::fullscreenableChanged, q, [window, this] { this->dataChanged(window, IsFullScreenable); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::fullscreenChanged, q, [window, this] { this->dataChanged(window, IsFullScreen); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::maximizeableChanged, q, [window, this] { this->dataChanged(window, IsMaximizable); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::maximizedChanged, q, [window, this] { this->dataChanged(window, IsMaximized); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::minimizeableChanged, q, [window, this] { this->dataChanged(window, IsMinimizable); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::minimizedChanged, q, [window, this] { this->dataChanged(window, IsMinimized); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::keepAboveChanged, q, [window, this] { this->dataChanged(window, IsKeepAbove); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::keepBelowChanged, q, [window, this] { this->dataChanged(window, IsKeepBelow); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::shadeableChanged, q, [window, this] { this->dataChanged(window, IsShadeable); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::virtualDesktopChangeableChanged, q, [window, this] { this->dataChanged(window, IsVirtualDesktopChangeable); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::virtualDesktopChanged, q, [window, this] { this->dataChanged(window, VirtualDesktop); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::onAllDesktopsChanged, q, [window, this] { this->dataChanged(window, IsOnAllVirtualDesktops); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::geometryChanged, q, [window, this] { this->dataChanged(window, QVector{Geometry, ScreenGeometry}); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::demandsAttentionChanged, q, [window, this] { this->dataChanged(window, IsDemandingAttention); } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::skipTaskbarChanged, q, [window, this] { this->dataChanged(window, SkipTaskbar); } ); } void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, int role) { QModelIndex idx = q->index(windows.indexOf(window)); emit q->dataChanged(idx, idx, QVector{role}); } void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, const QVector &roles) { QModelIndex idx = q->index(windows.indexOf(window)); emit q->dataChanged(idx, idx, roles); } WaylandTasksModel::WaylandTasksModel(QObject *parent) : AbstractTasksModel(parent) , d(new Private(this)) { d->initWayland(); } WaylandTasksModel::~WaylandTasksModel() = default; QVariant WaylandTasksModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= d->windows.count()) { return QVariant(); } KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); if (role == Qt::DisplayRole) { return window->title(); } else if (role == Qt::DecorationRole) { return window->icon(); } else if (role == AppId) { return window->appId(); } else if (role == AppName) { if (d->serviceCache.contains(window)) { return d->serviceCache.value(window)->name(); } else { return window->title(); } } else if (role == GenericName) { if (d->serviceCache.contains(window)) { return d->serviceCache.value(window)->genericName(); } } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) { if (d->serviceCache.contains(window)) { return QUrl::fromLocalFile(d->serviceCache.value(window)->entryPath()); } } else if (role == IsWindow) { return true; } else if (role == IsActive) { return window->isActive(); } else if (role == IsClosable) { return window->isCloseable(); } else if (role == IsMovable) { return window->isMovable(); } else if (role == IsResizable) { return window->isResizable(); } else if (role == IsMaximizable) { return window->isMaximizeable(); } else if (role == IsMaximized) { return window->isMaximized(); } else if (role == IsMinimizable) { return window->isMinimizeable(); } else if (role == IsMinimized) { return window->isMinimized(); } else if (role == IsKeepAbove) { return window->isKeepAbove(); } else if (role == IsKeepBelow) { return window->isKeepBelow(); } else if (role == IsFullScreenable) { return window->isFullscreenable(); } else if (role == IsFullScreen) { return window->isFullscreen(); } else if (role == IsShadeable) { return window->isShadeable(); } else if (role == IsShaded) { return window->isShaded(); } else if (role == IsVirtualDesktopChangeable) { return window->isVirtualDesktopChangeable(); } else if (role == VirtualDesktop) { return window->virtualDesktop(); } else if (role == IsOnAllVirtualDesktops) { return window->isOnAllDesktops(); } else if (role == Geometry) { return window->geometry(); } else if (role == ScreenGeometry) { return screenGeometry(window->geometry().center()); } else if (role == Activities) { // FIXME Implement. } else if (role == IsDemandingAttention) { return window->isDemandingAttention(); } else if (role == SkipTaskbar) { return window->skipTaskbar(); } else if (role == SkipPager) { // FIXME Implement. } return QVariant(); } int WaylandTasksModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : d->windows.count(); } QModelIndex WaylandTasksModel::index(int row, int column, const QModelIndex &parent) const { return hasIndex(row, column, parent) ? createIndex(row, column, d->windows.at(row)) : QModelIndex(); } void WaylandTasksModel::requestActivate(const QModelIndex &index) { // FIXME Lacks transient handling of the XWindows version. if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } d->windows.at(index.row())->requestActivate(); } void WaylandTasksModel::requestNewInstance(const QModelIndex &index) { if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } KWayland::Client::PlasmaWindow* window = d->windows.at(index.row()); if (d->serviceCache.contains(window)) { const KService::Ptr service = d->serviceCache.value(window); new KRun(QUrl::fromLocalFile(service->entryPath()), 0, false); KActivities::ResourceInstance::notifyAccessed(QUrl("applications:" + service->storageId()), "org.kde.libtaskmanager"); } } void WaylandTasksModel::requestOpenUrls(const QModelIndex &index, const QList &urls) { if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count() || urls.isEmpty()) { return; } KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); if (d->serviceCache.contains(window)) { const KService::Ptr service = d->serviceCache.value(window); KRun::runApplication(*service, urls, nullptr, 0); KActivities::ResourceInstance::notifyAccessed(QUrl("applications:" + service->storageId()), "org.kde.libtaskmanager"); } } void WaylandTasksModel::requestClose(const QModelIndex &index) { if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } d->windows.at(index.row())->requestClose(); } void WaylandTasksModel::requestMove(const QModelIndex &index) { // FIXME Move-to-desktop logic from XWindows version. (See also others.) if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } d->windows.at(index.row())->requestMove(); } void WaylandTasksModel::requestResize(const QModelIndex &index) { // FIXME Move-to-desktop logic from XWindows version. (See also others.) if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } d->windows.at(index.row())->requestResize(); } void WaylandTasksModel::requestToggleMinimized(const QModelIndex &index) { // FIXME Move-to-desktop logic from XWindows version. (See also others.) if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } d->windows.at(index.row())->requestToggleMinimized(); } void WaylandTasksModel::requestToggleMaximized(const QModelIndex &index) { // FIXME Move-to-desktop logic from XWindows version. (See also others.) if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } d->windows.at(index.row())->requestToggleMaximized(); } void WaylandTasksModel::requestToggleKeepAbove(const QModelIndex &index) { Q_UNUSED(index) // FIXME Implement. } void WaylandTasksModel::requestToggleKeepBelow(const QModelIndex &index) { Q_UNUSED(index) // FIXME Implement. } void WaylandTasksModel::requestToggleFullScreen(const QModelIndex &index) { Q_UNUSED(index) // FIXME Implement. } void WaylandTasksModel::requestToggleShaded(const QModelIndex &index) { if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } d->windows.at(index.row())->requestToggleShaded(); } void WaylandTasksModel::requestVirtualDesktop(const QModelIndex &index, qint32 desktop) { // FIXME Lacks add-new-desktop code from XWindows version. // FIXME Does this do the set-on-all-desktops stuff from the XWindows version? if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } d->windows.at(index.row())->requestVirtualDesktop(desktop); } void WaylandTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities) { Q_UNUSED(index) Q_UNUSED(activities) } void WaylandTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate) { /* FIXME: This introduces the dependency on Qt5::Quick. I might prefer reversing this and publishing the window pointer through the model, then calling PlasmaWindow::setMinimizeGeometry in the applet backend, rather than hand delegate items into the lib, keeping the lib more UI- agnostic. */ Q_UNUSED(geometry) if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } const QQuickItem *item = qobject_cast(delegate); if (!item || !item->parentItem() || !item->window()) { return; } QWindow *itemWindow = item->window(); if (!itemWindow) { return; } using namespace KWayland::Client; Surface *surface = Surface::fromWindow(itemWindow); if (!surface) { return; } QRect rect(item->x(), item->y(), item->width(), item->height()); rect.moveTopLeft(item->parentItem()->mapToScene(rect.topLeft()).toPoint()); KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); window->setMinimizedGeometry(surface, rect); } } diff --git a/plasma-windowed/main.cpp b/plasma-windowed/main.cpp index de3dff89..abc126e0 100644 --- a/plasma-windowed/main.cpp +++ b/plasma-windowed/main.cpp @@ -1,75 +1,74 @@ /* * Copyright 2014 Bhushan Shah * Copyright 2014 Marco Martin * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see */ #include #include #include #include #include #include "plasmawindowedview.h" #include "plasmawindowedcorona.h" static const char version[] = "1.0"; int main(int argc, char **argv) { QQuickWindow::setDefaultAlphaBuffer(true); QApplication app(argc, argv); app.setApplicationVersion(version); app.setOrganizationDomain(QStringLiteral("kde.org")); KDBusService service(KDBusService::Unique); QCommandLineParser parser; parser.setApplicationDescription(i18n("Plasma Windowed")); parser.addOption(QCommandLineOption(QStringLiteral("statusnotifier"), i18n("Makes the plasmoid stay alive in the Notification Area, even when the window is closed."))); parser.addPositionalArgument(QStringLiteral("applet"), i18n("The applet to open.")); parser.addPositionalArgument(QStringLiteral("args"), i18n("Arguments to pass to the plasmoid."), QStringLiteral("[args...]")); parser.addVersionOption(); parser.addHelpOption(); parser.process(app); if (parser.positionalArguments().isEmpty()) { parser.showHelp(1); } PlasmaWindowedCorona *corona = new PlasmaWindowedCorona(); + const QStringList arguments = parser.positionalArguments(); QVariantList args; - QStringList::const_iterator constIterator; - constIterator = parser.positionalArguments().constBegin(); - ++constIterator; - for (; constIterator != parser.positionalArguments().constEnd(); + QStringList::const_iterator constIterator = arguments.constBegin() + 1; + for (; constIterator != arguments.constEnd(); ++constIterator) { args << (*constIterator); } corona->setHasStatusNotifier(parser.isSet(QStringLiteral("statusnotifier"))); - corona->loadApplet(parser.positionalArguments().first(), args); + corona->loadApplet(arguments.first(), args); QObject::connect(&service, &KDBusService::activateRequested, corona, &PlasmaWindowedCorona::activateRequested); const int ret = app.exec(); delete corona; return ret; } diff --git a/runners/bookmarks/browsers/chrome.cpp b/runners/bookmarks/browsers/chrome.cpp index 9d5e6e94..7932ef8e 100644 --- a/runners/bookmarks/browsers/chrome.cpp +++ b/runners/bookmarks/browsers/chrome.cpp @@ -1,134 +1,134 @@ /* * Copyright 2007 Glenn Ergeerts * Copyright 2012 Glenn Ergeerts * * 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 "chrome.h" #include "faviconfromblob.h" #include "browsers/findprofile.h" #include #include #include #include #include "bookmarksrunner_defs.h" #include #include class ProfileBookmarks { public: ProfileBookmarks(Profile &profile) : m_profile(profile) {} inline QList bookmarks() { return m_bookmarks; } inline Profile profile() { return m_profile; } void tearDown() { m_profile.favicon()->teardown(); m_bookmarks.clear(); } void add(QVariantMap &bookmarkEntry) { m_bookmarks << bookmarkEntry; } void clear() { m_bookmarks.clear(); } private: Profile m_profile; QList m_bookmarks; }; Chrome::Chrome( FindProfile* findProfile, QObject* parent ) : QObject(parent), m_watcher(new KDirWatch(this)), m_dirty(false) { foreach(Profile profile, findProfile->find()) { m_profileBookmarks << new ProfileBookmarks(profile); m_watcher->addFile(profile.path()); } connect(m_watcher, &KDirWatch::created, [=] { m_dirty = true; }); } Chrome::~Chrome() { foreach(ProfileBookmarks *profileBookmark, m_profileBookmarks) { delete profileBookmark; } } QList Chrome::match(const QString &term, bool addEveryThing) { if (m_dirty) { prepare(); } QList results; foreach(ProfileBookmarks *profileBookmarks, m_profileBookmarks) { results << match(term, addEveryThing, profileBookmarks); } return results; } QList Chrome::match(const QString &term, bool addEveryThing, ProfileBookmarks *profileBookmarks) { QList results; - foreach(QVariantMap bookmark, profileBookmarks->bookmarks()) { + foreach(const QVariantMap &bookmark, profileBookmarks->bookmarks()) { QString url = bookmark.value(QStringLiteral("url")).toString(); BookmarkMatch bookmarkMatch(profileBookmarks->profile().favicon(), term, bookmark.value(QStringLiteral("name")).toString(), url); bookmarkMatch.addTo(results, addEveryThing); } return results; } void Chrome::prepare() { m_dirty = false; foreach(ProfileBookmarks *profileBookmarks, m_profileBookmarks) { Profile profile = profileBookmarks->profile(); profileBookmarks->clear(); QFile bookmarksFile(profile.path()); if (!bookmarksFile.open(QIODevice::ReadOnly | QIODevice::Text)) { continue; }; QJsonDocument jdoc = QJsonDocument::fromJson(bookmarksFile.readAll()); if (jdoc.isNull()) { continue; } - QVariantMap resultMap = jdoc.object().toVariantMap(); + const QVariantMap resultMap = jdoc.object().toVariantMap(); if (!resultMap.contains(QStringLiteral("roots"))) { return; } - QVariantMap entries = resultMap.value(QStringLiteral("roots")).toMap(); - foreach(QVariant folder, entries.values()) { + const QVariantMap entries = resultMap.value(QStringLiteral("roots")).toMap(); + foreach(const QVariant &folder, entries) { parseFolder(folder.toMap(), profileBookmarks); } profile.favicon()->prepare(); } } void Chrome::teardown() { foreach(ProfileBookmarks *profileBookmarks, m_profileBookmarks) { profileBookmarks->tearDown(); } } void Chrome::parseFolder(const QVariantMap &entry, ProfileBookmarks *profile) { QVariantList children = entry.value(QStringLiteral("children")).toList(); - foreach(QVariant child, children) { + foreach(const QVariant &child, children) { QVariantMap entry = child.toMap(); if(entry.value(QStringLiteral("type")).toString() == QLatin1String("folder")) parseFolder(entry, profile); else { profile->add(entry); } } } diff --git a/runners/bookmarks/browsers/chromefindprofile.cpp b/runners/bookmarks/browsers/chromefindprofile.cpp index 925083a9..994a1a19 100644 --- a/runners/bookmarks/browsers/chromefindprofile.cpp +++ b/runners/bookmarks/browsers/chromefindprofile.cpp @@ -1,68 +1,68 @@ /* * Copyright 2007 Glenn Ergeerts * Copyright 2012 Glenn Ergeerts * * 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 "chromefindprofile.h" #include #include #include #include #include #include "bookmarksrunner_defs.h" #include "faviconfromblob.h" #include FindChromeProfile::FindChromeProfile (const QString &applicationName, const QString &homeDirectory, QObject* parent ) : QObject(parent), m_applicationName(applicationName), m_homeDirectory(homeDirectory) { } QList FindChromeProfile::find() { QString configDirectory = QStringLiteral("%1/.config/%2") - .arg(m_homeDirectory).arg(m_applicationName); + .arg(m_homeDirectory, m_applicationName); QString localStateFileName = QStringLiteral("%1/Local State") .arg(configDirectory); QList profiles; QFile localStateFile(localStateFileName); if (!localStateFile.open(QIODevice::ReadOnly | QIODevice::Text)) { return profiles; } QJsonDocument jdoc = QJsonDocument::fromJson(localStateFile.readAll()); if(jdoc.isNull()) { qDebug() << "error opening " << QFileInfo(localStateFile).absoluteFilePath(); return profiles; } QVariantMap localState = jdoc.object().toVariantMap(); QVariantMap profilesConfig = localState.value(QStringLiteral("profile")).toMap().value(QStringLiteral("info_cache")).toMap(); - foreach(QString profile, profilesConfig.keys()) { - QString profilePath = QStringLiteral("%1/%2").arg(configDirectory).arg(profile); - QString profileBookmarksPath = QStringLiteral("%1/%2").arg(profilePath).arg(QStringLiteral("Bookmarks")); + foreach(const QString &profile, profilesConfig.keys()) { + const QString profilePath = QStringLiteral("%1/%2").arg(configDirectory, profile); + const QString profileBookmarksPath = QStringLiteral("%1/%2").arg(profilePath, QStringLiteral("Bookmarks")); profiles << Profile(profileBookmarksPath, FaviconFromBlob::chrome(profilePath, this)); } return profiles; } diff --git a/runners/bookmarks/faviconfromblob.cpp b/runners/bookmarks/faviconfromblob.cpp index 77512eb4..082a8b2e 100644 --- a/runners/bookmarks/faviconfromblob.cpp +++ b/runners/bookmarks/faviconfromblob.cpp @@ -1,148 +1,146 @@ /* * Copyright 2007 Glenn Ergeerts * Copyright 2012 Glenn Ergeerts * * 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 "faviconfromblob.h" #include #include #include #include #include #include #include #include "bookmarksrunner_defs.h" #include "fetchsqlite.h" #include #include #include #include #define dbFileName m_profileCacheDirectory + QDir::separator() + "Favicons.sqlite" class StaticQuery : public BuildQuery { public: StaticQuery(const QString &query) : m_query(query) {} QString query(QSqlDatabase *database) const override { Q_UNUSED(database); return m_query; } private: const QString m_query; }; class ChromeQuery : public BuildQuery { public: ChromeQuery() {} QString query(QSqlDatabase *database) const override { //qDebug() << "tables: " << database->tables(); if(database->tables().contains(QStringLiteral("favicon_bitmaps"))) return QStringLiteral("SELECT * FROM favicons " \ "inner join icon_mapping on icon_mapping.icon_id = favicons.id " \ "inner join favicon_bitmaps on icon_mapping.icon_id = favicon_bitmaps.icon_id " \ "WHERE page_url = :url ORDER BY height desc LIMIT 1;"); return QStringLiteral("SELECT * FROM favicons inner join icon_mapping " \ "on icon_mapping.icon_id = favicons.id " \ "WHERE page_url = :url LIMIT 1;"); } }; FaviconFromBlob *FaviconFromBlob::chrome(const QString &profileDirectory, QObject *parent) { QString profileName = QFileInfo(profileDirectory).fileName(); QString faviconCache = QStringLiteral("%1/KRunner-Chrome-Favicons-%2.sqlite") - .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) - .arg(profileName); + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), profileName); FetchSqlite *fetchSqlite = new FetchSqlite(profileDirectory + "/Favicons", faviconCache, parent); return new FaviconFromBlob(profileName, new ChromeQuery(), QStringLiteral("image_data"), fetchSqlite, parent); } FaviconFromBlob *FaviconFromBlob::firefox(FetchSqlite *fetchSqlite, QObject *parent) { QString faviconQuery = QStringLiteral("SELECT moz_favicons.data FROM moz_favicons" \ " inner join moz_places ON moz_places.favicon_id = moz_favicons.id" \ " WHERE moz_places.url = :url LIMIT 1;"); return new FaviconFromBlob(QStringLiteral("firefox-default"), new StaticQuery(faviconQuery), QStringLiteral("data"), fetchSqlite, parent); } FaviconFromBlob::FaviconFromBlob(const QString &profileName, BuildQuery *buildQuery, const QString &blobColumn, FetchSqlite *fetchSqlite, QObject *parent) : Favicon(parent), m_buildQuery(buildQuery), m_blobcolumn(blobColumn), m_fetchsqlite(fetchSqlite) { m_profileCacheDirectory = QStringLiteral("%1/KRunner-Favicons-%2") - .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) - .arg(profileName); + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), profileName); //qDebug() << "got cache directory: " << m_profileCacheDirectory; cleanCacheDirectory(); QDir().mkpath(m_profileCacheDirectory); } FaviconFromBlob::~FaviconFromBlob() { cleanCacheDirectory(); delete m_buildQuery; } void FaviconFromBlob::prepare() { m_fetchsqlite->prepare(); } void FaviconFromBlob::teardown() { m_fetchsqlite->teardown(); } void FaviconFromBlob::cleanCacheDirectory() { - foreach(QFileInfo file, QDir(m_profileCacheDirectory).entryInfoList(QDir::NoDotAndDotDot)) { + foreach(const QFileInfo &file, QDir(m_profileCacheDirectory).entryInfoList(QDir::NoDotAndDotDot)) { //qDebug() << "Removing file " << file.absoluteFilePath() << ": " << - QFile(file.absoluteFilePath()).remove(); + QFile(file.absoluteFilePath()).remove(); } QDir().rmdir(m_profileCacheDirectory); } QIcon FaviconFromBlob::iconFor(const QString &url) { //qDebug() << "got url: " << url; QString fileChecksum = QString::number(qChecksum(url.toLatin1(), url.toLatin1().size())); QFile iconFile( m_profileCacheDirectory + QDir::separator() + fileChecksum + "_favicon" ); if(iconFile.size() == 0) iconFile.remove(); if(!iconFile.exists()) { QMap bindVariables; bindVariables.insert(QStringLiteral("url"), url); QList faviconFound = m_fetchsqlite->query(m_buildQuery, bindVariables); if(faviconFound.isEmpty()) return defaultIcon(); QByteArray iconData = faviconFound.first().value(m_blobcolumn).toByteArray(); //qDebug() << "Favicon found: " << iconData.size() << " bytes"; if(iconData.size() <=0) return defaultIcon(); iconFile.open(QFile::WriteOnly); iconFile.write(iconData); iconFile.close(); } return QIcon(iconFile.fileName()); } diff --git a/runners/kill/killrunner.cpp b/runners/kill/killrunner.cpp index a7a7a01c..5c2e8529 100644 --- a/runners/kill/killrunner.cpp +++ b/runners/kill/killrunner.cpp @@ -1,225 +1,225 @@ /* Copyright 2009 Jan Gerrit Marker * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "killrunner.h" #include #include #include #include #include #include #include "processcore/processes.h" #include "processcore/process.h" #include "killrunner_config.h" K_EXPORT_PLASMA_RUNNER(kill, KillRunner) KillRunner::KillRunner(QObject *parent, const QVariantList& args) : Plasma::AbstractRunner(parent, args), m_processes(0) { Q_UNUSED(args); setObjectName( QLatin1String("Kill Runner") ); reloadConfiguration(); connect(this, &Plasma::AbstractRunner::prepare, this, &KillRunner::prep); connect(this, &Plasma::AbstractRunner::teardown, this, &KillRunner::cleanup); m_delayedCleanupTimer.setInterval(50); m_delayedCleanupTimer.setSingleShot(true); connect(&m_delayedCleanupTimer, &QTimer::timeout, this, &KillRunner::cleanup); } KillRunner::~KillRunner() { } void KillRunner::reloadConfiguration() { KConfigGroup grp = config(); m_triggerWord.clear(); if (grp.readEntry(CONFIG_USE_TRIGGERWORD, true)) { m_triggerWord = grp.readEntry(CONFIG_TRIGGERWORD, i18n("kill")) + ' '; } m_sorting = (KillRunnerConfig::Sort) grp.readEntry(CONFIG_SORTING, 0); QList syntaxes; syntaxes << Plasma::RunnerSyntax(m_triggerWord + ":q:", i18n("Terminate running applications whose names match the query.")); setSyntaxes(syntaxes); } void KillRunner::prep() { m_delayedCleanupTimer.stop(); } void KillRunner::cleanup() { if (!m_processes) { return; } if (m_prepLock.tryLockForWrite()) { delete m_processes; m_processes = 0; m_prepLock.unlock(); } else { m_delayedCleanupTimer.stop(); } } void KillRunner::match(Plasma::RunnerContext &context) { QString term = context.query(); const bool hasTrigger = !m_triggerWord.isEmpty(); if (hasTrigger && !term.startsWith(m_triggerWord, Qt::CaseInsensitive)) { return; } m_prepLock.lockForRead(); if (!m_processes) { m_prepLock.unlock(); m_prepLock.lockForWrite(); if (!m_processes) { suspendMatching(true); m_processes = new KSysGuard::Processes(); m_processes->updateAllProcesses(); suspendMatching(false); } } m_prepLock.unlock(); term = term.right(term.length() - m_triggerWord.length()); if (term.length() < 2) { return; } QList matches; const QList processlist = m_processes->getAllProcesses(); foreach (const KSysGuard::Process *process, processlist) { if (!context.isValid()) { return; } const QString name = process->name(); if (!name.contains(term, Qt::CaseInsensitive)) { //Process doesn't match the search term continue; } const quint64 pid = process->pid(); const qlonglong uid = process->uid(); const QString user = getUserName(uid); QVariantList data; data << pid << user; Plasma::QueryMatch match(this); match.setText(i18n("Terminate %1", name)); match.setSubtext(i18n("Process ID: %1\nRunning as user: %2", QString::number(pid), user)); match.setIconName(QStringLiteral("application-exit")); match.setData(data); match.setId(name); // Set the relevance switch (m_sorting) { case KillRunnerConfig::CPU: match.setRelevance((process->userUsage() + process->sysUsage()) / 100); break; case KillRunnerConfig::CPUI: match.setRelevance(1 - (process->userUsage() + process->sysUsage()) / 100); break; case KillRunnerConfig::NONE: match.setRelevance(name.compare(term, Qt::CaseInsensitive) == 0 ? 1 : 9); break; } matches << match; } qDebug() << "match count is" << matches.count(); context.addMatches(matches); } void KillRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) { Q_UNUSED(context) QVariantList data = match.data().value(); quint64 pid = data[0].toUInt(); - QString user = data[1].toString(); +// QString user = data[1].toString(); int signal; if (match.selectedAction() != NULL) { signal = match.selectedAction()->data().toInt(); } else { signal = 9; //default: SIGKILL } QStringList args; args << QStringLiteral("-%1").arg(signal) << QStringLiteral("%1").arg(pid); KProcess *process = new KProcess(this); int returnCode = process->execute(QStringLiteral("kill"), args); if (returnCode == 0) { return; } KAuth::Action killAction = QStringLiteral("org.kde.ksysguard.processlisthelper.sendsignal"); killAction.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper")); killAction.addArgument(QStringLiteral("pid0"), pid); killAction.addArgument(QStringLiteral("pidcount"), 1); killAction.addArgument(QStringLiteral("signal"), signal); killAction.execute(); } QList KillRunner::actionsForMatch(const Plasma::QueryMatch &match) { Q_UNUSED(match) QList ret; if (!action(QStringLiteral("SIGTERM"))) { (addAction(QStringLiteral("SIGTERM"), QIcon::fromTheme(QStringLiteral("application-exit")), i18n("Send SIGTERM")))->setData(15); (addAction(QStringLiteral("SIGKILL"), QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Send SIGKILL")))->setData(9); } ret << action(QStringLiteral("SIGTERM")) << action(QStringLiteral("SIGKILL")); return ret; } QString KillRunner::getUserName(qlonglong uid) { KUser user(uid); if (user.isValid()) { return user.loginName(); } qDebug() << QStringLiteral("No user with UID %1 was found").arg(uid); return QStringLiteral("root");//No user with UID uid was found, so root is used } #include "killrunner.moc" diff --git a/runners/shell/shellrunner.h b/runners/shell/shellrunner.h index 29d5d3cb..0529e72f 100644 --- a/runners/shell/shellrunner.h +++ b/runners/shell/shellrunner.h @@ -1,45 +1,45 @@ /* * Copyright (C) 2006 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 as * published by the Free Software Foundation * * 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 SHELLRUNNER_H #define SHELLRUNNER_H #include /** * This class runs programs using the literal name of the binary, much as one * would use at a shell prompt. */ class ShellRunner : public Plasma::AbstractRunner { Q_OBJECT public: ShellRunner(QObject *parent, const QVariantList &args); ~ShellRunner() override; void match(Plasma::RunnerContext &context) override; void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &action) override; - QList actionsForMatch(const Plasma::QueryMatch &match); + QList actionsForMatch(const Plasma::QueryMatch &match) override; private: bool m_enabled; }; #endif diff --git a/runners/webshortcuts/webshortcutrunner.cpp b/runners/webshortcuts/webshortcutrunner.cpp index 1ad6677d..245f61f7 100644 --- a/runners/webshortcuts/webshortcutrunner.cpp +++ b/runners/webshortcuts/webshortcutrunner.cpp @@ -1,163 +1,163 @@ /* * Copyright (C) 2007 Teemu Rytilahti * * 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 as * published by the Free Software Foundation * * 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 "webshortcutrunner.h" #include +#include +#include #include #include #include -#include - WebshortcutRunner::WebshortcutRunner(QObject *parent, const QVariantList& args) : Plasma::AbstractRunner(parent, args), m_match(this), m_filterBeforeRun(false) { Q_UNUSED(args); setObjectName( QLatin1String("Web Shortcut" )); setIgnoredTypes(Plasma::RunnerContext::Directory | Plasma::RunnerContext::File | Plasma::RunnerContext::Executable); m_match.setType(Plasma::QueryMatch::ExactMatch); m_match.setRelevance(0.9); // Listen for KUriFilter plugin config changes and update state... QDBusConnection sessionDbus = QDBusConnection::sessionBus(); sessionDbus.connect(QString(), QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure"), this, SLOT(readFiltersConfig())); connect(this, &WebshortcutRunner::teardown, this, &WebshortcutRunner::resetState); readFiltersConfig(); } WebshortcutRunner::~WebshortcutRunner() { } void WebshortcutRunner::readFiltersConfig() { // Make sure that the searchEngines cache, etc. is refreshed when the config file is changed. loadSyntaxes(); } void WebshortcutRunner::loadSyntaxes() { KUriFilterData filterData (QLatin1String(":q")); filterData.setSearchFilteringOptions(KUriFilterData::RetrieveAvailableSearchProvidersOnly); if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) { m_delimiter = filterData.searchTermSeparator(); } //qDebug() << "keyword delimiter:" << m_delimiter; //qDebug() << "search providers:" << filterData.preferredSearchProviders(); QList syns; Q_FOREACH (const QString &provider, filterData.preferredSearchProviders()) { //qDebug() << "checking out" << provider; Plasma::RunnerSyntax s(filterData.queryForPreferredSearchProvider(provider), /*":q:",*/ i18n("Opens \"%1\" in a web browser with the query :q:.", provider)); syns << s; } setSyntaxes(syns); } void WebshortcutRunner::resetState() { qDebug(); m_lastFailedKey.clear(); m_lastProvider.clear(); m_lastKey.clear(); } void WebshortcutRunner::match(Plasma::RunnerContext &context) { const QString term = context.query(); if (term.length() < 3 || !term.contains(m_delimiter)) return; // qDebug() << "checking term" << term; const int delimIndex = term.indexOf(m_delimiter); if (delimIndex == term.length() - 1) return; const QString key = term.left(delimIndex); if (key == m_lastFailedKey) { return; // we already know it's going to suck ;) } if (!context.isValid()) { qDebug() << "invalid context"; return; } // Do a fake user feedback text update if the keyword has not changed. // There is no point filtering the request on every key stroke. // filtering if (m_lastKey == key) { m_filterBeforeRun = true; m_match.setText(i18n("Search %1 for %2", m_lastProvider, term.mid(delimIndex + 1))); context.addMatch(m_match); return; } KUriFilterData filterData(term); if (!KUriFilter::self()->filterSearchUri(filterData, KUriFilter::WebShortcutFilter)) { m_lastFailedKey = key; return; } m_lastFailedKey.clear(); m_lastKey = key; m_lastProvider = filterData.searchProvider(); - m_match.setData(filterData.uri().url()); + m_match.setData(filterData.uri()); m_match.setId("WebShortcut:" + key); m_match.setIconName(filterData.iconName()); m_match.setText(i18n("Search %1 for %2", m_lastProvider, filterData.searchTerm())); context.addMatch(m_match); } void WebshortcutRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) { - QString location; + QUrl location; //qDebug() << "filter before run?" << m_filterBeforeRun; if (m_filterBeforeRun) { m_filterBeforeRun = false; //qDebug() << "look up webshortcut:" << context.query(); KUriFilterData filterData (context.query()); if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::WebShortcutFilter)) - location = filterData.uri().url(); + location = filterData.uri(); } else { - location = match.data().toString(); + location = match.data().toUrl(); } //qDebug() << location; if (!location.isEmpty()) { - KToolInvocation::invokeBrowser(location); + QDesktopServices::openUrl(location); } } K_EXPORT_PLASMA_RUNNER(webshortcuts, WebshortcutRunner) #include "webshortcutrunner.moc" diff --git a/shell/alternativeshelper.cpp b/shell/alternativeshelper.cpp index a96b2664..d0f5dfd8 100644 --- a/shell/alternativeshelper.cpp +++ b/shell/alternativeshelper.cpp @@ -1,90 +1,90 @@ /* * 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 "alternativeshelper.h" #include #include #include #include AlternativesHelper::AlternativesHelper(Plasma::Applet *applet, QObject *parent) : QObject(parent), m_applet(applet) { } AlternativesHelper::~AlternativesHelper() { } QStringList AlternativesHelper::appletProvides() const { - return m_applet->pluginInfo().property(QStringLiteral("X-Plasma-Provides")).value(); + return m_applet->pluginInfo().property(QStringLiteral("X-Plasma-Provides")).toStringList(); } QString AlternativesHelper::currentPlugin() const { return m_applet->pluginInfo().pluginName(); } QQuickItem *AlternativesHelper::applet() const { return m_applet->property("_plasma_graphicObject").value(); } void AlternativesHelper::loadAlternative(const QString &plugin) { if (plugin == m_applet->pluginInfo().pluginName() || m_applet->isContainment()) { return; } Plasma::Containment *cont = m_applet->containment(); if (!cont) { return; } QQuickItem *appletItem = m_applet->property("_plasma_graphicObject").value(); QQuickItem *contItem = cont->property("_plasma_graphicObject").value(); if (!appletItem || !contItem) { return; } // ensure the global shortcut is moved to the new applet const QKeySequence &shortcut = m_applet->globalShortcut(); m_applet->setGlobalShortcut(QKeySequence()); // need to unmap the old one first const QPoint newPos = appletItem->mapToItem(contItem, QPointF(0,0)).toPoint(); m_applet->destroy(); connect(m_applet, &QObject::destroyed, [=]() { Plasma::Applet *newApplet = Q_NULLPTR; QMetaObject::invokeMethod(contItem, "createApplet", Q_RETURN_ARG(Plasma::Applet *, newApplet), Q_ARG(QString, plugin), Q_ARG(QVariantList, QVariantList()), Q_ARG(QPoint, newPos)); if (newApplet) { newApplet->setGlobalShortcut(shortcut); } }); } #include "moc_alternativeshelper.cpp" diff --git a/shell/containmentconfigview.cpp b/shell/containmentconfigview.cpp index 3ae29859..cec067e4 100644 --- a/shell/containmentconfigview.cpp +++ b/shell/containmentconfigview.cpp @@ -1,246 +1,246 @@ /* * 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 "currentcontainmentactionsmodel.h" #include "containmentconfigview.h" #include "plasmaquick/configmodel.h" #include "shellcorona.h" #include "config-workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //////////////////////////////ContainmentConfigView ContainmentConfigView::ContainmentConfigView(Plasma::Containment *cont, QWindow *parent) : ConfigView(cont, parent), m_containment(cont), m_wallpaperConfigModel(0), m_containmentActionConfigModel(0), m_containmentPluginsConfigModel(0), m_currentContainmentActionsModel(0), m_currentWallpaperConfig(0), m_ownWallpaperConfig(0) { qmlRegisterType(); rootContext()->setContextProperty(QStringLiteral("configDialog"), this); setCurrentWallpaper(cont->containment()->wallpaper()); KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Wallpaper")); pkg.setPath(m_containment->wallpaper()); KConfigGroup cfg = m_containment->config(); cfg = KConfigGroup(&cfg, "Wallpaper"); syncWallpaperObjects(); } ContainmentConfigView::~ContainmentConfigView() { } void ContainmentConfigView::init() { setSource(QUrl::fromLocalFile(m_containment->corona()->kPackage().filePath("containmentconfigurationui"))); } PlasmaQuick::ConfigModel *ContainmentConfigView::containmentActionConfigModel() { if (!m_containmentActionConfigModel) { m_containmentActionConfigModel = new PlasmaQuick::ConfigModel(this); KPluginInfo::List actions = Plasma::PluginLoader::self()->listContainmentActionsInfo(QString()); KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Generic")); foreach (const KPluginInfo &info, actions) { pkg.setDefaultPackageRoot(QStandardPaths::locate(QStandardPaths::GenericDataLocation, PLASMA_RELATIVE_DATA_INSTALL_DIR "/containmentactions", QStandardPaths::LocateDirectory)); m_containmentActionConfigModel->appendCategory(info.icon(), info.name(), pkg.filePath("ui", QStringLiteral("config.qml")), info.pluginName()); } } return m_containmentActionConfigModel; } QAbstractItemModel *ContainmentConfigView::currentContainmentActionsModel() { if (!m_currentContainmentActionsModel) { m_currentContainmentActionsModel = new CurrentContainmentActionsModel(m_containment, this); } return m_currentContainmentActionsModel; } QString ContainmentConfigView::containmentPlugin() const { return m_containment->pluginInfo().pluginName(); } void ContainmentConfigView::setContainmentPlugin(const QString &plugin) { if (plugin.isEmpty() || containmentPlugin() == plugin) { return; } m_containment = static_cast(m_containment->corona())->setContainmentTypeForScreen(m_containment->screen(), plugin); emit containmentPluginChanged(); } PlasmaQuick::ConfigModel *ContainmentConfigView::wallpaperConfigModel() { if (!m_wallpaperConfigModel) { m_wallpaperConfigModel = new PlasmaQuick::ConfigModel(this); QStringList dirs(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, PLASMA_RELATIVE_DATA_INSTALL_DIR "/wallpapers", QStandardPaths::LocateDirectory)); KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/Generic")); QStringList platform = KDeclarative::KDeclarative::runtimePlatform(); if (!platform.isEmpty()) { QMutableStringListIterator it(platform); while (it.hasNext()) { it.next(); it.setValue("platformcontents/" + it.value()); } platform.append(QStringLiteral("contents")); pkg.setContentsPrefixPaths(platform); } pkg.addFileDefinition("mainscript", QStringLiteral("ui/main.qml"), i18n("Main Script File")); foreach (const QString &dirPath, dirs) { QDir dir(dirPath); pkg.setDefaultPackageRoot(dirPath); QStringList packages; foreach (const QString &sdir, dir.entryList(QDir::AllDirs | QDir::Readable)) { QString metadata = dirPath + '/' + sdir + "/metadata.desktop"; if (QFile::exists(metadata)) { packages << sdir; } } foreach (const QString &package, packages) { pkg.setPath(package); if (!pkg.isValid()) { continue; } m_wallpaperConfigModel->appendCategory(pkg.metadata().iconName(), pkg.metadata().name(), pkg.filePath("ui", QStringLiteral("config.qml")), package); } } } return m_wallpaperConfigModel; } PlasmaQuick::ConfigModel *ContainmentConfigView::containmentPluginsConfigModel() { if (!m_containmentPluginsConfigModel) { m_containmentPluginsConfigModel = new PlasmaQuick::ConfigModel(this); KPluginInfo::List actions = Plasma::PluginLoader::self()->listContainmentsOfType(QStringLiteral("Desktop")); foreach (const KPluginInfo &info, actions) { m_containmentPluginsConfigModel->appendCategory(info.icon(), info.name(), QString(), info.pluginName()); } } return m_containmentPluginsConfigModel; } KDeclarative::ConfigPropertyMap *ContainmentConfigView::wallpaperConfiguration() const { return m_currentWallpaperConfig; } QString ContainmentConfigView::currentWallpaper() const { return m_currentWallpaper; } void ContainmentConfigView::setCurrentWallpaper(const QString &wallpaper) { if (m_currentWallpaper == wallpaper) { return; } delete m_ownWallpaperConfig; m_ownWallpaperConfig = 0; if (m_containment->wallpaper() == wallpaper) { syncWallpaperObjects(); } else { //we have to construct an independent ConfigPropertyMap when we want to configure wallpapers that are not the current one KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Generic")); pkg.setDefaultPackageRoot(PLASMA_RELATIVE_DATA_INSTALL_DIR "/wallpapers"); pkg.setPath(wallpaper); QFile file(pkg.filePath("config", QStringLiteral("main.xml"))); KConfigGroup cfg = m_containment->config(); cfg = KConfigGroup(&cfg, "Wallpaper"); m_currentWallpaperConfig = m_ownWallpaperConfig = new KDeclarative::ConfigPropertyMap(new KConfigLoader(cfg, &file), this); } m_currentWallpaper = wallpaper; emit currentWallpaperChanged(); emit wallpaperConfigurationChanged(); } void ContainmentConfigView::applyWallpaper() { m_containment->setWallpaper(m_currentWallpaper); syncWallpaperObjects(); if (m_currentWallpaperConfig && m_ownWallpaperConfig) { - for (auto key : m_ownWallpaperConfig->keys()) { + for (const auto &key : m_ownWallpaperConfig->keys()) { m_currentWallpaperConfig->insert(key, m_ownWallpaperConfig->value(key)); } } delete m_ownWallpaperConfig; m_ownWallpaperConfig = 0; emit wallpaperConfigurationChanged(); } void ContainmentConfigView::syncWallpaperObjects() { QObject *wallpaperGraphicsObject = m_containment->property("wallpaperGraphicsObject").value(); if (!wallpaperGraphicsObject) { return; } rootContext()->setContextProperty(QStringLiteral("wallpaper"), wallpaperGraphicsObject); //FIXME: why m_wallpaperGraphicsObject->property("configuration").value() doesn't work? m_currentWallpaperConfig = static_cast(wallpaperGraphicsObject->property("configuration").value()); } #include "private/moc_containmentconfigview.cpp" diff --git a/shell/desktopview.h b/shell/desktopview.h index f72add18..d0696df6 100644 --- a/shell/desktopview.h +++ b/shell/desktopview.h @@ -1,104 +1,104 @@ /* * Copyright 2013 Marco Martin * * 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. */ #ifndef DESKTOVIEW_H #define DESKTOVIEW_H #include "plasmaquick/containmentview.h" #include "panelconfigview.h" #include namespace KWayland { namespace Client { class PlasmaShellSurface; } } class DesktopView : public PlasmaQuick::ContainmentView { Q_OBJECT Q_PROPERTY(WindowType windowType READ windowType WRITE setWindowType NOTIFY windowTypeChanged) //What kind of plasma session we're in: are we in a full workspace, an application?... Q_PROPERTY(SessionType sessionType READ sessionType CONSTANT) public: enum WindowType { Window, /** The window is a normal resizable window with titlebar and appears in the taskbar */ FullScreen, /** The window is fullscreen and goes over all the other windows */ Desktop, /** The window is the desktop layer, under everything else, doesn't appear in the taskbar */ WindowedDesktop /** full screen and borderless as Desktop, but can be brought in front and appears in the taskbar */ }; - Q_ENUMS(WindowType) + Q_ENUM(WindowType) enum SessionType { ApplicationSession, /** our session is a normal application */ ShellSession /** We are running as the primary user interface of this machine */ }; - Q_ENUMS(SessionType) + Q_ENUM(SessionType) explicit DesktopView(Plasma::Corona *corona, QScreen *targetScreen = 0); ~DesktopView() override; /*This is different from screen() as is always there, even if the window is temporarly outside the screen or if is hidden: only plasmashell will ever change this property, unlike QWindow::screen()*/ void setScreenToFollow(QScreen *screen); QScreen *screenToFollow() const; void adaptToScreen(); void showEvent(QShowEvent*) override; WindowType windowType() const; void setWindowType(WindowType type); SessionType sessionType() const; protected: bool event(QEvent *e) override; void keyPressEvent(QKeyEvent *e) override; protected Q_SLOTS: /** * It will be called when the configuration is requested */ void showConfigurationInterface(Plasma::Applet *applet) override; private Q_SLOTS: void screenGeometryChanged(); Q_SIGNALS: void stayBehindChanged(); void windowTypeChanged(); private: void coronaPackageChanged(const KPackage::Package &package); void ensureWindowType(); void setupWaylandIntegration(); QPointer m_configView; QPointer m_oldScreen; QPointer m_screenToFollow; WindowType m_windowType; KWayland::Client::PlasmaShellSurface *m_shellSurface; }; #endif // DESKTOVIEW_H diff --git a/shell/panelview.h b/shell/panelview.h index 36c414e1..7e521bd4 100644 --- a/shell/panelview.h +++ b/shell/panelview.h @@ -1,230 +1,230 @@ /* * Copyright 2013 Marco Martin * * 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. */ #ifndef PANELVIEW_H #define PANELVIEW_H #include #include #include #include "plasmaquick/containmentview.h" #include "plasmaquick/configview.h" class ShellCorona; namespace KWayland { namespace Client { class PlasmaShellSurface; } } class PanelView : public PlasmaQuick::ContainmentView { Q_OBJECT /** * Alignment of the panel: when not fullsize it can be aligned at left, * right or center of the screen (left and right work as top/bottom * too for vertical panels) */ Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) /** * how much the panel is moved from the left/right/center anchor point */ Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged) /** * height of horizontal panels, width of vertical panels */ Q_PROPERTY(int thickness READ thickness WRITE setThickness NOTIFY thicknessChanged) /** * width of horizontal panels, height of vertical panels */ Q_PROPERTY(int length READ length WRITE setLength NOTIFY lengthChanged) /** * if the panel resizes itself, never resize more than that */ Q_PROPERTY(int maximumLength READ maximumLength WRITE setMaximumLength NOTIFY maximumLengthChanged) /** * if the panel resizes itself, never resize less than that */ Q_PROPERTY(int minimumLength READ minimumLength WRITE setMinimumLength NOTIFY minimumLengthChanged) /** * how much the panel is distant for the screen edge: used by the panel controller to drag it around */ Q_PROPERTY(int distance READ distance WRITE setDistance NOTIFY distanceChanged) /** * The borders that should have a shadow * @since 5.7 */ Q_PROPERTY(Plasma::FrameSvg::EnabledBorders enabledBorders READ enabledBorders NOTIFY enabledBordersChanged) /** * informations about the screen in which the panel is in */ Q_PROPERTY(QScreen *screenToFollow READ screenToFollow WRITE setScreenToFollow NOTIFY screenToFollowChanged) /** * how the panel behaves, visible, autohide etc. */ Q_PROPERTY(VisibilityMode visibilityMode READ visibilityMode WRITE setVisibilityMode NOTIFY visibilityModeChanged) public: enum VisibilityMode { NormalPanel = 0, /** default, always visible panel, the windowmanager reserves a places for it */ AutoHide, /**the panel will be shownn only if the mouse cursor is on screen edges */ LetWindowsCover, /** always visible, windows will go over the panel, no area reserved */ WindowsGoBelow /** always visible, windows will go under the panel, no area reserved */ }; - Q_ENUMS(VisibilityMode) + Q_ENUM(VisibilityMode) explicit PanelView(ShellCorona *corona, QScreen *targetScreen = 0, QWindow *parent = 0); ~PanelView() override; KConfigGroup config() const override; Q_INVOKABLE void maximize(); Qt::Alignment alignment() const; void setAlignment(Qt::Alignment alignment); int offset() const; void setOffset(int offset); int thickness() const; void setThickness(int thickness); int length() const; void setLength(int value); int maximumLength() const; void setMaximumLength(int length); int minimumLength() const; void setMinimumLength(int length); int distance() const; void setDistance(int dist); Plasma::FrameSvg::EnabledBorders enabledBorders() const; VisibilityMode visibilityMode() const; void setVisibilityMode(PanelView::VisibilityMode mode); /** * @returns the geometry of the panel given a distance */ QRect geometryByDistance(int distance) const; /* Shared with script/panel.cpp */ static KConfigGroup panelConfig(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen); void updateStruts(); /*This is different from screen() as is always there, even if the window is temporarly outside the screen or if is hidden: only plasmashell will ever change this property, unlike QWindow::screen()*/ void setScreenToFollow(QScreen* screen); QScreen* screenToFollow() const; protected: void resizeEvent(QResizeEvent *ev) override; void showEvent(QShowEvent *event) override; void moveEvent(QMoveEvent *ev) override; bool event(QEvent *e) override; void updateMask(); Q_SIGNALS: void alignmentChanged(); void offsetChanged(); void screenGeometryChanged(); void thicknessChanged(); void lengthChanged(); void maximumLengthChanged(); void minimumLengthChanged(); void distanceChanged(); void enabledBordersChanged(); //QWindow does not have a property for screen. Adding this property requires re-implementing the signal void screenToFollowChanged(QScreen *screen); void visibilityModeChanged(); protected Q_SLOTS: /** * It will be called when the configuration is requested */ void showConfigurationInterface(Plasma::Applet *applet) override; private Q_SLOTS: void themeChanged(); void positionPanel(); void restore(); void setAutoHideEnabled(bool autoHideEnabled); void showTemporarily(); void containmentChanged(); void statusChanged(Plasma::Types::ItemStatus); void restoreAutoHide(); void screenDestroyed(QObject* screen); void adaptToScreen(); private: void resizePanel(); void integrateScreen(); bool containmentContainsPosition(const QPointF &point) const; QPointF positionAdjustedForContainment(const QPointF &point) const; void setupWaylandIntegration(); void visibilityModeToWayland(); bool edgeActivated() const; void updateEnabledBorders(); bool canSetStrut() const; int m_offset; int m_maxLength; int m_minLength; int m_contentLength; int m_distance; int m_thickness; Qt::Alignment m_alignment; QPointer m_panelConfigView; ShellCorona *m_corona; QTimer m_strutsTimer; VisibilityMode m_visibilityMode; Plasma::Theme m_theme; QTimer m_positionPaneltimer; QTimer m_unhideTimer; //only for the mask, not to actually paint Plasma::FrameSvg *m_background; Plasma::FrameSvg::EnabledBorders m_enabledBorders = Plasma::FrameSvg::AllBorders; KWayland::Client::PlasmaShellSurface *m_shellSurface; QPointer m_lastScreen; QPointer m_screenToFollow; static const int STRUTSTIMERDELAY = 200; }; #endif // PANELVIEW_H diff --git a/shell/scripting/scriptengine_v1.cpp b/shell/scripting/scriptengine_v1.cpp index ab33bf50..e8326807 100644 --- a/shell/scripting/scriptengine_v1.cpp +++ b/shell/scripting/scriptengine_v1.cpp @@ -1,931 +1,931 @@ /* * Copyright 2009 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 "scriptengine_v1.h" #include #include #include #include #include #include #include #include #include #include #include #include // KIO //#include // no camelcase include #include #include #include #include #include #include #include "appinterface.h" #include "containment.h" #include "configgroup.h" #include "i18n.h" #include "panel.h" #include "widget.h" #include "../shellcorona.h" #include "../standaloneappcorona.h" #include "../screenpool.h" namespace { template inline void awaitFuture(const QFuture &future) { while (!future.isFinished()) { QCoreApplication::processEvents(); } } class ScriptArray_forEach_Helper { public: ScriptArray_forEach_Helper(const QScriptValue &array) : array(array) { } // operator + is commonly used for these things // to avoid having the lambda inside the parenthesis template void operator+ (Function function) const { if (!array.isArray()) return; int length = array.property("length").toInteger(); for (int i = 0; i < length; ++i) { function(array.property(i)); } } private: const QScriptValue &array; }; #define SCRIPT_ARRAY_FOREACH(Variable, Array) \ ScriptArray_forEach_Helper(Array) + [&] (const QScriptValue &Variable) class ScriptObject_forEach_Helper { public: ScriptObject_forEach_Helper(const QScriptValue &object) : object(object) { } // operator + is commonly used for these things // to avoid having the lambda inside the parenthesis template void operator+ (Function function) const { QScriptValueIterator it(object); while (it.hasNext()) { it.next(); function(it.name(), it.value()); } } private: const QScriptValue &object; }; #define SCRIPT_OBJECT_FOREACH(Key, Value, Array) \ ScriptObject_forEach_Helper(Array) + [&] (const QString &Key, const QScriptValue &Value) // Case insensitive comparison of two strings template inline bool matches(const QString &object, const StringType &string) { return object.compare(string, Qt::CaseInsensitive) == 0; } } namespace WorkspaceScripting { QScriptValue ScriptEngine::V1::desktopById(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("activityById requires an id")); } const uint id = context->argument(0).toInt32(); ScriptEngine *env = envFor(engine); foreach (Plasma::Containment *c, env->m_corona->containments()) { if (c->id() == id && !isPanel(c)) { return env->wrap(c); } } return engine->undefinedValue(); } QScriptValue ScriptEngine::V1::desktopsForActivity(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("desktopsForActivity requires an id")); } QScriptValue containments = engine->newArray(); int count = 0; const QString id = context->argument(0).toString(); ScriptEngine *env = envFor(engine); const auto result = env->desktopsForActivity(id); for (Containment* c: result) { containments.setProperty(count, env->wrap(c)); ++count; } containments.setProperty(QStringLiteral("length"), count); return containments; } QScriptValue ScriptEngine::V1::desktopForScreen(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("activityForScreen requires a screen id")); } const uint screen = context->argument(0).toInt32(); ScriptEngine *env = envFor(engine); return env->wrap(env->m_corona->containmentForScreen(screen)); } QScriptValue ScriptEngine::V1::createActivity(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() < 0) { return context->throwError(i18n("createActivity required the activity name")); } const QString name = context->argument(0).toString(); QString plugin = context->argument(1).toString(); ScriptEngine *env = envFor(engine); KActivities::Controller controller; // This is not the nicest way to do this, but createActivity // is a synchronous API :/ QFuture futureId = controller.addActivity(name); awaitFuture(futureId); QString id = futureId.result(); qDebug() << "Setting default Containment plugin:" << plugin; ShellCorona *sc = qobject_cast(env->m_corona); StandaloneAppCorona *ac = qobject_cast(env->m_corona); if (sc) { if (plugin.isEmpty() || plugin == QLatin1String("undefined")) { plugin = sc->defaultContainmentPlugin(); } sc->insertActivity(id, plugin); } else if (ac) { if (plugin.isEmpty() || plugin == QLatin1String("undefined")) { KConfigGroup shellCfg = KConfigGroup(KSharedConfig::openConfig(env->m_corona->package().filePath("defaults")), "Desktop"); plugin = shellCfg.readEntry("Containment", "org.kde.desktopcontainment"); } ac->insertActivity(id, plugin); } return QScriptValue(id); } QScriptValue ScriptEngine::V1::setCurrentActivity(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() < 0) { return context->throwError(i18n("setCurrentActivity required the activity id")); } const QString id = context->argument(0).toString(); KActivities::Controller controller; QFuture task = controller.setCurrentActivity(id); awaitFuture(task); return QScriptValue(task.result()); } QScriptValue ScriptEngine::V1::setActivityName(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() < 2) { return context->throwError(i18n("setActivityName required the activity id and name")); } const QString id = context->argument(0).toString(); const QString name = context->argument(1).toString(); KActivities::Controller controller; QFuture task = controller.setActivityName(id, name); awaitFuture(task); return QScriptValue(); } QScriptValue ScriptEngine::V1::activityName(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() < 1) { return context->throwError(i18n("setActivityName required the activity id and name")); } const QString id = context->argument(0).toString(); KActivities::Info info(id); return QScriptValue(info.name()); } QScriptValue ScriptEngine::V1::currentActivity(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) Q_UNUSED(context) KActivities::Consumer consumer; return consumer.currentActivity(); } QScriptValue ScriptEngine::V1::activities(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) return qScriptValueFromSequence(engine, envFor(engine)->availableActivities()); } // Utility function to process configs and config groups template void loadSerializedConfigs(Object *object, const QScriptValue &configs) { SCRIPT_OBJECT_FOREACH(escapedGroup, config, configs) { // If the config group is set, pass it on to the containment QStringList groups = escapedGroup.split('/', QString::SkipEmptyParts); for (QString &group: groups) { group = QUrl::fromPercentEncoding(group.toUtf8()); } qDebug() << "Config group" << groups; object->setCurrentConfigGroup(groups); // Read other properties and set the configuration SCRIPT_OBJECT_FOREACH(key, value, config) { object->writeConfig(key, value.toVariant()); }; }; } QScriptValue ScriptEngine::V1::loadSerializedLayout(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() < 1) { return context->throwError(i18n("loadSerializedLayout requires the JSON object to deserialize from")); } ScriptEngine *env = envFor(engine); const auto data = context->argument(0); if (data.property("serializationFormatVersion").toInteger() != 1) { return context->throwError(i18n("loadSerializedLayout: invalid version of the serialized object")); } const auto desktops = env->desktopsForActivity(KActivities::Consumer().currentActivity()); Q_ASSERT_X(desktops.size() != 0, "V1::loadSerializedLayout", "We need desktops"); // qDebug() << "DESKTOP DESERIALIZATION: Loading desktops..."; int count = 0; SCRIPT_ARRAY_FOREACH(desktopData, data.property("desktops")) { // If the template has more desktops than we do, ignore them if (count >= desktops.size()) return; auto desktop = desktops[count]; // qDebug() << "DESKTOP DESERIALIZATION: var cont = desktopsArray[...]; " << count << " -> " << desktop; // Setting the wallpaper plugin because it is special desktop->setWallpaperPlugin(desktopData.property("wallpaperPlugin").toString()); // qDebug() << "DESKTOP DESERIALIZATION: cont->setWallpaperPlugin(...) " << desktop->wallpaperPlugin(); // Now, lets go through the configs loadSerializedConfigs(desktop, desktopData.property("config")); // After the config, we want to load the applets SCRIPT_ARRAY_FOREACH(appletData, desktopData.property("applets")) { // qDebug() << "DESKTOP DESERIALIZATION: Applet: " << appletData.toString(); // TODO: It would be nicer to be able to call addWidget directly auto desktopObject = env->wrap(desktop); auto addAppletFunction = desktopObject.property("addWidget"); QScriptValueList args { appletData.property("plugin"), appletData.property("geometry.x").toInteger() * ScriptEngine::gridUnit(), appletData.property("geometry.y").toInteger() * ScriptEngine::gridUnit(), appletData.property("geometry.width").toInteger() * ScriptEngine::gridUnit(), appletData.property("geometry.height").toInteger() * ScriptEngine::gridUnit() }; auto appletObject = addAppletFunction.call(desktopObject, args); if (auto applet = qobject_cast(appletObject.toQObject())) { // Now, lets go through the configs for the applet loadSerializedConfigs(applet, appletData.property("config")); } }; count++; }; // qDebug() << "PANEL DESERIALIZATION: Loading panels..."; SCRIPT_ARRAY_FOREACH(panelData, data.property("panels")) { const auto panel = qobject_cast(env->createContainment( QStringLiteral("Panel"), QStringLiteral("org.kde.panel"))); Q_ASSERT(panel); // Basic panel setup panel->setLocation(panelData.property("location").toString()); panel->setHeight(panelData.property("height").toInteger() * ScriptEngine::gridUnit()); // Loading the config for the panel loadSerializedConfigs(panel, panelData.property("config")); // Now dealing with the applets SCRIPT_ARRAY_FOREACH(appletData, panelData.property("applets")) { // qDebug() << "PANEL DESERIALIZATION: Applet: " << appletData.toString(); // TODO: It would be nicer to be able to call addWidget directly auto panelObject = env->wrap(panel); auto addAppletFunction = panelObject.property("addWidget"); QScriptValueList args { appletData.property("plugin") }; auto appletObject = addAppletFunction.call(panelObject, args); // qDebug() << "PANEL DESERIALIZATION: addWidget" // << appletData.property("plugin").toString() // ; if (auto applet = qobject_cast(appletObject.toQObject())) { // Now, lets go through the configs for the applet loadSerializedConfigs(applet, appletData.property("config")); } }; }; return QScriptValue(); } QScriptValue ScriptEngine::V1::newPanel(QScriptContext *context, QScriptEngine *engine) { QString plugin(QStringLiteral("org.kde.panel")); if (context->argumentCount() > 0) { plugin = context->argument(0).toString(); } return createContainment(QStringLiteral("Panel"), plugin, context, engine); } QScriptValue ScriptEngine::V1::panelById(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("panelById requires an id")); } const uint id = context->argument(0).toInt32(); ScriptEngine *env = envFor(engine); foreach (Plasma::Containment *c, env->m_corona->containments()) { if (c->id() == id && isPanel(c)) { return env->wrap(c); } } return engine->undefinedValue(); } QScriptValue ScriptEngine::V1::panels(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) QScriptValue panels = engine->newArray(); ScriptEngine *env = envFor(engine); int count = 0; foreach (Plasma::Containment *c, env->m_corona->containments()) { if (isPanel(c)) { panels.setProperty(count, env->wrap(c)); ++count; } } panels.setProperty(QStringLiteral("length"), count); return panels; } QScriptValue ScriptEngine::V1::fileExists(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } const QString path = context->argument(0).toString(); if (path.isEmpty()) { return false; } QFile f(KShell::tildeExpand(path)); return f.exists(); } QScriptValue ScriptEngine::V1::loadTemplate(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { // qDebug() << "no arguments"; return false; } const QString layout = context->argument(0).toString(); if (layout.isEmpty() || layout.contains(QStringLiteral("'"))) { // qDebug() << "layout is empty"; return false; } auto filter = [&layout](const KPluginMetaData &md) -> bool { return md.pluginId() == layout && md.value(QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); }; QList offers = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); if (offers.isEmpty()) { // qDebug() << "offers fail" << constraint; return false; } KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); KPluginMetaData pluginData(offers.first()); QString path; { ScriptEngine *env = envFor(engine); ShellCorona *sc = qobject_cast(env->m_corona); if (sc) { const QString overridePackagePath = sc->lookAndFeelPackage().path() + QStringLiteral("contents/layouts/") + pluginData.pluginId(); path = overridePackagePath + QStringLiteral("/metadata.desktop"); if (QFile::exists(path)) { package.setPath(overridePackagePath); } } } if (!package.isValid()) { path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, package.defaultPackageRoot() + pluginData.pluginId() + "/metadata.desktop"); if (path.isEmpty()) { // qDebug() << "script path is empty"; return false; } package.setPath(pluginData.pluginId()); } const QString scriptFile = package.filePath("mainscript"); if (scriptFile.isEmpty()) { // qDebug() << "scriptfile is empty"; return false; } QFile file(scriptFile); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << i18n("Unable to load script file: %1", path); return false; } QString script = file.readAll(); if (script.isEmpty()) { // qDebug() << "script is empty"; return false; } ScriptEngine *env = envFor(engine); env->globalObject().setProperty(QStringLiteral("templateName"), env->newVariant(pluginData.name()), QScriptValue::ReadOnly | QScriptValue::Undeletable); env->globalObject().setProperty(QStringLiteral("templateComment"), env->newVariant(pluginData.description()), QScriptValue::ReadOnly | QScriptValue::Undeletable); QScriptValue rv = env->newObject(); QScriptContext *ctx = env->pushContext(); ctx->setThisObject(rv); env->evaluateScript(script, path); env->popContext(); return rv; } QScriptValue ScriptEngine::V1::applicationExists(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } const QString application = context->argument(0).toString(); if (application.isEmpty()) { return false; } // first, check for it in $PATH if (!QStandardPaths::findExecutable(application).isEmpty()) { return true; } if (KService::serviceByStorageId(application)) { return true; } if (application.contains(QStringLiteral("'"))) { // apostrophes just screw up the trader lookups below, so check for it return false; } // next, consult ksycoca for an app by that name if (!KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("Name =~ '%1'").arg(application)).isEmpty()) { return true; } // next, consult ksycoca for an app by that generic name if (!KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("GenericName =~ '%1'").arg(application)).isEmpty()) { return true; } return false; } QScriptValue ScriptEngine::V1::defaultApplication(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } const QString application = context->argument(0).toString(); if (application.isEmpty()) { return false; } const bool storageId = context->argumentCount() < 2 ? false : context->argument(1).toBool(); // FIXME: there are some pretty horrible hacks below, in the sense that they // assume a very // specific implementation system. there is much room for improvement here. // see // kdebase-runtime/kcontrol/componentchooser/ for all the gory details ;) if (matches(application, QLatin1String("mailer"))) { // KEMailSettings settings; // in KToolInvocation, the default is kmail; but let's be friendlier :) // QString command = settings.getSetting(KEMailSettings::ClientProgram); QString command; if (command.isEmpty()) { if (KService::Ptr kontact = KService::serviceByStorageId(QStringLiteral("kontact"))) { return storageId ? kontact->storageId() : onlyExec(kontact->exec()); } else if (KService::Ptr kmail = KService::serviceByStorageId(QStringLiteral("kmail"))) { return storageId ? kmail->storageId() : onlyExec(kmail->exec()); } } if (!command.isEmpty()) { if (false) { KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); const QString preferredTerminal = confGroup.readPathEntry( "TerminalApplication", QStringLiteral("konsole")); command = preferredTerminal + QLatin1String(" -e ") + command; } return command; } } else if (matches(application, QLatin1String("browser"))) { KConfigGroup config(KSharedConfig::openConfig(), "General"); QString browserApp = config.readPathEntry("BrowserApplication", QString()); if (browserApp.isEmpty()) { const KService::Ptr htmlApp = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html")); if (htmlApp) { browserApp = storageId ? htmlApp->storageId() : htmlApp->exec(); } } else if (browserApp.startsWith('!')) { browserApp = browserApp.mid(1); } return onlyExec(browserApp); } else if (matches(application, QLatin1String("terminal"))) { KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); return onlyExec(confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole"))); } else if (matches(application, QLatin1String("filemanager"))) { KService::Ptr service = KMimeTypeTrader::self()->preferredService( QStringLiteral("inode/directory")); if (service) { return storageId ? service->storageId() : onlyExec(service->exec()); } } else if (matches(application, QLatin1String("windowmanager"))) { KConfig cfg(QStringLiteral("ksmserverrc"), KConfig::NoGlobals); KConfigGroup confGroup(&cfg, "General"); return onlyExec( confGroup.readEntry("windowManager", QStringLiteral("kwin"))); } else if (KService::Ptr service = KMimeTypeTrader::self()->preferredService(application)) { return storageId ? service->storageId() : onlyExec(service->exec()); } else { // try the files in share/apps/kcm_componentchooser/ const QStringList services = QStandardPaths::locateAll( QStandardPaths::GenericDataLocation, QStringLiteral("kcm_componentchooser/")); qDebug() << "ok, trying in" << services; foreach (const QString &service, services) { if (!service.endsWith(QLatin1String(".desktop"))) { continue; } KConfig config(service, KConfig::SimpleConfig); KConfigGroup cg = config.group(QByteArray()); const QString type = cg.readEntry("valueName", QString()); // qDebug() << " checking" << service << type << application; if (matches(type, application)) { KConfig store( cg.readPathEntry("storeInFile", QStringLiteral("null"))); KConfigGroup storeCg(&store, cg.readEntry("valueSection", QString())); const QString exec = storeCg.readPathEntry( cg.readEntry("valueName", "kcm_componenchooser_null"), cg.readEntry("defaultImplementation", QString())); if (!exec.isEmpty()) { return exec; } break; } } } return false; } QScriptValue ScriptEngine::V1::applicationPath(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } const QString application = context->argument(0).toString(); if (application.isEmpty()) { return false; } // first, check for it in $PATH const QString path = QStandardPaths::findExecutable(application); if (!path.isEmpty()) { return path; } if (KService::Ptr service = KService::serviceByStorageId(application)) { return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, service->entryPath()); } if (application.contains(QStringLiteral("'"))) { // apostrophes just screw up the trader lookups below, so check for it return QString(); } // next, consult ksycoca for an app by that name KService::List offers = KServiceTypeTrader::self()->query( QStringLiteral("Application"), QStringLiteral("Name =~ '%1'").arg(application)); if (offers.isEmpty()) { // next, consult ksycoca for an app by that generic name offers = KServiceTypeTrader::self()->query( QStringLiteral("Application"), QStringLiteral("GenericName =~ '%1'").arg(application)); } if (!offers.isEmpty()) { KService::Ptr offer = offers.first(); return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, offer->entryPath()); } return QString(); } QScriptValue ScriptEngine::V1::userDataPath(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return QDir::homePath(); } const QString type = context->argument(0).toString(); if (type.isEmpty()) { return QDir::homePath(); } QStandardPaths::StandardLocation location = QStandardPaths::GenericDataLocation; if (matches(type, QLatin1String("desktop"))) { location = QStandardPaths::DesktopLocation; } else if (matches(type, QLatin1String("documents"))) { location = QStandardPaths::DocumentsLocation; } else if (matches(type, QLatin1String("music"))) { location = QStandardPaths::MusicLocation; } else if (matches(type, QLatin1String("video"))) { location = QStandardPaths::MoviesLocation; } else if (matches(type, QLatin1String("downloads"))) { location = QStandardPaths::DownloadLocation; } else if (matches(type, QLatin1String("pictures"))) { location = QStandardPaths::PicturesLocation; } else if (matches(type, QLatin1String("config"))) { location = QStandardPaths::GenericConfigLocation; } if (context->argumentCount() > 1) { QString loc = QStandardPaths::writableLocation(location); loc.append(QDir::separator()); loc.append(context->argument(1).toString()); return loc; } const QStringList &locations = QStandardPaths::standardLocations(location); return locations.count() ? locations.first() : QString(); } QScriptValue ScriptEngine::V1::knownWallpaperPlugins(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) QString formFactor; if (context->argumentCount() > 0) { formFactor = context->argument(0).toString(); } QString constraint; if (!formFactor.isEmpty()) { constraint.append("[X-Plasma-FormFactors] ~~ '") .append(formFactor) .append("'"); } - QList wallpapers + const QList wallpapers = KPackage::PackageLoader::self()->listPackages( QStringLiteral("Plasma/Wallpaper"), QString()); QScriptValue rv = engine->newArray(wallpapers.size()); for (auto wp : wallpapers) { rv.setProperty(wp.name(), engine->newArray(0)); } return rv; } QScriptValue ScriptEngine::V1::configFile(QScriptContext *context, QScriptEngine *engine) { ConfigGroup *file = 0; if (context->argumentCount() > 0) { if (context->argument(0).isString()) { file = new ConfigGroup; const QString &fileName = context->argument(0).toString(); const ScriptEngine *env = envFor(engine); const Plasma::Corona *corona = env->corona(); if (fileName == corona->config()->name()) { file->setConfig(corona->config()); } else { file->setFile(fileName); } if (context->argumentCount() > 1) { file->setGroup(context->argument(1).toString()); } } else if (ConfigGroup *parent = qobject_cast( context->argument(0).toQObject())) { file = new ConfigGroup(parent); if (context->argumentCount() > 1) { file->setGroup(context->argument(1).toString()); } } } else { file = new ConfigGroup; } QScriptValue v = engine->newQObject(file, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeSuperClassProperties | QScriptEngine::ExcludeSuperClassMethods); return v; } QScriptValue ScriptEngine::V1::desktops(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) QScriptValue containments = engine->newArray(); ScriptEngine *env = envFor(engine); int count = 0; foreach (Plasma::Containment *c, env->corona()->containments()) { // make really sure we get actual desktops, so check for a non empty // activty id if (!isPanel(c) && !c->activity().isEmpty()) { containments.setProperty(count, env->wrap(c)); ++count; } } containments.setProperty(QStringLiteral("length"), count); return containments; } QScriptValue ScriptEngine::V1::gridUnit() { return ScriptEngine::gridUnit(); } QScriptValue ScriptEngine::V1::createContainment(const QString &type, const QString &defaultPlugin, QScriptContext *context, QScriptEngine *engine) { const QString plugin = context->argumentCount() > 0 ? context->argument(0).toString() : defaultPlugin; ScriptEngine *env = envFor(engine); auto result = env->createContainment(type, plugin); if (!result) { return context->throwError(i18n("Could not find a plugin for %1 named %2.", type, plugin)); } return env->wrap(result); } } // namespace WorkspaceScripting diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp index 25e03786..48623ebc 100644 --- a/shell/shellcorona.cpp +++ b/shell/shellcorona.cpp @@ -1,1973 +1,1973 @@ /* * Copyright 2008 Aaron Seigo * Copyright 2013 Sebastian Kügler * Copyright 2013 Ivan Cukic * 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 "shellcorona.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 #include "config-ktexteditor.h" // HAVE_KTEXTEDITOR #include "alternativeshelper.h" #include "desktopview.h" #include "panelview.h" #include "scripting/scriptengine.h" #include "plasmaquick/configview.h" #include "shellmanager.h" #include "osd.h" #include "screenpool.h" #include "waylanddialogfilter.h" #include "plasmashelladaptor.h" #include "debug.h" #include "futureutil.h" #ifndef NDEBUG #define CHECK_SCREEN_INVARIANTS screenInvariants(); #else #define CHECK_SCREEN_INVARIANTS #endif #if HAVE_X11 #include #include #endif static const int s_configSyncDelay = 10000; // 10 seconds ShellCorona::ShellCorona(QObject *parent) : Plasma::Corona(parent), m_screenPool(new ScreenPool(KSharedConfig::openConfig(), this)), m_activityController(new KActivities::Controller(this)), m_addPanelAction(nullptr), m_addPanelsMenu(nullptr), m_interactiveConsole(nullptr), m_waylandPlasmaShell(nullptr) { setupWaylandIntegration(); qmlRegisterUncreatableType("org.kde.plasma.shell", 2, 0, "Desktop", QStringLiteral("It is not possible to create objects of type Desktop")); qmlRegisterUncreatableType("org.kde.plasma.shell", 2, 0, "Panel", QStringLiteral("It is not possible to create objects of type Panel")); m_lookAndFeelPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); if (!packageName.isEmpty()) { m_lookAndFeelPackage.setPath(packageName); } connect(this, &Plasma::Corona::containmentCreated, this, [this] (Plasma::Containment *c) { executeSetupPlasmoidScript(c, c); }); connect(this, &Plasma::Corona::availableScreenRectChanged, this, &Plasma::Corona::availableScreenRegionChanged); m_appConfigSyncTimer.setSingleShot(true); m_appConfigSyncTimer.setInterval(s_configSyncDelay); connect(&m_appConfigSyncTimer, &QTimer::timeout, this, &ShellCorona::syncAppConfig); m_waitingPanelsTimer.setSingleShot(true); m_waitingPanelsTimer.setInterval(250); connect(&m_waitingPanelsTimer, &QTimer::timeout, this, &ShellCorona::createWaitingPanels); m_reconsiderOutputsTimer.setSingleShot(true); m_reconsiderOutputsTimer.setInterval(1000); connect(&m_reconsiderOutputsTimer, &QTimer::timeout, this, &ShellCorona::reconsiderOutputs); m_desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package().filePath("defaults")), "Desktop"); m_lnfDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(m_lookAndFeelPackage.filePath("defaults")), "Desktop"); m_lnfDefaultsConfig = KConfigGroup(&m_lnfDefaultsConfig, QStringLiteral("org.kde.plasma.desktop")); new PlasmaShellAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/PlasmaShell"), this); connect(this, &Plasma::Corona::startupCompleted, this, []() { qDebug() << "Plasma Shell startup completed"; QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); //TODO: remove }); // Look for theme config in plasmarc, if it isn't configured, take the theme from the // LookAndFeel package, if either is set, change the default theme connect(qApp, &QCoreApplication::aboutToQuit, this, [this]() { //saveLayout is a slot but arguments not compatible saveLayout(); }); connect(this, &ShellCorona::containmentAdded, this, &ShellCorona::handleContainmentAdded); QAction *dashboardAction = actions()->addAction(QStringLiteral("show dashboard")); QObject::connect(dashboardAction, &QAction::triggered, this, &ShellCorona::setDashboardShown); dashboardAction->setText(i18n("Show Desktop")); connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, [dashboardAction](bool showing) { dashboardAction->setText(showing ? i18n("Hide Desktop") : i18n("Show Desktop")); dashboardAction->setChecked(showing); }); dashboardAction->setAutoRepeat(true); dashboardAction->setCheckable(true); dashboardAction->setIcon(QIcon::fromTheme(QStringLiteral("dashboard-show"))); dashboardAction->setData(Plasma::Types::ControlAction); KGlobalAccel::self()->setGlobalShortcut(dashboardAction, Qt::CTRL + Qt::Key_F12); checkAddPanelAction(); connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(checkAddPanelAction(QStringList))); //Activity stuff QAction *activityAction = actions()->addAction(QStringLiteral("manage activities")); connect(activityAction, &QAction::triggered, this, &ShellCorona::toggleActivityManager); activityAction->setText(i18n("Activities...")); activityAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-activities"))); activityAction->setData(Plasma::Types::ConfigureAction); activityAction->setShortcut(QKeySequence(QStringLiteral("alt+d, alt+a"))); activityAction->setShortcutContext(Qt::ApplicationShortcut); KGlobalAccel::self()->setGlobalShortcut(activityAction, Qt::META + Qt::Key_Q); QAction *stopActivityAction = actions()->addAction(QStringLiteral("stop current activity")); QObject::connect(stopActivityAction, &QAction::triggered, this, &ShellCorona::stopCurrentActivity); stopActivityAction->setText(i18n("Stop Current Activity")); stopActivityAction->setData(Plasma::Types::ControlAction); stopActivityAction->setVisible(false); KGlobalAccel::self()->setGlobalShortcut(stopActivityAction, Qt::META + Qt::Key_S); connect(m_activityController, &KActivities::Controller::currentActivityChanged, this, &ShellCorona::currentActivityChanged); connect(m_activityController, &KActivities::Controller::activityAdded, this, &ShellCorona::activityAdded); connect(m_activityController, &KActivities::Controller::activityRemoved, this, &ShellCorona::activityRemoved); new Osd(this); qApp->installEventFilter(this); } ShellCorona::~ShellCorona() { while (!containments().isEmpty()) { //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona delete containments().first(); } qDeleteAll(m_panelViews); m_panelViews.clear(); } bool ShellCorona::eventFilter(QObject *watched, QEvent *event) { if (event->type() == QEvent::PlatformSurface && watched->inherits("PlasmaQuick::Dialog")) { QPlatformSurfaceEvent *se = static_cast(event); if (se->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { if (KWindowSystem::isPlatformWayland()) { WaylandDialogFilter::install(qobject_cast(watched), this); } } } return QObject::eventFilter(watched, event); } KPackage::Package ShellCorona::lookAndFeelPackage() { return m_lookAndFeelPackage; } void ShellCorona::setShell(const QString &shell) { if (m_shell == shell) { return; } m_shell = shell; KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Shell")); package.setPath(shell); package.setAllowExternalPaths(true); setKPackage(package); m_desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Desktop"); m_lnfDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(m_lookAndFeelPackage.filePath("defaults")), "Desktop"); m_lnfDefaultsConfig = KConfigGroup(&m_lnfDefaultsConfig, shell); const QString themeGroupKey = QStringLiteral("Theme"); const QString themeNameKey = QStringLiteral("name"); QString themeName; KConfigGroup plasmarc(KSharedConfig::openConfig(QStringLiteral("plasmarc")), themeGroupKey); themeName = plasmarc.readEntry(themeNameKey, themeName); if (themeName.isEmpty()) { KConfigGroup shellCfg = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Theme"); themeName = shellCfg.readEntry("name", "default"); KConfigGroup lnfCfg = KConfigGroup(KSharedConfig::openConfig( m_lookAndFeelPackage.filePath("defaults")), "plasmarc" ); lnfCfg = KConfigGroup(&lnfCfg, themeGroupKey); themeName = lnfCfg.readEntry(themeNameKey, themeName); } if (!themeName.isEmpty()) { Plasma::Theme *t = new Plasma::Theme(this); t->setThemeName(themeName); } //FIXME: this would change the runtime platform to a fixed one if available // but a different way to load platform specific components is needed beforehand // because if we import and use two different components plugin, the second time // the import is called it will fail /* KConfigGroup cg(KSharedConfig::openConfig(package.filePath("defaults")), "General"); KDeclarative::KDeclarative::setRuntimePlatform(cg.readEntry("DefaultRuntimePlatform", QStringList()));*/ unload(); /* * we want to make an initial load once we have the initial screen config and we have loaded the activities _IF_ KAMD is running * it is valid for KAMD to not be running. * * Potentially 2 async jobs * * here we connect for status changes from KAMD, and fetch the first config from kscreen. * load() will check that we have a kscreen config, and m_activityController->serviceStatus() is not loading (i.e not unknown) * * It might seem that we only need this connection if the activityConsumer is currently in state Unknown, however * there is an issue where m_activityController will start the kactivitymanagerd, as KAMD is starting the serviceStatus will be "not running" * Whilst we are loading the kscreen config, the event loop runs and we might find KAMD has started. * m_activityController will change from "not running" to unknown, and might still be unknown when the kscreen fetching is complete. * * if that happens we want to continue monitoring for state changes, and only finally load when it is up. * * See https://bugs.kde.org/show_bug.cgi?id=342431 be careful about changing * * The unique connection makes sure we don't reload plasma if KAMD ever crashes and reloads, the signal is disconnected in the body of load */ connect(m_activityController, &KActivities::Controller::serviceStatusChanged, this, &ShellCorona::load, Qt::UniqueConnection); if (m_activityController->serviceStatus() == KActivities::Controller::Running) { load(); } } QJsonObject dumpconfigGroupJS(const KConfigGroup &rootGroup) { QJsonObject result; QStringList hierarchy; QStringList escapedHierarchy; QList groups{rootGroup}; QSet visitedNodes; const QSet forbiddenKeys { QStringLiteral("activityId"), QStringLiteral("ItemsGeometries"), QStringLiteral("AppletOrder"), QStringLiteral("SystrayContainmentId"), QStringLiteral("location"), QStringLiteral("plugin") }; auto groupID = [&escapedHierarchy]() { return '/' + escapedHierarchy.join('/'); }; // Perform a depth-first tree traversal for config groups while (!groups.isEmpty()) { KConfigGroup cg = groups.last(); KConfigGroup parentCg = cg; //FIXME: name is not enough hierarchy.clear(); escapedHierarchy.clear(); while (parentCg.isValid() && parentCg.name() != rootGroup.name()) { const auto name = parentCg.name(); hierarchy.prepend(name); escapedHierarchy.prepend(QString::fromUtf8(QUrl::toPercentEncoding(name.toUtf8()))); parentCg = parentCg.parent(); } visitedNodes.insert(groupID()); groups.pop_back(); QJsonObject configGroupJson; if (!cg.keyList().isEmpty()) { //TODO: this is conditional if applet or containment const auto map = cg.entryMap(); auto i = map.cbegin(); for (; i != map.cend(); ++i) { //some blacklisted keys we don't want to save if (!forbiddenKeys.contains(i.key())) { configGroupJson.insert(i.key(), i.value()); } } } foreach (const QString &groupName, cg.groupList()) { if (groupName == QStringLiteral("Applets") || visitedNodes.contains(groupID() + '/' + groupName)) { continue; } groups << KConfigGroup(&cg, groupName); } if (!configGroupJson.isEmpty()) { result.insert(groupID(), configGroupJson); } } return result; } QByteArray ShellCorona::dumpCurrentLayoutJS() const { QJsonObject root; root.insert("serializationFormatVersion", "1"); //same gridUnit calculation as ScriptEngine int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height(); if (gridUnit % 2 != 0) { gridUnit++; } auto isPanel = [] (Plasma::Containment *cont) { return (cont->formFactor() == Plasma::Types::Horizontal || cont->formFactor() == Plasma::Types::Vertical) && (cont->location() == Plasma::Types::TopEdge || cont->location() == Plasma::Types::BottomEdge || cont->location() == Plasma::Types::LeftEdge || cont->location() == Plasma::Types::RightEdge) && cont->pluginInfo().pluginName() != QStringLiteral("org.kde.plasma.private.systemtray"); }; auto isDesktop = [] (Plasma::Containment *cont) { return !cont->activity().isEmpty(); }; const auto containments = ShellCorona::containments(); // Collecting panels QJsonArray panelsJsonArray; foreach (Plasma::Containment *cont, containments) { if (!isPanel(cont)) { continue; } QJsonObject panelJson; const PanelView *view = m_panelViews.value(cont); const auto location = cont->location(); panelJson.insert("location", location == Plasma::Types::TopEdge ? "top" : location == Plasma::Types::LeftEdge ? "left" : location == Plasma::Types::RightEdge ? "right" : /* Plasma::Types::BottomEdge */ "bottom"); const qreal units = // If we do not have a panel, fallback to 4 units !view ? 4 : location == Plasma::Types::TopEdge ? view->height() / gridUnit : location == Plasma::Types::LeftEdge ? view->width() / gridUnit : location == Plasma::Types::RightEdge ? view->width() / gridUnit : /* Plasma::Types::BottomEdge */ view->height() / gridUnit; panelJson.insert("height", units); // Saving the config keys const KConfigGroup contConfig = cont->config(); panelJson.insert("config", dumpconfigGroupJS(contConfig)); // Generate the applets array QJsonArray appletsJsonArray; // Try to parse the encoded applets order const KConfigGroup genericConf(&contConfig, QStringLiteral("General")); const QStringList appletsOrderStrings = genericConf.readEntry(QStringLiteral("AppletOrder"), QString()) .split(QChar(';')); // Consider the applet order to be valid only if there are as many entries as applets() if (appletsOrderStrings.length() == cont->applets().length()) { foreach (const QString &appletId, appletsOrderStrings) { KConfigGroup appletConfig(&contConfig, QStringLiteral("Applets")); appletConfig = KConfigGroup(&appletConfig, appletId); const QString pluginName = appletConfig.readEntry(QStringLiteral("plugin"), QString()); if (pluginName.isEmpty()) { continue; } QJsonObject appletJson; appletJson.insert("plugin", pluginName); appletJson.insert("config", dumpconfigGroupJS(appletConfig)); appletsJsonArray << appletJson; } } else { foreach (Plasma::Applet *applet, cont->applets()) { QJsonObject appletJson; KConfigGroup appletConfig = applet->config(); appletJson.insert("plugin", applet->pluginInfo().pluginName()); appletJson.insert("config", dumpconfigGroupJS(appletConfig)); appletsJsonArray << appletJson; } } panelJson.insert("applets", appletsJsonArray); panelsJsonArray << panelJson; } root.insert("panels", panelsJsonArray); // Now we are collecting desktops QJsonArray desktopsJson; const auto currentActivity = m_activityController->currentActivity(); foreach (Plasma::Containment *cont, containments) { if (!isDesktop(cont) || cont->activity() != currentActivity) { continue; } QJsonObject desktopJson; desktopJson.insert("wallpaperPlugin", cont->wallpaper()); // Get the config for the containment KConfigGroup contConfig = cont->config(); desktopJson.insert("config", dumpconfigGroupJS(contConfig)); // Try to parse the item geometries const KConfigGroup genericConf(&contConfig, QStringLiteral("General")); const QStringList appletsGeomStrings = genericConf.readEntry(QStringLiteral("ItemsGeometries"), QString()) .split(QChar(';')); QHash appletGeometries; foreach (const QString &encoded, appletsGeomStrings) { const QStringList keyValue = encoded.split(QChar(':')); if (keyValue.length() != 2) { continue; } const QStringList rectPieces = keyValue.last().split(QChar(',')); if (rectPieces.length() != 5) { continue; } QRect rect(rectPieces[0].toInt(), rectPieces[1].toInt(), rectPieces[2].toInt(), rectPieces[3].toInt()); appletGeometries[keyValue.first()] = rect; } QJsonArray appletsJsonArray; foreach (Plasma::Applet *applet, cont->applets()) { const QRect geometry = appletGeometries.value( QStringLiteral("Applet-") % QString::number(applet->id())); QJsonObject appletJson; appletJson.insert("title", applet->title()); appletJson.insert("plugin", applet->pluginInfo().pluginName()); appletJson.insert("geometry.x", geometry.x() / gridUnit); appletJson.insert("geometry.y", geometry.y() / gridUnit); appletJson.insert("geometry.width", geometry.width() / gridUnit); appletJson.insert("geometry.height", geometry.height() / gridUnit); KConfigGroup appletConfig = applet->config(); appletJson.insert("config", dumpconfigGroupJS(appletConfig)); appletsJsonArray << appletJson; } desktopJson.insert("applets", appletsJsonArray); desktopsJson << desktopJson; } root.insert("desktops", desktopsJson); QJsonDocument json; json.setObject(root); return "var plasma = getApiVersion(1);\n\n" "var layout = " + json.toJson() + ";\n\n" "plasma.loadSerializedLayout(layout);\n"; } void ShellCorona::loadLookAndFeelDefaultLayout(const QString &packageName) { KPackage::Package newPack = m_lookAndFeelPackage; newPack.setPath(packageName); if (!newPack.isValid()) { return; } KSharedConfig::Ptr conf = KSharedConfig::openConfig(QStringLiteral("plasma-") + m_shell + QStringLiteral("-appletsrc"), KConfig::SimpleConfig); m_lookAndFeelPackage.setPath(packageName); //get rid of old config for (const QString &group : conf->groupList()) { conf->deleteGroup(group); } conf->sync(); unload(); load(); } QString ShellCorona::shell() const { return m_shell; } void ShellCorona::load() { if (m_shell.isEmpty() || m_activityController->serviceStatus() != KActivities::Controller::Running) { return; } disconnect(m_activityController, &KActivities::Controller::serviceStatusChanged, this, &ShellCorona::load); //TODO: a kconf_update script is needed QString configFileName(QStringLiteral("plasma-") + m_shell + QStringLiteral("-appletsrc")); loadLayout(configFileName); checkActivities(); if (containments().isEmpty()) { // Seems like we never really get to this point since loadLayout already // (virtually) calls loadDefaultLayout if it does not load anything // from the config file. Maybe if the config file is not empty, // but still does not have any containments loadDefaultLayout(); processUpdateScripts(); } else { processUpdateScripts(); foreach(Plasma::Containment *containment, containments()) { if (containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment) { //Don't give a view to containments that don't want one (negative lastscreen) //(this is pretty mucha special case for the systray) //also, make sure we don't have a view already. //this will be true for first startup as the view has already been created at the new Panel JS call if (!m_waitingPanels.contains(containment) && containment->lastScreen() >= 0 && !m_panelViews.contains(containment)) { m_waitingPanels << containment; } //historically CustomContainments are treated as desktops } else if (containment->containmentType() == Plasma::Types::DesktopContainment || containment->containmentType() == Plasma::Types::CustomContainment) { //FIXME ideally fix this, or at least document the crap out of it int screen = containment->lastScreen(); if (screen < 0) { screen = 0; qWarning() << "last screen is < 0 so putting containment on screen " << screen; } insertContainment(containment->activity(), screen, containment); } } } //NOTE: this is needed in case loadLayout() did *not* call loadDefaultLayout() //it needs to be after of loadLayout() as it would always create new //containments on each startup otherwise for (QScreen* screen : qGuiApp->screens()) { addOutput(screen); } connect(qGuiApp, &QGuiApplication::screenAdded, this, &ShellCorona::addOutput); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &ShellCorona::primaryOutputChanged); connect(qGuiApp, &QGuiApplication::screenRemoved, this, &ShellCorona::screenRemoved); if (!m_waitingPanels.isEmpty()) { m_waitingPanelsTimer.start(); } if (config()->isImmutable() || !KAuthorized::authorize(QStringLiteral("plasma/plasmashell/unlockedDesktop"))) { setImmutability(Plasma::Types::SystemImmutable); } else { KConfigGroup coronaConfig(config(), "General"); setImmutability((Plasma::Types::ImmutabilityType)coronaConfig.readEntry("immutability", (int)Plasma::Types::Mutable)); } } void ShellCorona::primaryOutputChanged() { if (!m_desktopViewforId.contains(0)) { return; } QScreen *oldPrimary = m_desktopViewforId.value(0)->screen(); QScreen *newPrimary = qGuiApp->primaryScreen(); if (!newPrimary || newPrimary == oldPrimary) { return; } qWarning()<<"Old primary output:"<id(newPrimary->name()); //swap order in m_desktopViewforId if (m_desktopViewforId.contains(0) && m_desktopViewforId.contains(oldIdOfPrimary)) { DesktopView *primaryDesktop = m_desktopViewforId.value(0); DesktopView *oldDesktopOfPrimary = m_desktopViewforId.value(oldIdOfPrimary); primaryDesktop->setScreenToFollow(newPrimary); oldDesktopOfPrimary->setScreenToFollow(oldPrimary); primaryDesktop->show(); oldDesktopOfPrimary->show(); } m_screenPool->setPrimaryConnector(newPrimary->name()); foreach (PanelView *panel, m_panelViews) { if (panel->screen() == oldPrimary) { panel->setScreenToFollow(newPrimary); } else if (panel->screen() == newPrimary) { panel->setScreenToFollow(oldPrimary); } } CHECK_SCREEN_INVARIANTS } #ifndef NDEBUG void ShellCorona::screenInvariants() const { Q_ASSERT(m_desktopViewforId.keys().count() <= QGuiApplication::screens().count()); QSet screens; foreach (const int id, m_desktopViewforId.keys()) { const DesktopView *view = m_desktopViewforId.value(id); QScreen *screen = view->screenToFollow(); Q_ASSERT(!screens.contains(screen)); Q_ASSERT(!m_redundantOutputs.contains(screen)); // commented out because a different part of the code-base is responsible for this // and sometimes is not yet called here. // Q_ASSERT(!view->fillScreen() || view->geometry() == screen->geometry()); Q_ASSERT(view->containment()); Q_ASSERT(view->containment()->screen() == id || view->containment()->screen() == -1); Q_ASSERT(view->containment()->lastScreen() == id || view->containment()->lastScreen() == -1); Q_ASSERT(view->isVisible()); foreach (const PanelView *panel, panelsForScreen(screen)) { Q_ASSERT(panel->containment()); Q_ASSERT(panel->containment()->screen() == id || panel->containment()->screen() == -1); Q_ASSERT(panel->isVisible()); } screens.insert(screen); } foreach (QScreen* out, m_redundantOutputs) { Q_ASSERT(isOutputRedundant(out)); } if (m_desktopViewforId.isEmpty()) { qWarning() << "no screens!!"; } } #endif void ShellCorona::showAlternativesForApplet(Plasma::Applet *applet) { const QString alternativesQML = package().filePath("appletalternativesui"); if (alternativesQML.isEmpty()) { return; } KDeclarative::QmlObject *qmlObj = new KDeclarative::QmlObject(this); qmlObj->setInitializationDelayed(true); qmlObj->setSource(QUrl::fromLocalFile(alternativesQML)); AlternativesHelper *helper = new AlternativesHelper(applet, qmlObj); qmlObj->rootContext()->setContextProperty(QStringLiteral("alternativesHelper"), helper); m_alternativesObjects << qmlObj; qmlObj->completeInitialization(); connect(qmlObj->rootObject(), SIGNAL(visibleChanged(bool)), this, SLOT(alternativesVisibilityChanged(bool))); connect(applet, &Plasma::Applet::destroyedChanged, this, [this, qmlObj] (bool destroyed) { if (!destroyed) { return; } QMutableListIterator it(m_alternativesObjects); while (it.hasNext()) { KDeclarative::QmlObject *obj = it.next(); if (obj == qmlObj) { it.remove(); obj->deleteLater(); } } }); } void ShellCorona::alternativesVisibilityChanged(bool visible) { if (visible) { return; } QObject *root = sender(); QMutableListIterator it(m_alternativesObjects); while (it.hasNext()) { KDeclarative::QmlObject *obj = it.next(); if (obj->rootObject() == root) { it.remove(); obj->deleteLater(); } } } void ShellCorona::unload() { if (m_shell.isEmpty()) { return; } - qDeleteAll(m_desktopViewforId.values()); + qDeleteAll(m_desktopViewforId); m_desktopViewforId.clear(); - qDeleteAll(m_panelViews.values()); + qDeleteAll(m_panelViews); m_panelViews.clear(); m_desktopContainments.clear(); m_waitingPanels.clear(); m_activityContainmentPlugins.clear(); while (!containments().isEmpty()) { //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona //this form doesn't crash, while qDeleteAll(containments()) does delete containments().first(); } } KSharedConfig::Ptr ShellCorona::applicationConfig() { return KSharedConfig::openConfig(); } void ShellCorona::requestApplicationConfigSync() { m_appConfigSyncTimer.start(); } void ShellCorona::loadDefaultLayout() { //NOTE: Is important the containments already exist for each screen // at the moment of the script execution,the same loop in :load() // is executed too late for (QScreen* screen : qGuiApp->screens()) { addOutput(screen); } QString script = ShellManager::s_testModeLayout; if (script.isEmpty()) { script = m_lookAndFeelPackage.filePath("layouts", QString(shell() + "-layout.js").toLatin1()); } if (script.isEmpty()) { script = package().filePath("defaultlayout"); } QFile file(script); if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { QString code = file.readAll(); qDebug() << "evaluating startup script:" << script; // We need to know which activities are here in order for // the scripting engine to work. activityAdded does not mind // if we pass it the same activity multiple times QStringList existingActivities = m_activityController->activities(); foreach (const QString &id, existingActivities) { activityAdded(id); } WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); if (!scriptEngine.evaluateScript(code, script)) { qWarning() << "failed to initialize layout properly:" << script; } } Q_EMIT startupCompleted(); } void ShellCorona::processUpdateScripts() { WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); foreach (const QString &script, WorkspaceScripting::ScriptEngine::pendingUpdateScripts(this)) { QFile file(script); if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { QString code = file.readAll(); scriptEngine.evaluateScript(code); } else { qWarning() << "Unable to open the script file" << script << "for reading"; } } } int ShellCorona::numScreens() const { return qGuiApp->screens().count(); } QRect ShellCorona::screenGeometry(int id) const { if (!m_desktopViewforId.contains(id)) { qWarning() << "requesting unexisting screen" << id; QScreen *s = qGuiApp->primaryScreen(); return s ? s->geometry() : QRect(); } return m_desktopViewforId.value(id)->geometry(); } QRegion ShellCorona::availableScreenRegion(int id) const { if (!m_desktopViewforId.contains(id)) { //each screen should have a view qWarning() << "requesting unexisting screen" << id; QScreen *s = qGuiApp->primaryScreen(); return s ? s->availableGeometry() : QRegion(); } DesktopView *view = m_desktopViewforId.value(id); QRegion r = view->geometry(); foreach (const PanelView *v, m_panelViews) { if (v->isVisible() && view->screen() == v->screen() && v->visibilityMode() != PanelView::AutoHide) { //if the panel is being moved around, we still want to calculate it from the edge r -= v->geometryByDistance(0); } } return r; } QRect ShellCorona::availableScreenRect(int id) const { if (!m_desktopViewforId.contains(id)) { //each screen should have a view qWarning() << "requesting unexisting screen" << id; QScreen *s = qGuiApp->primaryScreen(); return s ? s->availableGeometry() : QRect(); } DesktopView *view = m_desktopViewforId.value(id); QRect r = view->geometry(); foreach (PanelView *v, m_panelViews) { if (v->isVisible() && v->screen() == view->screen() && v->visibilityMode() != PanelView::AutoHide) { switch (v->location()) { case Plasma::Types::LeftEdge: r.setLeft(r.left() + v->thickness()); break; case Plasma::Types::RightEdge: r.setRight(r.right() - v->thickness()); break; case Plasma::Types::TopEdge: r.setTop(r.top() + v->thickness()); break; case Plasma::Types::BottomEdge: r.setBottom(r.bottom() - v->thickness()); default: break; } } } return r; } QStringList ShellCorona::availableActivities() const { return m_activityContainmentPlugins.keys(); } void ShellCorona::removeDesktop(DesktopView *desktopView) { const int idx = m_screenPool->id(desktopView->screenToFollow()->name()); if (!m_desktopViewforId.contains(idx)) { return; } QMutableHashIterator it(m_panelViews); while (it.hasNext()) { it.next(); PanelView *panelView = it.value(); if (panelView->containment()->screen() == idx) { m_waitingPanels << panelView->containment(); it.remove(); delete panelView; } } Q_ASSERT(m_desktopViewforId.value(idx) == desktopView); delete desktopView; m_desktopViewforId.remove(idx); } PanelView *ShellCorona::panelView(Plasma::Containment *containment) const { return m_panelViews.value(containment); } ///// SLOTS QList ShellCorona::panelsForScreen(QScreen *screen) const { QList ret; foreach (PanelView *v, m_panelViews) { if (v->screenToFollow() == screen) { ret += v; } } return ret; } DesktopView* ShellCorona::desktopForScreen(QScreen* screen) const { return m_desktopViewforId.value(m_screenPool->id(screen->name())); } void ShellCorona::screenRemoved(QScreen* screen) { if (DesktopView* v = desktopForScreen(screen)) { removeDesktop(v); } m_reconsiderOutputsTimer.start(); m_redundantOutputs.remove(screen); } bool ShellCorona::isOutputRedundant(QScreen* screen) const { Q_ASSERT(screen); const QRect geometry = screen->geometry(); //FIXME: QScreen doesn't have any idea of "this qscreen is clone of this other one //so this ultra inefficient heuristic has to stay until we have a slightly better api foreach (QScreen* s, qGuiApp->screens()) { if (screen == s) { continue; } const QRect sGeometry = s->geometry(); if (sGeometry.contains(geometry, false) && sGeometry.width() > geometry.width() && sGeometry.height() > geometry.height()) { return true; } } return false; } void ShellCorona::reconsiderOutputs() { foreach (QScreen* screen, qGuiApp->screens()) { if (m_redundantOutputs.contains(screen)) { if (!isOutputRedundant(screen)) { // qDebug() << "not redundant anymore" << out; addOutput(screen); } } else if (isOutputRedundant(screen)) { qDebug() << "new redundant screen" << screen; if (DesktopView* v = desktopForScreen(screen)) removeDesktop(v); m_redundantOutputs.insert(screen); } // else // qDebug() << "fine screen" << out; } updateStruts(); CHECK_SCREEN_INVARIANTS } void ShellCorona::addOutput(QScreen* screen) { Q_ASSERT(screen); connect(screen, &QScreen::geometryChanged, &m_reconsiderOutputsTimer, static_cast(&QTimer::start), Qt::UniqueConnection); if (isOutputRedundant(screen)) { m_redundantOutputs.insert(screen); return; } else { m_redundantOutputs.remove(screen); } int insertPosition = m_screenPool->id(screen->name()); if (insertPosition < 0) { insertPosition = m_screenPool->firstAvailableId(); } DesktopView *view = new DesktopView(this, screen); connect(view, &QQuickWindow::sceneGraphError, this, &ShellCorona::showOpenGLNotCompatibleWarning); Plasma::Containment *containment = createContainmentForActivity(m_activityController->currentActivity(), insertPosition); Q_ASSERT(containment); QAction *removeAction = containment->actions()->action(QStringLiteral("remove")); if (removeAction) { removeAction->deleteLater(); } m_screenPool->insertScreenMapping(insertPosition, screen->name()); m_desktopViewforId[insertPosition] = view; view->setContainment(containment); view->show(); Q_ASSERT(screen == view->screen()); //need to specifically call the reactToScreenChange, since when the screen is shown it's not yet //in the list. We still don't want to have an invisible view added. containment->reactToScreenChange(); //were there any panels for this screen before it popped up? if (!m_waitingPanels.isEmpty()) { m_waitingPanelsTimer.start(); } emit availableScreenRectChanged(); CHECK_SCREEN_INVARIANTS } Plasma::Containment *ShellCorona::createContainmentForActivity(const QString& activity, int screenNum) { if (m_desktopContainments.contains(activity)) { for (Plasma::Containment *cont : m_desktopContainments.value(activity)) { if (cont->screen() == screenNum && cont->activity() == activity) { return cont; } } } QString plugin = m_activityContainmentPlugins.value(activity); if (plugin.isEmpty()) { plugin = defaultContainmentPlugin(); } Plasma::Containment *containment = containmentForScreen(screenNum, plugin, QVariantList()); Q_ASSERT(containment); if (containment) { containment->setActivity(activity); insertContainment(activity, screenNum, containment); } return containment; } void ShellCorona::createWaitingPanels() { QList stillWaitingPanels; foreach (Plasma::Containment *cont, m_waitingPanels) { //ignore non existing (yet?) screens int requestedScreen = cont->lastScreen(); if (requestedScreen < 0) { requestedScreen = 0; } if (!m_desktopViewforId.contains(requestedScreen)) { stillWaitingPanels << cont; continue; } //TODO: does a similar check make sense? //Q_ASSERT(qBound(0, requestedScreen, m_screenPool->count() - 1) == requestedScreen); QScreen *screen = m_desktopViewforId.value(requestedScreen)->screenToFollow(); PanelView* panel = new PanelView(this, screen); connect(panel, &QQuickWindow::sceneGraphError, this, &ShellCorona::showOpenGLNotCompatibleWarning); connect(panel, &QWindow::visibleChanged, this, &Plasma::Corona::availableScreenRectChanged); connect(panel, &PanelView::locationChanged, this, &Plasma::Corona::availableScreenRectChanged); connect(panel, &PanelView::visibilityModeChanged, this, &Plasma::Corona::availableScreenRectChanged); connect(panel, &PanelView::thicknessChanged, this, &Plasma::Corona::availableScreenRectChanged); m_panelViews[cont] = panel; panel->setContainment(cont); cont->reactToScreenChange(); connect(cont, &QObject::destroyed, this, &ShellCorona::panelContainmentDestroyed); } m_waitingPanels = stillWaitingPanels; emit availableScreenRectChanged(); } void ShellCorona::panelContainmentDestroyed(QObject *cont) { auto view = m_panelViews.take(static_cast(cont)); view->deleteLater(); emit availableScreenRectChanged(); } void ShellCorona::handleContainmentAdded(Plasma::Containment *c) { connect(c, &Plasma::Containment::showAddWidgetsInterface, this, &ShellCorona::toggleWidgetExplorer); // Why queued? this is usually triggered after a context menu closes // due to its sync,modal nature it may eat some mouse event from the scene // waiting a bit to create a new window, the dialog seems to reliably // avoid the eating of one click in the panel after the context menu is gone connect(c, &Plasma::Containment::appletAlternativesRequested, this, &ShellCorona::showAlternativesForApplet, Qt::QueuedConnection); connect(c, &Plasma::Containment::appletCreated, this, [this, c] (Plasma::Applet *applet) { executeSetupPlasmoidScript(c, applet); }); } void ShellCorona::executeSetupPlasmoidScript(Plasma::Containment *containment, Plasma::Applet *applet) { if (!applet->pluginInfo().isValid() || !containment->pluginInfo().isValid()) { return; } const QString scriptFile = m_lookAndFeelPackage.filePath("plasmoidsetupscripts", applet->pluginInfo().pluginName() + ".js"); if (scriptFile.isEmpty()) { return; } WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); QFile file(scriptFile); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << i18n("Unable to load script file: %1", scriptFile); return; } QString script = file.readAll(); if (script.isEmpty()) { // qDebug() << "script is empty"; return; } scriptEngine.globalObject().setProperty(QStringLiteral("applet"), scriptEngine.wrap(applet)); scriptEngine.globalObject().setProperty(QStringLiteral("containment"), scriptEngine.wrap(containment)); scriptEngine.evaluateScript(script, scriptFile); } void ShellCorona::toggleWidgetExplorer() { const QPoint cursorPos = QCursor::pos(); - foreach (DesktopView *view, m_desktopViewforId.values()) { + foreach (DesktopView *view, m_desktopViewforId) { if (view->screen()->geometry().contains(cursorPos)) { //The view QML has to provide something to display the widget explorer view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleWidgetExplorer", Q_ARG(QVariant, QVariant::fromValue(sender()))); return; } } } void ShellCorona::toggleActivityManager() { const QPoint cursorPos = QCursor::pos(); - foreach (DesktopView *view, m_desktopViewforId.values()) { + foreach (DesktopView *view, m_desktopViewforId) { if (view->screen()->geometry().contains(cursorPos)) { //The view QML has to provide something to display the activity explorer view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleActivityManager", Qt::QueuedConnection); return; } } } void ShellCorona::syncAppConfig() { applicationConfig()->sync(); } void ShellCorona::setDashboardShown(bool show) { KWindowSystem::setShowingDesktop(show); } void ShellCorona::toggleDashboard() { setDashboardShown(!KWindowSystem::showingDesktop()); } void ShellCorona::loadInteractiveConsole() { if (KSharedConfig::openConfig()->isImmutable() || !KAuthorized::authorize(QStringLiteral("plasma-desktop/scripting_console"))) { delete m_interactiveConsole; m_interactiveConsole = 0; return; } if (!m_interactiveConsole) { const QString consoleQML = package().filePath("interactiveconsole"); if (consoleQML.isEmpty()) { return; } m_interactiveConsole = new KDeclarative::QmlObject(this); m_interactiveConsole->setInitializationDelayed(true); m_interactiveConsole->setSource(QUrl::fromLocalFile(consoleQML)); QObject *engine = new WorkspaceScripting::ScriptEngine(this, m_interactiveConsole); m_interactiveConsole->rootContext()->setContextProperty(QStringLiteral("scriptEngine"), engine); m_interactiveConsole->completeInitialization(); if (m_interactiveConsole->rootObject()) { connect(m_interactiveConsole->rootObject(), SIGNAL(visibleChanged(bool)), this, SLOT(interactiveConsoleVisibilityChanged(bool))); } } } void ShellCorona::showInteractiveConsole() { loadInteractiveConsole(); if (m_interactiveConsole && m_interactiveConsole->rootObject()) { m_interactiveConsole->rootObject()->setProperty("mode", "desktop"); m_interactiveConsole->rootObject()->setProperty("visible", true); } } void ShellCorona::loadScriptInInteractiveConsole(const QString &script) { showInteractiveConsole(); if (m_interactiveConsole) { m_interactiveConsole->rootObject()->setProperty("script", script); } } void ShellCorona::showInteractiveKWinConsole() { loadInteractiveConsole(); if (m_interactiveConsole && m_interactiveConsole->rootObject()) { m_interactiveConsole->rootObject()->setProperty("mode", "windowmanager"); m_interactiveConsole->rootObject()->setProperty("visible", true); } } void ShellCorona::loadKWinScriptInInteractiveConsole(const QString &script) { showInteractiveKWinConsole(); if (m_interactiveConsole) { m_interactiveConsole->rootObject()->setProperty("script", script); } } void ShellCorona::evaluateScript(const QString &script) { if (immutability() != Plasma::Types::Mutable) { if (calledFromDBus()) { sendErrorReply(QDBusError::Failed, QStringLiteral("Widgets are locked")); } return; } WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); scriptEngine.evaluateScript(script); if (scriptEngine.hasUncaughtException() && calledFromDBus()) { sendErrorReply(QDBusError::Failed, scriptEngine.uncaughtException().toString()); } } void ShellCorona::interactiveConsoleVisibilityChanged(bool visible) { if (!visible) { m_interactiveConsole->deleteLater(); m_interactiveConsole = nullptr; } } void ShellCorona::checkActivities() { KActivities::Controller::ServiceStatus status = m_activityController->serviceStatus(); //qDebug() << "$%$%$#%$%$%Status:" << status; if (status != KActivities::Controller::Running) { //panic and give up - better than causing a mess qDebug() << "ShellCorona::checkActivities is called whilst activity daemon is still connecting"; return; } QStringList existingActivities = m_activityController->activities(); foreach (const QString &id, existingActivities) { activityAdded(id); } // Checking whether the result we got is valid. Just in case. Q_ASSERT_X(!existingActivities.isEmpty(), "isEmpty", "There are no activities, and the service is running"); Q_ASSERT_X(existingActivities[0] != QStringLiteral("00000000-0000-0000-0000-000000000000"), "null uuid", "There is a nulluuid activity present"); // Killing the unassigned containments foreach (Plasma::Containment *cont, containments()) { if ((cont->containmentType() == Plasma::Types::DesktopContainment || cont->containmentType() == Plasma::Types::CustomContainment) && !existingActivities.contains(cont->activity())) { cont->destroy(); } } } void ShellCorona::currentActivityChanged(const QString &newActivity) { // qDebug() << "Activity changed:" << newActivity; foreach (int id, m_desktopViewforId.keys()) { Plasma::Containment *c = createContainmentForActivity(newActivity, id); QAction *removeAction = c->actions()->action(QStringLiteral("remove")); if (removeAction) { removeAction->deleteLater(); } m_desktopViewforId.value(id)->setContainment(c); } } void ShellCorona::activityAdded(const QString &id) { //TODO more sanity checks if (m_activityContainmentPlugins.contains(id)) { qWarning() << "Activity added twice" << id; return; } m_activityContainmentPlugins.insert(id, defaultContainmentPlugin()); } void ShellCorona::activityRemoved(const QString &id) { m_activityContainmentPlugins.remove(id); } void ShellCorona::insertActivity(const QString &id, const QString &plugin) { activityAdded(id); const QString currentActivityReally = m_activityController->currentActivity(); // TODO: This needs to go away! // The containment creation API does not know when we have a // new activity to create a containment for, we need to pretend // that the current activity has been changed QFuture currentActivity = m_activityController->setCurrentActivity(id); awaitFuture(currentActivity); if (!currentActivity.result()) { qDebug() << "Failed to create and switch to the activity"; return; } while (m_activityController->currentActivity() != id) { QCoreApplication::processEvents(); } m_activityContainmentPlugins.insert(id, plugin); foreach (int screenId, m_desktopViewforId.keys()) { Plasma::Containment *c = createContainmentForActivity(id, screenId); if (c) { c->config().writeEntry("lastScreen", screenId); } } } Plasma::Containment *ShellCorona::setContainmentTypeForScreen(int screen, const QString &plugin) { Plasma::Containment *oldContainment = containmentForScreen(screen); //no valid containment in given screen, giving up if (!oldContainment) { return 0; } if (plugin.isEmpty()) { return oldContainment; } DesktopView *view = 0; - foreach (DesktopView *v, m_desktopViewforId.values()) { + foreach (DesktopView *v, m_desktopViewforId) { if (v->containment() == oldContainment) { view = v; break; } } //no view? give up if (!view) { return oldContainment; } //create a new containment Plasma::Containment *newContainment = createContainmentDelayed(plugin); //if creation failed or invalid plugin, give up if (!newContainment) { return oldContainment; } else if (!newContainment->pluginInfo().isValid()) { newContainment->deleteLater(); return oldContainment; } newContainment->setWallpaper(oldContainment->wallpaper()); //At this point we have a valid new containment from plugin and a view //copy all configuration groups (excluded applets) KConfigGroup oldCg = oldContainment->config(); //newCg *HAS* to be from a KSharedConfig, because some KConfigSkeleton will need to be synced //this makes the configscheme work KConfigGroup newCg(KSharedConfig::openConfig(oldCg.config()->name()), "Containments"); newCg = KConfigGroup(&newCg, QString::number(newContainment->id())); //this makes containment->config() work, is a separate thing from its configscheme KConfigGroup newCg2 = newContainment->config(); foreach (const QString &group, oldCg.groupList()) { if (group != QLatin1String("Applets")) { KConfigGroup subGroup(&oldCg, group); KConfigGroup newSubGroup(&newCg, group); subGroup.copyTo(&newSubGroup); KConfigGroup newSubGroup2(&newCg2, group); subGroup.copyTo(&newSubGroup2); } } newContainment->init(); newCg.writeEntry("activityId", oldContainment->activity()); newContainment->restore(newCg); newContainment->updateConstraints(Plasma::Types::StartupCompletedConstraint); newContainment->save(newCg); requestConfigSync(); newContainment->flushPendingConstraintsEvents(); emit containmentAdded(newContainment); //Move the applets foreach (Plasma::Applet *applet, oldContainment->applets()) { newContainment->addApplet(applet); } //remove the "remove" action QAction *removeAction = newContainment->actions()->action(QStringLiteral("remove")); if (removeAction) { removeAction->deleteLater(); } view->setContainment(newContainment); newContainment->setActivity(oldContainment->activity()); m_desktopContainments.remove(oldContainment->activity()); insertContainment(oldContainment->activity(), screen, newContainment); //removing the focus from the item that is going to be destroyed //fixes a crash //delayout the destruction of the old containment fixes another crash view->rootObject()->setFocus(true, Qt::MouseFocusReason); QTimer::singleShot(2500, oldContainment, &Plasma::Applet::destroy); emit availableScreenRectChanged(); return newContainment; } void ShellCorona::checkAddPanelAction(const QStringList &sycocaChanges) { if (!sycocaChanges.isEmpty() && !sycocaChanges.contains(QStringLiteral("services"))) { return; } delete m_addPanelAction; m_addPanelAction = 0; delete m_addPanelsMenu; m_addPanelsMenu = 0; KPluginInfo::List panelContainmentPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); auto filter = [](const KPluginMetaData &md) -> bool { return md.value(QStringLiteral("NoDisplay")) != QStringLiteral("true") && md.value(QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); }; QList templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); if (panelContainmentPlugins.count() + templates.count() == 1) { m_addPanelAction = new QAction(i18n("Add Panel"), this); m_addPanelAction->setData(Plasma::Types::AddAction); connect(m_addPanelAction, SIGNAL(triggered(bool)), this, SLOT(addPanel())); } else if (!panelContainmentPlugins.isEmpty()) { m_addPanelsMenu = new QMenu; m_addPanelAction = m_addPanelsMenu->menuAction(); m_addPanelAction->setText(i18n("Add Panel")); m_addPanelAction->setData(Plasma::Types::AddAction); connect(m_addPanelsMenu, &QMenu::aboutToShow, this, &ShellCorona::populateAddPanelsMenu); connect(m_addPanelsMenu, SIGNAL(triggered(QAction*)), this, SLOT(addPanel(QAction*))); } if (m_addPanelAction) { m_addPanelAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); actions()->addAction(QStringLiteral("add panel"), m_addPanelAction); } } void ShellCorona::populateAddPanelsMenu() { m_addPanelsMenu->clear(); const KPluginInfo emptyInfo; KPluginInfo::List panelContainmentPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); QMap > sorted; foreach (const KPluginInfo &plugin, panelContainmentPlugins) { if (plugin.property(QStringLiteral("NoDisplay")).toString() == QStringLiteral("true")) { continue; } sorted.insert(plugin.name(), qMakePair(plugin, KPluginMetaData())); } auto filter = [](const KPluginMetaData &md) -> bool { return md.value(QStringLiteral("NoDisplay")) != QStringLiteral("true") && md.value(QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); }; - QList templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); + const QList templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); for (auto tpl : templates) { sorted.insert(tpl.name(), qMakePair(emptyInfo, tpl)); } QMapIterator > it(sorted); KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); while (it.hasNext()) { it.next(); QPair pair = it.value(); if (pair.first.isValid()) { KPluginInfo plugin = pair.first; QAction *action = m_addPanelsMenu->addAction(i18n("Empty %1", plugin.name())); if (!plugin.icon().isEmpty()) { action->setIcon(QIcon::fromTheme(plugin.icon())); } action->setData(plugin.pluginName()); } else { KPluginInfo info(pair.second); package.setPath(info.pluginName()); const QString scriptFile = package.filePath("mainscript"); if (!scriptFile.isEmpty()) { QAction *action = m_addPanelsMenu->addAction(info.name()); action->setData(QStringLiteral("plasma-desktop-template:%1").arg(info.pluginName())); } } } } void ShellCorona::addPanel() { KPluginInfo::List panelPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); if (!panelPlugins.isEmpty()) { addPanel(panelPlugins.first().pluginName()); } } void ShellCorona::addPanel(QAction *action) { const QString plugin = action->data().toString(); if (plugin.startsWith(QLatin1String("plasma-desktop-template:"))) { WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); const QString templateName = plugin.right(plugin.length() - qstrlen("plasma-desktop-template:")); scriptEngine.evaluateScript(QStringLiteral("loadTemplate(\"%1\")").arg(templateName)); } else if (!plugin.isEmpty()) { addPanel(plugin); } } Plasma::Containment *ShellCorona::addPanel(const QString &plugin) { Plasma::Containment *panel = createContainment(plugin); if (!panel) { return 0; } QList availableLocations; availableLocations << Plasma::Types::LeftEdge << Plasma::Types::TopEdge << Plasma::Types::RightEdge << Plasma::Types::BottomEdge; foreach (const Plasma::Containment *cont, m_panelViews.keys()) { availableLocations.removeAll(cont->location()); } Plasma::Types::Location loc; if (availableLocations.isEmpty()) { loc = Plasma::Types::TopEdge; } else { loc = availableLocations.first(); } panel->setLocation(loc); switch (loc) { case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: panel->setFormFactor(Plasma::Types::Vertical); break; default: panel->setFormFactor(Plasma::Types::Horizontal); break; } Q_ASSERT(panel); m_waitingPanels << panel; //not creating the panel view yet in order to have the same code path //between the first and subsequent plasma starts. we want to have the panel appearing only when its layout is completed, to not have //many visible relayouts. otherwise we had even panel resizes at startup that //made al lthe full representations be loaded. m_waitingPanelsTimer.start(); const QPoint cursorPos(QCursor::pos()); foreach (QScreen *screen, QGuiApplication::screens()) { //m_panelViews.contains(panel) == false iff addPanel is executed in a startup script if (screen->geometry().contains(cursorPos) && m_panelViews.contains(panel)) { m_panelViews[panel]->setScreenToFollow(screen); break; } } return panel; } int ShellCorona::screenForContainment(const Plasma::Containment *containment) const { //case in which this containment is child of an applet, hello systray :) if (Plasma::Applet *parentApplet = qobject_cast(containment->parent())) { if (Plasma::Containment* cont = parentApplet->containment()) { return screenForContainment(cont); } else { return -1; } } //if the desktop views already exist, base the decision upon them foreach (int id, m_desktopViewforId.keys()) { if (m_desktopViewforId.value(id)->containment() == containment && containment->activity() == m_activityController->currentActivity()) { return id; } } //if the panel views already exist, base upon them PanelView *view = m_panelViews.value(containment); if (view) { return m_screenPool->id(view->screenToFollow()->name()); } //Failed? fallback on lastScreen() //lastScreen() is the correct screen for panels //It is also correct for desktops *that have the correct activity()* //a containment with lastScreen() == 0 but another activity, //won't be associated to a screen // qDebug() << "ShellCorona screenForContainment: " << containment << " Last screen is " << containment->lastScreen(); for (int i = 0, count = qGuiApp->screens().count(); ilastScreen() == i && (containment->activity() == m_activityController->currentActivity() || containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment)) { return i; } } return -1; } void ShellCorona::nextActivity() { const QStringList list = m_activityController->activities(KActivities::Info::Running); if (list.isEmpty()) { return; } const int start = list.indexOf(m_activityController->currentActivity()); const int i = (start + 1) % list.size(); m_activityController->setCurrentActivity(list.at(i)); } void ShellCorona::previousActivity() { const QStringList list = m_activityController->activities(KActivities::Info::Running); if (list.isEmpty()) { return; } const int start = list.indexOf(m_activityController->currentActivity()); int i = start - 1; if(i < 0) { i = list.size() - 1; } m_activityController->setCurrentActivity(list.at(i)); } void ShellCorona::stopCurrentActivity() { const QStringList list = m_activityController->activities(KActivities::Info::Running); if (list.isEmpty()) { return; } m_activityController->stopActivity(m_activityController->currentActivity()); } void ShellCorona::insertContainment(const QString &activity, int screenNum, Plasma::Containment *containment) { Plasma::Containment *cont = nullptr; for (Plasma::Containment *c : m_desktopContainments.value(activity)) { if (c->screen() == screenNum) { cont = c; if (containment == cont) { return; } break; } } Q_ASSERT(!m_desktopContainments.value(activity).values().contains(containment)); if (cont) { disconnect(cont, SIGNAL(destroyed(QObject*)), this, SLOT(desktopContainmentDestroyed(QObject*))); cont->destroy(); } m_desktopContainments[activity].insert(containment); //when a containment gets deleted update our map of containments connect(containment, SIGNAL(destroyed(QObject*)), this, SLOT(desktopContainmentDestroyed(QObject*))); } void ShellCorona::desktopContainmentDestroyed(QObject *obj) { // when QObject::destroyed arrives, ~Plasma::Containment has run, // members of Containment are not accessible anymore, // so keep ugly bookeeping by hand auto containment = static_cast(obj); for (auto a : m_desktopContainments) { QMutableSetIterator it(a); while (it.hasNext()) { it.next(); if (it.value() == containment) { it.remove(); return; } } } } void ShellCorona::showOpenGLNotCompatibleWarning() { static bool s_multipleInvokations = false; if (s_multipleInvokations) { return; } s_multipleInvokations = true; QCoreApplication::setAttribute(Qt::AA_ForceRasterWidgets); QMessageBox::critical(nullptr, i18n("Plasma Failed To Start"), i18n("Plasma is unable to start as it could not correctly use OpenGL 2.\n Please check that your graphic drivers are set up correctly.")); qCritical("Open GL context could not be created"); // this doesn't work and I have no idea why. QCoreApplication::exit(1); } void ShellCorona::setupWaylandIntegration() { if (!KWindowSystem::isPlatformWayland()) { return; } using namespace KWayland::Client; ConnectionThread *connection = ConnectionThread::fromApplication(this); if (!connection) { return; } Registry *registry = new Registry(this); registry->create(connection); connect(registry, &Registry::plasmaShellAnnounced, this, [this, registry] (quint32 name, quint32 version) { m_waylandPlasmaShell = registry->createPlasmaShell(name, version, this); } ); registry->setup(); } KWayland::Client::PlasmaShell *ShellCorona::waylandPlasmaShellInterface() const { return m_waylandPlasmaShell; } ScreenPool *ShellCorona::screenPool() const { return m_screenPool; } QList ShellCorona::screenIds() const { return m_desktopViewforId.keys(); } QString ShellCorona::defaultContainmentPlugin() const { QString plugin = m_lnfDefaultsConfig.readEntry("Containment", QString()); if (plugin.isEmpty()) { plugin = m_desktopDefaultsConfig.readEntry("Containment", "org.kde.desktopcontainment"); } return plugin; } void ShellCorona::updateStruts() { foreach(PanelView* view, m_panelViews) { view->updateStruts(); } } void ShellCorona::activateLauncherMenu() { for (auto it = m_panelViews.constBegin(), end = m_panelViews.constEnd(); it != end; ++it) { const auto applets = it.key()->applets(); for (auto applet : applets) { if (applet->pluginInfo().property("X-Plasma-Provides").toStringList().contains(QStringLiteral("org.kde.plasma.launchermenu"))) { if (!applet->globalShortcut().isEmpty()) { emit applet->activated(); return; } } } } } // Desktop corona handler #include "moc_shellcorona.cpp" diff --git a/wallpapers/image/backgroundlistmodel.cpp b/wallpapers/image/backgroundlistmodel.cpp index 8130b1c2..c85aefc0 100644 --- a/wallpapers/image/backgroundlistmodel.cpp +++ b/wallpapers/image/backgroundlistmodel.cpp @@ -1,588 +1,588 @@ /*************************************************************************** * Copyright 2007 Paolo Capriotti * * * * 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 . * ***************************************************************************/ #ifndef BACKGROUNDLISTMODEL_CPP #define BACKGROUNDLISTMODEL_CPP #include "backgroundlistmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "image.h" QStringList BackgroundFinder::m_suffixes; ImageSizeFinder::ImageSizeFinder(const QString &path, QObject *parent) : QObject(parent), m_path(path) { } void ImageSizeFinder::run() { QImage image(m_path); Q_EMIT sizeFound(m_path, image.size()); } BackgroundListModel::BackgroundListModel(Image *wallpaper, QObject *parent) : QAbstractListModel(parent), m_wallpaper(wallpaper) { connect(&m_dirwatch, &KDirWatch::deleted, this, &BackgroundListModel::removeBackground); //TODO: on Qt 4.4 use the ui scale factor QFontMetrics fm(QGuiApplication::font()); m_screenshotSize = fm.width('M') * 15; m_imageCache = new KImageCache(QStringLiteral("plasma_wallpaper_preview"), 10485760); } BackgroundListModel::~BackgroundListModel() { delete m_imageCache; } QHash BackgroundListModel::BackgroundListModel::roleNames() const { return { { Qt::DisplayRole, "display" }, { Qt::DecorationRole, "decoration" }, { AuthorRole, "author" }, { ScreenshotRole, "screenshot" }, { ResolutionRole, "resolution" }, { PathRole, "path" }, { PackageNameRole, "packageName" }, { RemovableRole, "removable" }, { PendingDeletionRole, "pendingDeletion" }, }; } void BackgroundListModel::removeBackground(const QString &path) { QModelIndex index; while ((index = indexOf(path)).isValid()) { beginRemoveRows(QModelIndex(), index.row(), index.row()); m_packages.removeAt(index.row()); endRemoveRows(); emit countChanged(); } } void BackgroundListModel::reload() { reload(QStringList()); } void BackgroundListModel::reload(const QStringList &selected) { if (!m_packages.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_packages.count() - 1); m_packages.clear(); endRemoveRows(); emit countChanged(); } if (!m_wallpaper) { return; } if (!selected.isEmpty()) { qDebug() << "selected" << selected; processPaths(selected); } const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("wallpapers/"), QStandardPaths::LocateDirectory); qDebug() << " WP : -------" << dirs; BackgroundFinder *finder = new BackgroundFinder(m_wallpaper.data(), dirs); connect(finder, &BackgroundFinder::backgroundsFound, this, &BackgroundListModel::backgroundsFound); m_findToken = finder->token(); finder->start(); m_removableWallpapers = QSet::fromList(selected); } void BackgroundListModel::backgroundsFound(const QStringList &paths, const QString &token) { if (token == m_findToken) { processPaths(paths); } } void BackgroundListModel::processPaths(const QStringList &paths) { if (!m_wallpaper) { return; } QList newPackages; Q_FOREACH (QString file, paths) { // check if the path is a symlink and if it is, // work with the target rather than the symlink QFileInfo info(file); if (info.isSymLink()) { file = info.symLinkTarget(); } // now check if the path contains "contents" part // which could indicate that the file is part of some other // package (could have been symlinked) and we should work // with the package (which can already be present) rather // than just one file from it int contentsIndex = file.indexOf(QStringLiteral("contents")); // FIXME: additionally check for metadata.desktop being present // which would confirm a package but might be slowing things if (contentsIndex != -1) { file.truncate(contentsIndex); } // so now we have a path to a package, check if we're not // processing the same path twice (this is different from // the "!contains(file)" call lower down, that one checks paths // already in the model and does not include the paths // that are being checked in here); we want to check for duplicates // if and only if we actually changed the path (so the conditions from above // are reused here as that means we did change the path) if ((info.isSymLink() || contentsIndex != -1) && paths.contains(file)) { continue; } if (!contains(file) && QFile::exists(file)) { KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); package.setPath(file); if (package.isValid()) { m_wallpaper->findPreferedImageInPackage(package); newPackages << package; } } } // add new files to dirwatch - Q_FOREACH (KPackage::Package b, newPackages) { + Q_FOREACH (const KPackage::Package &b, newPackages) { if (!m_dirwatch.contains(b.path())) { m_dirwatch.addFile(b.path()); } } if (!newPackages.isEmpty()) { const int start = rowCount(); beginInsertRows(QModelIndex(), start, start + newPackages.size() - 1); m_packages.append(newPackages); endInsertRows(); emit countChanged(); } //qDebug() << t.elapsed(); } void BackgroundListModel::addBackground(const QString& path) { if (!m_wallpaper || !contains(path)) { if (!m_dirwatch.contains(path)) { m_dirwatch.addFile(path); } beginInsertRows(QModelIndex(), 0, 0); KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); m_removableWallpapers.insert(path); package.setPath(path); m_wallpaper->findPreferedImageInPackage(package); qDebug() << "WP Bckground added " << path << package.isValid(); m_packages.prepend(package); endInsertRows(); emit countChanged(); } } QModelIndex BackgroundListModel::indexOf(const QString &path) const { for (int i = 0; i < m_packages.size(); i++) { // packages will end with a '/', but the path passed in may not QString package = m_packages[i].path(); if (package.at(package.length() - 1) == QChar::fromLatin1('/')) { package.truncate(package.length() - 1); } if (path.startsWith(package)) { // FIXME: ugly hack to make a difference between local files in the same dir // package->path does not contain the actual file name qDebug() << "WP prefix" << m_packages[i].contentsPrefixPaths() << m_packages[i].filePath("preferred") << package << path; QStringList ps = m_packages[i].contentsPrefixPaths(); bool prefixempty = ps.count() == 0; if (!prefixempty) { prefixempty = ps[0].isEmpty(); } //For local files (user wallpapers) path == m_packages[i].filePath("preferred") //E.X. path = "/home/kde/next.png" //m_packages[i].filePath("preferred") = "/home/kde/next.png" // //But for the system wallpapers this is not the case. path != m_packages[i].filePath("preferred") //E.X. path = /usr/share/wallpapers/Next/" //m_packages[i].filePath("preferred") = "/usr/share/wallpapers/Next/contents/images/1920x1080.png" if ((path == m_packages[i].filePath("preferred")) || m_packages[i].filePath("preferred").contains(path)) { qDebug() << "WP TRUE" << (!m_packages[i].contentsPrefixPaths().isEmpty()) << (path == m_packages[i].filePath("preferred")); return index(i, 0); } } } return QModelIndex(); } bool BackgroundListModel::contains(const QString &path) const { //qDebug() << "WP contains: " << path << indexOf(path).isValid(); return indexOf(path).isValid(); } int BackgroundListModel::rowCount(const QModelIndex &) const { return m_packages.size(); } QSize BackgroundListModel::bestSize(const KPackage::Package &package) const { if (m_sizeCache.contains(package.path())) { return m_sizeCache.value(package.path()); } const QString image = package.filePath("preferred"); if (image.isEmpty()) { return QSize(); } ImageSizeFinder *finder = new ImageSizeFinder(image); connect(finder, &ImageSizeFinder::sizeFound, this, &BackgroundListModel::sizeFound); QThreadPool::globalInstance()->start(finder); QSize size(-1, -1); const_cast(this)->m_sizeCache.insert(package.path(), size); return size; } void BackgroundListModel::sizeFound(const QString &path, const QSize &s) { if (!m_wallpaper) { return; } QModelIndex index = indexOf(path); if (index.isValid()) { KPackage::Package package = m_packages.at(index.row()); m_sizeCache.insert(package.path(), s); emit dataChanged(index, index); } } QVariant BackgroundListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= m_packages.size()) { return QVariant(); } KPackage::Package b = package(index.row()); if (!b.isValid()) { return QVariant(); } switch (role) { case Qt::DisplayRole: { QString title = b.metadata().isValid() ? b.metadata().name() : QString(); if (title.isEmpty()) { return QFileInfo(b.filePath("preferred")).completeBaseName(); } return title; } break; case ScreenshotRole: { QPixmap preview = QPixmap(QSize(m_screenshotSize*1.6, m_screenshotSize)); if (m_imageCache->findPixmap(b.filePath("preferred"), &preview)) { return preview; } // qDebug() << "WP preferred: " << b.filePath("preferred"); // qDebug() << "WP screenshot: " << b.filePath("screenshot"); QUrl file = QUrl::fromLocalFile(b.filePath("preferred")); if (!m_previewJobs.contains(file) && file.isValid()) { KFileItemList list; list.append(KFileItem(file, QString(), 0)); KIO::PreviewJob* job = KIO::filePreview(list, QSize(m_screenshotSize*1.6, m_screenshotSize)); job->setIgnoreMaximumSize(true); connect(job, &KIO::PreviewJob::gotPreview, this, &BackgroundListModel::showPreview); connect(job, &KIO::PreviewJob::failed, this, &BackgroundListModel::previewFailed); const_cast(this)->m_previewJobs.insert(file, QPersistentModelIndex(index)); } return QVariant(); } break; case AuthorRole: if (b.metadata().isValid() && !b.metadata().authors().isEmpty()) { return b.metadata().authors().first().name(); } else { return QString(); } break; case ResolutionRole:{ QSize size = bestSize(b); if (size.isValid()) { return QString::fromLatin1("%1x%2").arg(size.width()).arg(size.height()); } return QString(); } break; case PathRole: return QUrl::fromLocalFile(b.filePath("preferred")); break; case PackageNameRole: return !b.metadata().isValid() || b.metadata().pluginId().isEmpty() ? b.filePath("preferred") : b.metadata().pluginId(); break; case RemovableRole: { QString localWallpapers = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/wallpapers/"; QString path = b.filePath("preferred"); return path.startsWith(localWallpapers) || m_removableWallpapers.contains(path); } break; case PendingDeletionRole: { QUrl wallpaperUrl = QUrl::fromLocalFile(b.filePath("preferred")); return m_pendingDeletion.contains(wallpaperUrl.toLocalFile()) ? m_pendingDeletion[wallpaperUrl.toLocalFile()] : false; } break; default: return QVariant(); break; } Q_UNREACHABLE(); } bool BackgroundListModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) { return false; } if (role == PendingDeletionRole) { KPackage::Package b = package(index.row()); if (!b.isValid()) { return false; } const QUrl wallpaperUrl = QUrl::fromLocalFile(b.filePath("preferred")); m_pendingDeletion[wallpaperUrl.toLocalFile()] = value.toBool(); emit dataChanged(index, index); return true; } return false; } void BackgroundListModel::showPreview(const KFileItem &item, const QPixmap &preview) { if (!m_wallpaper) { return; } QPersistentModelIndex index = m_previewJobs.value(item.url()); m_previewJobs.remove(item.url()); if (!index.isValid()) { return; } KPackage::Package b = package(index.row()); if (!b.isValid()) { return; } m_imageCache->insertPixmap(b.filePath("preferred"), preview); //qDebug() << "WP preview size:" << preview.size(); emit dataChanged(index, index); } void BackgroundListModel::previewFailed(const KFileItem &item) { m_previewJobs.remove(item.url()); } KPackage::Package BackgroundListModel::package(int index) const { return m_packages.at(index); } void BackgroundListModel::setPendingDeletion(int rowIndex, bool pendingDeletion) { setData(index(rowIndex, 0), pendingDeletion, PendingDeletionRole); } const QStringList BackgroundListModel::wallpapersAwaitingDeletion() { QStringList candidates; - for (KPackage::Package b : m_packages) { + for (const KPackage::Package &b : m_packages) { const QUrl wallpaperUrl = QUrl::fromLocalFile(b.filePath("preferred")); if (m_pendingDeletion.contains(wallpaperUrl.toLocalFile()) && m_pendingDeletion[wallpaperUrl.toLocalFile()]) { candidates << wallpaperUrl.toLocalFile(); } } return candidates; } BackgroundFinder::BackgroundFinder(Image *wallpaper, const QStringList &paths) : QThread(wallpaper), m_paths(paths), m_token(QUuid().toString()) { } BackgroundFinder::~BackgroundFinder() { wait(); } QString BackgroundFinder::token() const { return m_token; } const QStringList &BackgroundFinder::suffixes() { if (m_suffixes.isEmpty()) { QSet suffixes; QMimeDatabase db; Q_FOREACH (const QByteArray &mimeType, QImageReader::supportedMimeTypes()) { QMimeType mime(db.mimeTypeForName(mimeType)); Q_FOREACH (const QString &pattern, mime.globPatterns()) { suffixes.insert(pattern); } } m_suffixes = suffixes.toList(); } return m_suffixes; } bool BackgroundFinder::isAcceptableSuffix(const QString &suffix) { // Despite its name, suffixes() returns a list of glob patterns. // Therefore the file suffix check needs to include the "*." prefix. const QStringList &globPatterns = suffixes(); return globPatterns.contains("*."+suffix.toLower()); } void BackgroundFinder::run() { QTime t; t.start(); QStringList papersFound; QDir dir; dir.setFilter(QDir::AllDirs | QDir::Files | QDir::Hidden | QDir::Readable); dir.setNameFilters(suffixes()); KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); int i; for (i = 0; i < m_paths.count(); ++i) { const QString path = m_paths.at(i); dir.setPath(path); const QFileInfoList files = dir.entryInfoList(); Q_FOREACH (const QFileInfo &wp, files) { if (wp.isDir()) { //qDebug() << "scanning directory" << wp.fileName(); const QString name = wp.fileName(); if (name == QString::fromLatin1(".") || name == QString::fromLatin1("..")) { // do nothing continue; } const QString filePath = wp.filePath(); if (QFile::exists(filePath + QString::fromLatin1("/metadata.desktop"))) { package.setPath(filePath); if (package.isValid()) { if (!package.filePath("images").isEmpty()) { papersFound << package.path(); } //qDebug() << "adding package" << wp.filePath(); continue; } } // add this to the directories we should be looking at m_paths.append(filePath); } else { //qDebug() << "adding image file" << wp.filePath(); papersFound << wp.filePath(); } } } //qDebug() << "WP background found!" << papersFound.size() << "in" << i << "dirs, taking" << t.elapsed() << "ms"; Q_EMIT backgroundsFound(papersFound, m_token); deleteLater(); } #endif // BACKGROUNDLISTMODEL_CPP diff --git a/wallpapers/image/image.cpp b/wallpapers/image/image.cpp index ea667338..0ec62bb2 100644 --- a/wallpapers/image/image.cpp +++ b/wallpapers/image/image.cpp @@ -1,848 +1,848 @@ /*************************************************************************** * Copyright 2007 Paolo Capriotti * * Copyright 2007 Aaron Seigo * * Copyright 2008 Petri Damsten * * Copyright 2008 Alexis Ménard * * Copyright 2014 Sebastian Kügler * * Copyright 2015 Kai Uwe Broulik * * * * 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 "image.h" #include #include // FLT_MAX #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backgroundlistmodel.h" #include Image::Image(QObject *parent) : QObject(parent), m_ready(false), m_delay(10), m_dirWatch(new KDirWatch(this)), m_mode(SingleImage), m_currentSlide(-1), m_model(0), m_dialog(0), m_width(0), m_height(0) { m_wallpaperPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); connect(&m_timer, &QTimer::timeout, this, &Image::nextSlide); connect(m_dirWatch, &KDirWatch::created, this, &Image::pathCreated); connect(m_dirWatch, &KDirWatch::dirty, this, &Image::pathDirty); connect(m_dirWatch, &KDirWatch::deleted, this, &Image::pathDeleted); m_dirWatch->startScan(); connect(this, &Image::sizeChanged, this, &Image::setTargetSize); useSingleImageDefaults(); setSingleImage(); } Image::~Image() { delete m_dialog; } void Image::classBegin() { } void Image::componentComplete() { // don't bother loading single image until all properties have settled // otherwise we would load a too small image (initial view size) just // to load the proper one afterwards etc etc m_ready = true; if (m_mode == SingleImage) { setSingleImage(); } } QString Image::photosPath() const { return QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } QUrl Image::wallpaperPath() const { return QUrl::fromLocalFile(m_wallpaperPath); } void Image::addUrl(const QString &url) { addUrl(QUrl(url), true); } void Image::addUrls(const QStringList &urls) { addUrls(urls); } Image::RenderingMode Image::renderingMode() const { return m_mode; } void Image::setRenderingMode(RenderingMode mode) { if (mode == m_mode) { return; } m_mode = mode; if (m_mode == SlideShow) { if (m_slidePaths.isEmpty()) { m_slidePaths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("share/wallpapers"), QStandardPaths::LocateDirectory); } QTimer::singleShot(200, this, &Image::startSlideshow); updateDirWatch(m_slidePaths); updateDirWatch(m_slidePaths); } else { // we need to reset the prefered image setSingleImage(); } } float distance(const QSize& size, const QSize& desired) { // compute difference of areas float delta = size.width() * size.height() - desired.width() * desired.height(); // scale down to about 1.0 delta /= ((desired.width() * desired.height())+(size.width() * size.height()))/2; // Difference of areas, slight preference to scale down return delta >= 0.0 ? delta : -delta + 2.0; } QSize resSize(const QString &str) { int index = str.indexOf('x'); if (index != -1) { return QSize(str.leftRef(index).toInt(), str.midRef(index + 1).toInt()); } return QSize(); } void Image::findPreferedImageInPackage(KPackage::Package &package) { if (!package.isValid() || !package.filePath("preferred").isEmpty()) { return; } QStringList images = package.entryList("images"); if (images.empty()) { return; } //qDebug() << "wanted" << size; // choose the nearest resolution float best = FLT_MAX; QString bestImage; foreach (const QString &entry, images) { QSize candidate = resSize(QFileInfo(entry).baseName()); if (candidate == QSize()) { continue; } double dist = distance(candidate, m_targetSize); //qDebug() << "candidate" << candidate << "distance" << dist; if (bestImage.isEmpty() || dist < best) { bestImage = entry; best = dist; //qDebug() << "best" << bestImage; if (dist == 0) { break; } } } //qDebug() << "best image" << bestImage; package.removeDefinition("preferred"); package.addFileDefinition("preferred", "images/" + bestImage, i18n("Recommended wallpaper file")); } QSize Image::targetSize() const { return m_targetSize; } void Image::setTargetSize(const QSize &size) { m_targetSize = size; if (m_mode == SingleImage) { setSingleImage(); } } int Image::height() const { return m_height; } void Image::setHeight(int h) { if (m_height != h) { m_height = h; emit sizeChanged(QSize(m_width, m_height)); } } int Image::width() const { return m_width; } void Image::setWidth(int w) { if (m_width != w) { m_width = w; emit sizeChanged(QSize(m_width, m_height)); } } KPackage::Package *Image::package() { return &m_wallpaperPackage; } void Image::useSingleImageDefaults() { Plasma::Theme theme; m_wallpaper = theme.wallpaperPath(); int index = m_wallpaper.indexOf(QString::fromLatin1("/contents/images/")); if (index > -1) { // We have file from package -> get path to package m_wallpaper = m_wallpaper.left(index); } } QAbstractItemModel* Image::wallpaperModel() { if (!m_model) { KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); m_usersWallpapers = cfg.readEntry("usersWallpapers", m_usersWallpapers); m_model = new BackgroundListModel(this, this); m_model->reload(m_usersWallpapers); } return m_model; } int Image::slideTimer() const { return m_delay; } void Image::setSlideTimer(int time) { if (time == m_delay) { return; } m_delay = time; if (m_mode == SlideShow) { updateDirWatch(m_slidePaths); startSlideshow(); } emit slideTimerChanged(); } QStringList Image::usersWallpapers() const { return m_usersWallpapers; } void Image::setUsersWallpapers(const QStringList &usersWallpapers) { if (usersWallpapers == m_usersWallpapers) { return; } m_usersWallpapers = usersWallpapers; emit usersWallpapersChanged(); } QStringList Image::slidePaths() const { return m_slidePaths; } void Image::setSlidePaths(const QStringList &slidePaths) { if (slidePaths == m_slidePaths) { return; } m_slidePaths = slidePaths; m_slidePaths.removeAll(QString()); if (m_slidePaths.isEmpty()) { m_slidePaths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("share/wallpapers"), QStandardPaths::LocateDirectory); } if (m_mode == SlideShow) { updateDirWatch(m_slidePaths); startSlideshow(); } emit slidePathsChanged(); } void Image::showAddSlidePathsDialog() { QFileDialog *dialog = new QFileDialog(0, i18n("Directory with the wallpaper to show slides from"), QLatin1String("")); dialog->setAttribute(Qt::WA_DeleteOnClose, true ); dialog->setOptions(QFileDialog::ShowDirsOnly); dialog->setAcceptMode(QFileDialog::AcceptOpen); connect(dialog, &QDialog::accepted, this, &Image::addDirFromSelectionDialog); dialog->show(); } void Image::addSlidePath(const QString &path) { if (!path.isEmpty() && !m_slidePaths.contains(path)) { m_slidePaths.append(path); if (m_mode == SlideShow) { updateDirWatch(m_slidePaths); } emit slidePathsChanged(); startSlideshow(); } } void Image::removeSlidePath(const QString &path) { if (m_slidePaths.contains(path)) { m_slidePaths.removeAll(path); if (m_mode == SlideShow) { updateDirWatch(m_slidePaths); } emit slidePathsChanged(); startSlideshow(); } } void Image::pathDirty(const QString& path) { updateDirWatch(QStringList(path)); } void Image::updateDirWatch(const QStringList &newDirs) { Q_FOREACH(const QString &oldDir, m_dirs) { if(!newDirs.contains(oldDir)) { m_dirWatch->removeDir(oldDir); } } Q_FOREACH(const QString &newDir, newDirs) { if(!m_dirWatch->contains(newDir)) { m_dirWatch->addDir(newDir, KDirWatch::WatchSubDirs | KDirWatch::WatchFiles); } } m_dirs = newDirs; } void Image::addDirFromSelectionDialog() { QFileDialog *dialog = qobject_cast(sender()); if (dialog) { addSlidePath(dialog->directoryUrl().toLocalFile()); } } void Image::syncWallpaperPackage() { m_wallpaperPackage.setPath(m_wallpaper); findPreferedImageInPackage(m_wallpaperPackage); m_wallpaperPath = m_wallpaperPackage.filePath("preferred"); } void Image::setSingleImage() { if (!m_ready) { return; } // supposedly QSize::isEmpty() is true if "either width or height are >= 0" if (!m_targetSize.width() || !m_targetSize.height()) { return; } const QString oldPath = m_wallpaperPath; if (m_wallpaper.isEmpty()) { useSingleImageDefaults(); } QString img; if (QDir::isAbsolutePath(m_wallpaper)) { syncWallpaperPackage(); if (QFile::exists(m_wallpaperPath)) { img = m_wallpaperPath; } } else { //if it's not an absolute path, check if it's just a wallpaper name const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("wallpapers/") + QString(m_wallpaper + QString::fromLatin1("/metadata.desktop"))); if (!path.isEmpty()) { QDir dir(path); dir.cdUp(); syncWallpaperPackage(); img = m_wallpaperPath; } } if (img.isEmpty()) { // ok, so the package we have failed to work out; let's try the default useSingleImageDefaults(); syncWallpaperPackage(); } if (m_wallpaperPath != oldPath) { Q_EMIT wallpaperPathChanged(); } } void Image::addUrls(const QList &urls) { bool first = true; Q_FOREACH (const QUrl &url, urls) { // set the first drop as the current paper, just add the rest to the roll addUrl(url, first); first = false; } } void Image::addUrl(const QUrl &url, bool setAsCurrent) { QString path; if (url.isLocalFile()) { path = url.toLocalFile(); } else if (url.scheme().isEmpty()) { if (QDir::isAbsolutePath(url.path())) { path = url.path(); } else { path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("wallpapers/") + url.path(), QStandardPaths::LocateDirectory); } if (path.isEmpty()) { return; } } else { QString wallpaperPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("wallpapers/") + url.path(); if (!wallpaperPath.isEmpty()) { KIO::FileCopyJob *job = KIO::file_copy(url, QUrl(wallpaperPath), -1, KIO::HideProgressInfo); if (setAsCurrent) { connect(job, &KJob::result, this, &Image::setWallpaperRetrieved); } else { connect(job, &KJob::result, this, &Image::addWallpaperRetrieved); } } return; } if (setAsCurrent) { setWallpaper(path); } else { if (m_mode != SingleImage) { // it's a slide show, add it to the slide show m_slideshowBackgrounds.append(path); m_unseenSlideshowBackgrounds.append(path); } // always add it to the user papers, though addUsersWallpaper(path); } } void Image::setWallpaperRetrieved(KJob *job) { KIO::FileCopyJob *copyJob = qobject_cast(job); if (copyJob && !copyJob->error()) { setWallpaper(copyJob->destUrl().toLocalFile()); } } void Image::addWallpaperRetrieved(KJob *job) { KIO::FileCopyJob *copyJob = qobject_cast(job); if (copyJob && !copyJob->error()) { addUrl(copyJob->destUrl(), false); } } void Image::setWallpaper(const QString &path) { if (m_mode == SingleImage) { m_wallpaper = path; setSingleImage(); } else { m_slideshowBackgrounds.append(path); m_unseenSlideshowBackgrounds.clear(); m_currentSlide = m_slideshowBackgrounds.size() - 2; nextSlide(); } //addUsersWallpaper(path); } void Image::startSlideshow() { if(m_findToken.isEmpty()) { // populate background list m_timer.stop(); m_slideshowBackgrounds.clear(); m_unseenSlideshowBackgrounds.clear(); BackgroundFinder *finder = new BackgroundFinder(this, m_dirs); m_findToken = finder->token(); connect(finder, &BackgroundFinder::backgroundsFound, this, &Image::backgroundsFound); finder->start(); //TODO: what would be cool: paint on the wallpaper itself a busy widget and perhaps some text //about loading wallpaper slideshow while the thread runs } else { m_scanDirty = true; } } void Image::backgroundsFound(const QStringList &paths, const QString &token) { if (token != m_findToken) { return; } m_findToken.clear(); if(m_scanDirty) { m_scanDirty = false; startSlideshow(); return; } m_slideshowBackgrounds = paths; m_unseenSlideshowBackgrounds.clear(); // start slideshow if (m_slideshowBackgrounds.isEmpty()) { // no image has been found, which is quite weird... try again later (this is useful for events which // are not detected by KDirWatch, like a NFS directory being mounted) QTimer::singleShot(1000, this, &Image::startSlideshow); } else { m_currentSlide = -1; nextSlide(); m_timer.start(m_delay * 1000); } } void Image::getNewWallpaper() { if (!m_newStuffDialog) { m_newStuffDialog = new KNS3::DownloadDialog( QString::fromLatin1("wallpaper.knsrc") ); KNS3::DownloadDialog *strong = m_newStuffDialog.data(); strong->setTitle(i18n("Download Wallpapers")); connect(m_newStuffDialog.data(), &QDialog::accepted, this, &Image::newStuffFinished); } m_newStuffDialog.data()->show(); } void Image::newStuffFinished() { if (m_model && (!m_newStuffDialog || m_newStuffDialog.data()->changedEntries().size() > 0)) { m_model->reload(m_usersWallpapers); } } void Image::showFileDialog() { if (!m_dialog) { QUrl baseUrl; if(m_wallpaper.indexOf(QDir::homePath()) > -1){ baseUrl = QUrl(m_wallpaper); } /* m_dialog = new KFileDialog(baseUrl, QString::fromLatin1("*.png *.jpeg *.jpg *.xcf *.svg *.svgz *.bmp"), 0); m_dialog->setOperationMode(KFileDialog::Opening); m_dialog->setInlinePreviewShown(true); m_dialog->setModal(false); connect(m_dialog, SIGNAL(okClicked()), this, SLOT(wallpaperBrowseCompleted())); connect(m_dialog, SIGNAL(destroyed(QObject*)), this, SLOT(fileDialogFinished())); */ QString path; const QStringList &locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); if (!locations.isEmpty()) { path = locations.at(0); } else { // HomeLocation is guaranteed not to be empty. path = QStandardPaths::standardLocations(QStandardPaths::HomeLocation).at(0); } QMimeDatabase db; QStringList imageGlobPatterns; foreach(const QByteArray &mimeType, QImageReader::supportedMimeTypes()) { QMimeType mime(db.mimeTypeForName(mimeType)); imageGlobPatterns << mime.globPatterns(); } m_dialog = new QFileDialog(0, i18n("Open Image"), path, i18n("Image Files") + " ("+imageGlobPatterns.join(' ') + ')'); //i18n people, this isn't a "word puzzle". there is a specific string format for QFileDialog::setNameFilters m_dialog->setFileMode(QFileDialog::ExistingFile); connect(m_dialog, &QDialog::accepted, this, &Image::wallpaperBrowseCompleted); } m_dialog->show(); m_dialog->raise(); m_dialog->activateWindow(); } void Image::fileDialogFinished() { m_dialog = 0; } void Image::wallpaperBrowseCompleted() { Q_ASSERT(m_model); if (m_dialog && m_dialog->selectedFiles().count() > 0) { addUsersWallpaper(m_dialog->selectedFiles().first()); emit customWallpaperPicked(); } } void Image::addUsersWallpaper(const QString &file) { QString f = file; f.replace(QLatin1String("file:/"), QLatin1String("")); const QFileInfo info(f); // FIXME //the full file path, so it isn't broken when dealing with symlinks const QString wallpaper = info.canonicalFilePath(); if (wallpaper.isEmpty()) { return; } if (m_model) { if (m_model->contains(wallpaper)) { return; } // add background to the model m_model->addBackground(wallpaper); } // save it KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); m_usersWallpapers = cfg.readEntry("usersWallpapers", m_usersWallpapers); if (!m_usersWallpapers.contains(wallpaper)) { m_usersWallpapers.prepend(wallpaper); cfg.writeEntry("usersWallpapers", m_usersWallpapers); cfg.sync(); emit usersWallpapersChanged(); } } void Image::nextSlide() { if (m_slideshowBackgrounds.isEmpty()) { return; } QString previousPath; if (m_currentSlide > -1 && m_currentSlide < m_unseenSlideshowBackgrounds.size()) { previousPath = m_unseenSlideshowBackgrounds.takeAt(m_currentSlide); } if (m_unseenSlideshowBackgrounds.isEmpty()) { m_unseenSlideshowBackgrounds = m_slideshowBackgrounds; // We're filling the queue again, make sure we can't pick up again // the last one picked from the previous set if (!previousPath.isEmpty()) { m_unseenSlideshowBackgrounds.removeAll(previousPath); // prevent empty list if (m_unseenSlideshowBackgrounds.isEmpty()) { m_unseenSlideshowBackgrounds = m_slideshowBackgrounds; } } } m_currentSlide = KRandom::random() % m_unseenSlideshowBackgrounds.size(); const QString currentPath = m_unseenSlideshowBackgrounds.at(m_currentSlide); m_wallpaperPackage.setPath(currentPath); findPreferedImageInPackage(m_wallpaperPackage); m_timer.stop(); m_timer.start(m_delay * 1000); QString current = m_wallpaperPackage.filePath("preferred"); if (current.isEmpty()) { m_wallpaperPath = currentPath; } else { m_wallpaperPath = current; } Q_EMIT wallpaperPathChanged(); } void Image::openSlide() { if (!m_wallpaperPackage.isValid()) { return; } // open in image viewer QUrl filepath(m_wallpaperPackage.filePath("preferred")); qDebug() << "opening file " << filepath.path(); new KRun(filepath, NULL); } void Image::pathCreated(const QString &path) { if(!m_slideshowBackgrounds.contains(path)) { QFileInfo fileInfo(path); if(fileInfo.isFile() && BackgroundFinder::isAcceptableSuffix(fileInfo.suffix())) { m_slideshowBackgrounds.append(path); m_unseenSlideshowBackgrounds.append(path); if(m_slideshowBackgrounds.count() == 1) { nextSlide(); } } } } void Image::pathDeleted(const QString &path) { if(m_slideshowBackgrounds.removeAll(path)) { m_unseenSlideshowBackgrounds.removeAll(path); if(path == m_img) { nextSlide(); } } } //FIXME: we have to save the configuration also when the dialog cancel button is clicked. void Image::removeWallpaper(QString name) { QString localWallpapers = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/wallpapers/"; QUrl nameUrl(name); //Package plugin name if (!name.contains('/')) { KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); KJob *j = p.uninstall(name, localWallpapers); connect(j, &KJob::finished, [=] () { m_model->reload(m_usersWallpapers); }); //absolute path in the home } else if (nameUrl.path().startsWith(localWallpapers)) { QFile f(nameUrl.path()); if (f.exists()) { f.remove(); } m_model->reload(m_usersWallpapers); } else { // save it KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); m_usersWallpapers = cfg.readEntry("usersWallpapers", m_usersWallpapers); int wallpaperIndex = -1; //passed as a path or as a file:// url? if (nameUrl.isValid()) { wallpaperIndex = m_usersWallpapers.indexOf(nameUrl.path()); } else { wallpaperIndex = m_usersWallpapers.indexOf(name); } if (wallpaperIndex >= 0){ m_usersWallpapers.removeAt(wallpaperIndex); m_model->reload(m_usersWallpapers); cfg.writeEntry("usersWallpapers", m_usersWallpapers); cfg.sync(); emit usersWallpapersChanged(); Q_EMIT settingsChanged(true); } } } void Image::commitDeletion() { //This is invokable from qml, so at any moment //we can't be sure the model exists if (!m_model) { return; } - for (const QString wallpaperCandidate : m_model->wallpapersAwaitingDeletion()) { + for (const QString &wallpaperCandidate : m_model->wallpapersAwaitingDeletion()) { removeWallpaper(wallpaperCandidate); } } diff --git a/wallpapers/image/image.h b/wallpapers/image/image.h index c488a844..6b9f63c9 100644 --- a/wallpapers/image/image.h +++ b/wallpapers/image/image.h @@ -1,195 +1,195 @@ /*************************************************************************** * Copyright 2007 Paolo Capriotti * * Copyright 2008 by Petri Damsten * * Copyright 2014 Sebastian Kügler * * Copyright 2015 Kai Uwe Broulik * * * * 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 . * ***************************************************************************/ #ifndef IMAGE_HEADER #define IMAGE_HEADER #include #include #include #include #include #include #include #include #include class QPropertyAnimation; class QFileDialog; class KDirWatch; class KJob; namespace KNS3 { class DownloadDialog; } class BackgroundListModel; class Image : public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(RenderingMode renderingMode READ renderingMode WRITE setRenderingMode NOTIFY renderingModeChanged) Q_PROPERTY(QUrl wallpaperPath READ wallpaperPath NOTIFY wallpaperPathChanged) Q_PROPERTY(QAbstractItemModel *wallpaperModel READ wallpaperModel CONSTANT) Q_PROPERTY(int slideTimer READ slideTimer WRITE setSlideTimer NOTIFY slideTimerChanged) Q_PROPERTY(QStringList usersWallpapers READ usersWallpapers WRITE setUsersWallpapers NOTIFY usersWallpapersChanged) Q_PROPERTY(QStringList slidePaths READ slidePaths WRITE setSlidePaths NOTIFY slidePathsChanged) Q_PROPERTY(int width MEMBER m_width READ width WRITE setWidth NOTIFY sizeChanged) Q_PROPERTY(int height MEMBER m_height READ height WRITE setHeight NOTIFY sizeChanged) Q_PROPERTY(QString photosPath READ photosPath CONSTANT) public: enum RenderingMode { SingleImage, SlideShow }; - Q_ENUMS(RenderingMode) + Q_ENUM(RenderingMode) Image(QObject* parent = 0); ~Image() override; QUrl wallpaperPath() const; //this is for QML use Q_INVOKABLE void addUrl(const QString &url); Q_INVOKABLE void addUrls(const QStringList &urls); Q_INVOKABLE void addSlidePath(const QString &path); Q_INVOKABLE void removeSlidePath(const QString &path); Q_INVOKABLE void getNewWallpaper(); Q_INVOKABLE void showFileDialog(); Q_INVOKABLE void addUsersWallpaper(const QString &file); Q_INVOKABLE void commitDeletion(); RenderingMode renderingMode() const; void setRenderingMode(RenderingMode mode); QSize targetSize() const; void setTargetSize(const QSize &size); int width() const; int height() const; void setWidth(int w); void setHeight(int h); KPackage::Package *package(); QAbstractItemModel* wallpaperModel(); int slideTimer() const; void setSlideTimer(int time); QStringList usersWallpapers() const; void setUsersWallpapers(const QStringList &usersWallpapers); QStringList slidePaths() const; void setSlidePaths(const QStringList &slidePaths); void findPreferedImageInPackage(KPackage::Package &package); void classBegin() override; void componentComplete() override; QString photosPath() const; public Q_SLOTS: void nextSlide(); void removeWallpaper(QString name); Q_SIGNALS: void settingsChanged(bool); void wallpaperPathChanged(); void renderingModeChanged(); void slideTimerChanged(); void usersWallpapersChanged(); void slidePathsChanged(); void resizeMethodChanged(); void sizeChanged(QSize s); void customWallpaperPicked(); protected Q_SLOTS: void showAddSlidePathsDialog(); void wallpaperBrowseCompleted(); /** * Open the current slide in the default image application */ void openSlide(); void startSlideshow(); void fileDialogFinished(); void addUrl(const QUrl &url, bool setAsCurrent); void addUrls(const QList &urls); void setWallpaper(const QString &path); void setWallpaperRetrieved(KJob *job); void addWallpaperRetrieved(KJob *job); void newStuffFinished(); void updateDirWatch(const QStringList &newDirs); void addDirFromSelectionDialog(); void pathCreated(const QString &path); void pathDeleted(const QString &path); void pathDirty(const QString &path); void backgroundsFound(const QStringList &paths, const QString &token); protected: void syncWallpaperPackage(); void setSingleImage(); void useSingleImageDefaults(); private: bool m_ready; int m_delay; QStringList m_dirs; QString m_wallpaper; QString m_wallpaperPath; QStringList m_usersWallpapers; KDirWatch *m_dirWatch; bool m_scanDirty; QSize m_targetSize; RenderingMode m_mode; KPackage::Package m_wallpaperPackage; QStringList m_slideshowBackgrounds; QStringList m_unseenSlideshowBackgrounds; QStringList m_slidePaths; QTimer m_timer; int m_currentSlide; BackgroundListModel *m_model; QFileDialog *m_dialog; QSize m_size; int m_width; int m_height; QString m_img; QDateTime m_previousModified; QPointer m_newStuffDialog; QString m_findToken; }; #endif