forked from Qortal/Brooklyn
477 lines
15 KiB
C++
477 lines
15 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2020 Konrad Materka <materka@gmail.com>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "systemtraymodel.h"
|
|
#include "debug.h"
|
|
|
|
#include "plasmoidregistry.h"
|
|
#include "statusnotifieritemhost.h"
|
|
#include "statusnotifieritemsource.h"
|
|
#include "systemtraysettings.h"
|
|
|
|
#include <KLocalizedString>
|
|
#include <Plasma/Applet>
|
|
#include <Plasma/DataContainer>
|
|
#include <Plasma/Service>
|
|
#include <PluginLoader>
|
|
|
|
#include <QIcon>
|
|
#include <QQuickItem>
|
|
|
|
BaseModel::BaseModel(QPointer<SystemTraySettings> settings, QObject *parent)
|
|
: QAbstractListModel(parent)
|
|
, m_settings(settings)
|
|
, m_showAllItems(m_settings->isShowAllItems())
|
|
, m_shownItems(m_settings->shownItems())
|
|
, m_hiddenItems(m_settings->hiddenItems())
|
|
{
|
|
connect(m_settings, &SystemTraySettings::configurationChanged, this, &BaseModel::onConfigurationChanged);
|
|
}
|
|
|
|
QHash<int, QByteArray> BaseModel::roleNames() const
|
|
{
|
|
return {
|
|
{Qt::DisplayRole, QByteArrayLiteral("display")},
|
|
{Qt::DecorationRole, QByteArrayLiteral("decoration")},
|
|
{static_cast<int>(BaseRole::ItemType), QByteArrayLiteral("itemType")},
|
|
{static_cast<int>(BaseRole::ItemId), QByteArrayLiteral("itemId")},
|
|
{static_cast<int>(BaseRole::CanRender), QByteArrayLiteral("canRender")},
|
|
{static_cast<int>(BaseRole::Category), QByteArrayLiteral("category")},
|
|
{static_cast<int>(BaseRole::Status), QByteArrayLiteral("status")},
|
|
{static_cast<int>(BaseRole::EffectiveStatus), QByteArrayLiteral("effectiveStatus")},
|
|
};
|
|
}
|
|
|
|
void BaseModel::onConfigurationChanged()
|
|
{
|
|
m_showAllItems = m_settings->isShowAllItems();
|
|
m_shownItems = m_settings->shownItems();
|
|
m_hiddenItems = m_settings->hiddenItems();
|
|
|
|
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {static_cast<int>(BaseModel::BaseRole::EffectiveStatus)});
|
|
}
|
|
|
|
Plasma::Types::ItemStatus BaseModel::calculateEffectiveStatus(bool canRender, Plasma::Types::ItemStatus status, QString itemId) const
|
|
{
|
|
if (!canRender) {
|
|
return Plasma::Types::ItemStatus::HiddenStatus;
|
|
}
|
|
|
|
if (status == Plasma::Types::ItemStatus::HiddenStatus) {
|
|
return Plasma::Types::ItemStatus::HiddenStatus;
|
|
}
|
|
|
|
bool forcedShown = m_showAllItems || m_shownItems.contains(itemId);
|
|
bool forcedHidden = m_hiddenItems.contains(itemId);
|
|
|
|
if (forcedShown || (!forcedHidden && status != Plasma::Types::ItemStatus::PassiveStatus)) {
|
|
return Plasma::Types::ItemStatus::ActiveStatus;
|
|
} else {
|
|
return Plasma::Types::ItemStatus::PassiveStatus;
|
|
}
|
|
}
|
|
|
|
static QString plasmoidCategoryForMetadata(const KPluginMetaData &metadata)
|
|
{
|
|
QString category = QStringLiteral("UnknownCategory");
|
|
|
|
if (metadata.isValid()) {
|
|
const QString notificationAreaCategory = metadata.value(QStringLiteral("X-Plasma-NotificationAreaCategory"));
|
|
if (!notificationAreaCategory.isEmpty()) {
|
|
category = notificationAreaCategory;
|
|
}
|
|
}
|
|
|
|
return category;
|
|
}
|
|
|
|
PlasmoidModel::PlasmoidModel(QPointer<SystemTraySettings> settings, QPointer<PlasmoidRegistry> plasmoidRegistry, QObject *parent)
|
|
: BaseModel(settings, parent)
|
|
, m_plasmoidRegistry(plasmoidRegistry)
|
|
{
|
|
connect(m_plasmoidRegistry, &PlasmoidRegistry::pluginRegistered, this, &PlasmoidModel::appendRow);
|
|
connect(m_plasmoidRegistry, &PlasmoidRegistry::pluginUnregistered, this, &PlasmoidModel::removeRow);
|
|
|
|
const auto appletMetaDataList = m_plasmoidRegistry->systemTrayApplets();
|
|
for (const auto &info : appletMetaDataList) {
|
|
if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationArea")) != "true") {
|
|
continue;
|
|
}
|
|
appendRow(info);
|
|
}
|
|
}
|
|
|
|
QVariant PlasmoidModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
|
|
return QVariant();
|
|
}
|
|
|
|
const PlasmoidModel::Item &item = m_items[index.row()];
|
|
const KPluginMetaData &pluginMetaData = item.pluginMetaData;
|
|
const Plasma::Applet *applet = item.applet;
|
|
|
|
if (role <= Qt::UserRole) {
|
|
switch (role) {
|
|
case Qt::DisplayRole: {
|
|
const QString dbusactivation = pluginMetaData.value(QStringLiteral("X-Plasma-DBusActivationService"));
|
|
if (dbusactivation.isEmpty()) {
|
|
return pluginMetaData.name();
|
|
} else {
|
|
return i18nc("Suffix added to the applet name if the applet is autoloaded via DBus activation", "%1 (Automatic load)", pluginMetaData.name());
|
|
}
|
|
}
|
|
case Qt::DecorationRole: {
|
|
QIcon icon = QIcon::fromTheme(pluginMetaData.iconName());
|
|
return icon.isNull() ? QVariant() : icon;
|
|
}
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
if (role < static_cast<int>(Role::Applet)) {
|
|
Plasma::Types::ItemStatus status = Plasma::Types::ItemStatus::UnknownStatus;
|
|
if (applet) {
|
|
status = applet->status();
|
|
}
|
|
|
|
switch (static_cast<BaseRole>(role)) {
|
|
case BaseRole::ItemType:
|
|
return QStringLiteral("Plasmoid");
|
|
case BaseRole::ItemId:
|
|
return pluginMetaData.pluginId();
|
|
case BaseRole::CanRender:
|
|
return applet != nullptr;
|
|
case BaseRole::Category:
|
|
return plasmoidCategoryForMetadata(pluginMetaData);
|
|
case BaseRole::Status:
|
|
return status;
|
|
case BaseRole::EffectiveStatus:
|
|
return calculateEffectiveStatus(applet != nullptr, status, pluginMetaData.pluginId());
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
switch (static_cast<Role>(role)) {
|
|
case Role::Applet:
|
|
return applet ? applet->property("_plasma_graphicObject") : QVariant();
|
|
case Role::HasApplet:
|
|
return applet != nullptr;
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
int PlasmoidModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
return parent.isValid() ? 0 : m_items.size();
|
|
}
|
|
|
|
QHash<int, QByteArray> PlasmoidModel::roleNames() const
|
|
{
|
|
QHash<int, QByteArray> roles = BaseModel::roleNames();
|
|
|
|
roles.insert(static_cast<int>(Role::Applet), QByteArrayLiteral("applet"));
|
|
roles.insert(static_cast<int>(Role::HasApplet), QByteArrayLiteral("hasApplet"));
|
|
|
|
return roles;
|
|
}
|
|
|
|
void PlasmoidModel::addApplet(Plasma::Applet *applet)
|
|
{
|
|
auto pluginMetaData = applet->pluginMetaData();
|
|
|
|
int idx = indexOfPluginId(pluginMetaData.pluginId());
|
|
|
|
if (idx < 0) {
|
|
idx = rowCount();
|
|
appendRow(pluginMetaData);
|
|
}
|
|
|
|
m_items[idx].applet = applet;
|
|
connect(applet, &Plasma::Applet::statusChanged, this, [this, applet](Plasma::Types::ItemStatus status) {
|
|
Q_UNUSED(status)
|
|
int idx = indexOfPluginId(applet->pluginMetaData().pluginId());
|
|
Q_EMIT dataChanged(index(idx, 0), index(idx, 0), {static_cast<int>(BaseRole::Status)});
|
|
});
|
|
|
|
Q_EMIT dataChanged(index(idx, 0), index(idx, 0));
|
|
}
|
|
|
|
void PlasmoidModel::removeApplet(Plasma::Applet *applet)
|
|
{
|
|
int idx = indexOfPluginId(applet->pluginMetaData().pluginId());
|
|
if (idx >= 0) {
|
|
m_items[idx].applet = nullptr;
|
|
Q_EMIT dataChanged(index(idx, 0), index(idx, 0));
|
|
applet->disconnect(this);
|
|
}
|
|
}
|
|
|
|
void PlasmoidModel::appendRow(const KPluginMetaData &pluginMetaData)
|
|
{
|
|
int idx = rowCount();
|
|
beginInsertRows(QModelIndex(), idx, idx);
|
|
|
|
PlasmoidModel::Item item;
|
|
item.pluginMetaData = pluginMetaData;
|
|
m_items.append(item);
|
|
|
|
endInsertRows();
|
|
}
|
|
|
|
void PlasmoidModel::removeRow(const QString &pluginId)
|
|
{
|
|
int idx = indexOfPluginId(pluginId);
|
|
beginRemoveRows(QModelIndex(), idx, idx);
|
|
m_items.removeAt(idx);
|
|
endRemoveRows();
|
|
}
|
|
|
|
int PlasmoidModel::indexOfPluginId(const QString &pluginId) const
|
|
{
|
|
for (int i = 0; i < rowCount(); i++) {
|
|
if (m_items[i].pluginMetaData.pluginId() == pluginId) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
StatusNotifierModel::StatusNotifierModel(QPointer<SystemTraySettings> settings, QObject *parent)
|
|
: BaseModel(settings, parent)
|
|
{
|
|
m_sniHost = StatusNotifierItemHost::self();
|
|
|
|
connect(m_sniHost, &StatusNotifierItemHost::itemAdded, this, &StatusNotifierModel::addSource);
|
|
connect(m_sniHost, &StatusNotifierItemHost::itemRemoved, this, &StatusNotifierModel::removeSource);
|
|
|
|
for (auto service : m_sniHost->services()) {
|
|
addSource(service);
|
|
}
|
|
}
|
|
|
|
static Plasma::Types::ItemStatus extractStatus(const StatusNotifierItemSource *sniData)
|
|
{
|
|
QString status = sniData->status();
|
|
if (status == QLatin1String("Active")) {
|
|
return Plasma::Types::ItemStatus::ActiveStatus;
|
|
} else if (status == QLatin1String("NeedsAttention")) {
|
|
return Plasma::Types::ItemStatus::NeedsAttentionStatus;
|
|
} else if (status == QLatin1String("Passive")) {
|
|
return Plasma::Types::ItemStatus::PassiveStatus;
|
|
} else {
|
|
return Plasma::Types::ItemStatus::UnknownStatus;
|
|
}
|
|
}
|
|
|
|
static QVariant extractIcon(const QIcon &icon, const QVariant &defaultValue = QVariant())
|
|
{
|
|
if (!icon.isNull()) {
|
|
return icon;
|
|
} else {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
static QString extractItemId(const StatusNotifierItemSource *sniData)
|
|
{
|
|
const QString itemId = sniData->id();
|
|
// Bug 378910: workaround for Dropbox not following the SNI specification
|
|
if (itemId.startsWith(QLatin1String("dropbox-client-"))) {
|
|
return QLatin1String("dropbox-client-PID");
|
|
} else {
|
|
return itemId;
|
|
}
|
|
}
|
|
|
|
QVariant StatusNotifierModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
|
|
return QVariant();
|
|
}
|
|
|
|
StatusNotifierModel::Item item = m_items[index.row()];
|
|
StatusNotifierItemSource *sniData = m_sniHost->itemForService(item.source);
|
|
|
|
const QString itemId = extractItemId(sniData);
|
|
|
|
if (role <= Qt::UserRole) {
|
|
switch (role) {
|
|
case Qt::DisplayRole:
|
|
return sniData->title();
|
|
case Qt::DecorationRole:
|
|
return extractIcon(sniData->icon(), sniData->iconName());
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
if (role < static_cast<int>(Role::DataEngineSource)) {
|
|
switch (static_cast<BaseRole>(role)) {
|
|
case BaseRole::ItemType:
|
|
return QStringLiteral("StatusNotifier");
|
|
case BaseRole::ItemId:
|
|
return itemId;
|
|
case BaseRole::CanRender:
|
|
return true;
|
|
case BaseRole::Category: {
|
|
QVariant category = sniData->category();
|
|
return category.isNull() ? QStringLiteral("UnknownCategory") : sniData->category();
|
|
}
|
|
case BaseRole::Status:
|
|
return extractStatus(sniData);
|
|
case BaseRole::EffectiveStatus:
|
|
return calculateEffectiveStatus(true, extractStatus(sniData), itemId);
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
switch (static_cast<Role>(role)) {
|
|
case Role::DataEngineSource:
|
|
return item.source;
|
|
case Role::Service:
|
|
return QVariant::fromValue(item.service);
|
|
case Role::AttentionIcon:
|
|
return extractIcon(sniData->attentionIcon());
|
|
case Role::AttentionIconName:
|
|
return sniData->attentionIconName();
|
|
case Role::AttentionMovieName:
|
|
return sniData->attentionMovieName();
|
|
case Role::Category:
|
|
return sniData->category();
|
|
case Role::Icon:
|
|
return extractIcon(sniData->icon());
|
|
case Role::IconName:
|
|
return sniData->iconName();
|
|
case Role::IconThemePath:
|
|
return sniData->iconThemePath();
|
|
case Role::Id:
|
|
return itemId;
|
|
case Role::ItemIsMenu:
|
|
return sniData->itemIsMenu();
|
|
case Role::OverlayIconName:
|
|
return sniData->overlayIconName();
|
|
case Role::Status:
|
|
return extractStatus(sniData);
|
|
case Role::Title:
|
|
return sniData->title();
|
|
case Role::ToolTipSubTitle:
|
|
return sniData->toolTipSubTitle();
|
|
case Role::ToolTipTitle:
|
|
return sniData->toolTipTitle();
|
|
case Role::WindowId:
|
|
return sniData->windowId();
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
int StatusNotifierModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
return parent.isValid() ? 0 : m_items.size();
|
|
}
|
|
|
|
QHash<int, QByteArray> StatusNotifierModel::roleNames() const
|
|
{
|
|
QHash<int, QByteArray> roles = BaseModel::roleNames();
|
|
|
|
roles.insert(static_cast<int>(Role::DataEngineSource), QByteArrayLiteral("DataEngineSource"));
|
|
roles.insert(static_cast<int>(Role::Service), QByteArrayLiteral("Service"));
|
|
roles.insert(static_cast<int>(Role::AttentionIcon), QByteArrayLiteral("AttentionIcon"));
|
|
roles.insert(static_cast<int>(Role::AttentionIconName), QByteArrayLiteral("AttentionIconName"));
|
|
roles.insert(static_cast<int>(Role::AttentionMovieName), QByteArrayLiteral("AttentionMovieName"));
|
|
roles.insert(static_cast<int>(Role::Category), QByteArrayLiteral("Category"));
|
|
roles.insert(static_cast<int>(Role::Icon), QByteArrayLiteral("Icon"));
|
|
roles.insert(static_cast<int>(Role::IconName), QByteArrayLiteral("IconName"));
|
|
roles.insert(static_cast<int>(Role::IconThemePath), QByteArrayLiteral("IconThemePath"));
|
|
roles.insert(static_cast<int>(Role::Id), QByteArrayLiteral("Id"));
|
|
roles.insert(static_cast<int>(Role::ItemIsMenu), QByteArrayLiteral("ItemIsMenu"));
|
|
roles.insert(static_cast<int>(Role::OverlayIconName), QByteArrayLiteral("OverlayIconName"));
|
|
roles.insert(static_cast<int>(Role::Status), QByteArrayLiteral("Status"));
|
|
roles.insert(static_cast<int>(Role::Title), QByteArrayLiteral("Title"));
|
|
roles.insert(static_cast<int>(Role::ToolTipSubTitle), QByteArrayLiteral("ToolTipSubTitle"));
|
|
roles.insert(static_cast<int>(Role::ToolTipTitle), QByteArrayLiteral("ToolTipTitle"));
|
|
roles.insert(static_cast<int>(Role::WindowId), QByteArrayLiteral("WindowId"));
|
|
|
|
return roles;
|
|
}
|
|
|
|
void StatusNotifierModel::addSource(const QString &source)
|
|
{
|
|
int count = rowCount();
|
|
beginInsertRows(QModelIndex(), count, count);
|
|
|
|
StatusNotifierModel::Item item;
|
|
item.source = source;
|
|
|
|
StatusNotifierItemSource *sni = m_sniHost->itemForService(source);
|
|
connect(sni, &StatusNotifierItemSource::dataUpdated, this, [=]() {
|
|
dataUpdated(source);
|
|
});
|
|
item.service = sni->createService();
|
|
m_items.append(item);
|
|
endInsertRows();
|
|
}
|
|
|
|
void StatusNotifierModel::removeSource(const QString &source)
|
|
{
|
|
int idx = indexOfSource(source);
|
|
if (idx >= 0) {
|
|
beginRemoveRows(QModelIndex(), idx, idx);
|
|
delete m_items[idx].service;
|
|
m_items.removeAt(idx);
|
|
endRemoveRows();
|
|
}
|
|
}
|
|
|
|
void StatusNotifierModel::dataUpdated(const QString &sourceName)
|
|
{
|
|
int idx = indexOfSource(sourceName);
|
|
|
|
if (idx >= 0) {
|
|
Q_EMIT dataChanged(index(idx, 0), index(idx, 0));
|
|
}
|
|
}
|
|
|
|
int StatusNotifierModel::indexOfSource(const QString &source) const
|
|
{
|
|
for (int i = 0; i < rowCount(); i++) {
|
|
if (m_items[i].source == source) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
SystemTrayModel::SystemTrayModel(QObject *parent)
|
|
: QConcatenateTablesProxyModel(parent)
|
|
{
|
|
m_roleNames = QConcatenateTablesProxyModel::roleNames();
|
|
}
|
|
|
|
QHash<int, QByteArray> SystemTrayModel::roleNames() const
|
|
{
|
|
return m_roleNames;
|
|
}
|
|
|
|
void SystemTrayModel::addSourceModel(QAbstractItemModel *sourceModel)
|
|
{
|
|
QHashIterator<int, QByteArray> it(sourceModel->roleNames());
|
|
while (it.hasNext()) {
|
|
it.next();
|
|
|
|
if (!m_roleNames.contains(it.key())) {
|
|
m_roleNames.insert(it.key(), it.value());
|
|
}
|
|
}
|
|
|
|
QConcatenateTablesProxyModel::addSourceModel(sourceModel);
|
|
}
|