dde-dock/frame/window/tray/tray_model.cpp

759 lines
21 KiB
C++
Raw Normal View History

// Copyright (C) 2022 ~ 2022 Deepin Technology Co., Ltd.
// SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "tray_model.h"
#include "tray_monitor.h"
#include "indicatortrayitem.h"
#include "indicatorplugin.h"
#include "quicksettingcontroller.h"
#include "pluginsiteminterface.h"
#include "docksettings.h"
#include "platformutils.h"
#include <QMimeData>
#include <QIcon>
#include <QDebug>
#include <QAbstractItemModel>
#include <QDBusInterface>
#define TRAY_DRAG_FALG "tray_drag"
#define DOCKQUICKTRAYNAME "Dock_Quick_Tray_Name"
TrayModel *TrayModel::getDockModel()
{
static TrayModel *model = nullptr;
if (!model) {
model = new TrayModel(false);
TrayModel *iconModel = getIconModel();
connect(iconModel, &TrayModel::rowsRemoved, model, [ = ] {
model->setExpandVisible(iconModel->rowCount() > 0);
});
connect(iconModel, &TrayModel::rowsInserted, model, [ = ] {
model->setExpandVisible(iconModel->rowCount() > 0);
});
connect(iconModel, &TrayModel::rowCountChanged, model, [ = ] {
model->setExpandVisible(iconModel->rowCount() > 0);
});
}
return model;
}
TrayModel *TrayModel::getIconModel()
{
static TrayModel model(true);
return &model;
}
TrayModel::TrayModel(bool isIconTray, QObject *parent)
: QAbstractListModel(parent)
, m_dragModelIndex(QModelIndex())
, m_dropModelIndex(QModelIndex())
, m_monitor(new TrayMonitor(this))
, m_isTrayIcon(isIconTray)
{
connect(m_monitor, &TrayMonitor::xEmbedTrayAdded, this, &TrayModel::onXEmbedTrayAdded);
connect(m_monitor, &TrayMonitor::xEmbedTrayRemoved, this, &TrayModel::onXEmbedTrayRemoved);
connect(m_monitor, &TrayMonitor::sniTrayAdded, this, &TrayModel::onSniTrayAdded);
connect(m_monitor, &TrayMonitor::sniTrayRemoved, this, &TrayModel::onSniTrayRemoved);
connect(m_monitor, &TrayMonitor::indicatorFounded, this, &TrayModel::onIndicatorFounded);
connect(m_monitor, &TrayMonitor::systemTrayAdded, this, &TrayModel::onSystemTrayAdded);
connect(m_monitor, &TrayMonitor::systemTrayRemoved, this, &TrayModel::onSystemTrayRemoved);
connect(m_monitor, &TrayMonitor::requestUpdateIcon, this, &TrayModel::requestUpdateIcon);
connect(DockSettings::instance(), &DockSettings::quickTrayNameChanged, this, &TrayModel::onSettingChanged);
m_fixedTrayNames = DockSettings::instance()->getTrayItemsOnDock();
m_fixedTrayNames.removeDuplicates();
}
void TrayModel::dropSwap(int newPos)
{
if (!m_dragModelIndex.isValid())
return;
int row = m_dragModelIndex.row();
if (row < m_winInfos.size())
m_dragInfo = m_winInfos.takeAt(row);
WinInfo name = m_dragInfo;
m_winInfos.insert(newPos, name);
emit QAbstractItemModel::dataChanged(m_dragModelIndex, m_dropModelIndex);
requestRefreshEditor();
}
void TrayModel::clearDragDropIndex()
{
const QModelIndex startIndex = m_dragModelIndex;
const QModelIndex endIndex = m_dropModelIndex;
m_dragModelIndex = m_dropModelIndex = QModelIndex();
emit QAbstractItemModel::dataChanged(startIndex, endIndex);
emit QAbstractItemModel::dataChanged(endIndex, startIndex);
}
void TrayModel::setDragingIndex(const QModelIndex index)
{
m_dragModelIndex = index;
m_dropModelIndex = index;
emit QAbstractListModel::dataChanged(index, index);
}
void TrayModel::setDragDropIndex(const QModelIndex index)
{
if (m_dragModelIndex == index)
return;
m_dropModelIndex = index;
emit QAbstractListModel::dataChanged(m_dragModelIndex, index);
emit QAbstractListModel::dataChanged(index, m_dragModelIndex);
}
void TrayModel::setExpandVisible(bool visible, bool openExpand)
{
// 如果是托盘,不支持展开图标
if (m_isTrayIcon)
return;
if (visible) {
// 如果展开图标已经存在,则不添加,
for (WinInfo &winInfo : m_winInfos) {
if (winInfo.type == TrayIconType::ExpandIcon) {
winInfo.expand = openExpand;
return;
}
}
// 如果是任务栏图标,则添加托盘展开图标
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = TrayIconType::ExpandIcon;
info.expand = openExpand;
m_winInfos.insert(0, info); // 展开图标始终显示在第一个
endInsertRows();
Q_EMIT requestRefreshEditor();
Q_EMIT rowCountChanged();
} else {
// 如果隐藏,则直接从列表中移除
bool rowChanged = false;
beginResetModel();
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.type == TrayIconType::ExpandIcon) {
m_winInfos.removeOne(winInfo);
rowChanged = true;
}
}
endResetModel();
if (rowChanged)
Q_EMIT rowCountChanged();
}
}
void TrayModel::updateOpenExpand(bool openExpand)
{
for (WinInfo &winInfo : m_winInfos) {
if (winInfo.type == TrayIconType::ExpandIcon)
winInfo.expand = openExpand;
}
}
void TrayModel::setDragKey(const QString &key)
{
m_dragKey = key;
}
bool TrayModel::indexDragging(const QModelIndex &index) const
{
if (index.isValid() && index.data(Role::KeyRole).toString() == m_dragKey)
return true;
if (!m_dragModelIndex.isValid() || !m_dropModelIndex.isValid())
return false;
const int start = m_dragModelIndex.row();
const int end = m_dropModelIndex.row();
const int current = index.row();
return (start <= end && current >= start && current <= end)
|| (start >= end && current <= start && current >= end);
}
IndicatorTrayItem *TrayModel::indicatorWidget(const QString &indicatorName) const
{
QString indicatorKey = indicatorName;
indicatorKey = indicatorKey.remove(0, QString("indicator:").length());
if (m_indicatorMap.contains(indicatorKey))
return m_indicatorMap.value(indicatorKey)->widget();
return nullptr;
}
QMimeData *TrayModel::mimeData(const QModelIndexList &indexes) const
{
Q_ASSERT(indexes.size() == 1);
QMimeData *mime = new QMimeData;
mime->setData(TRAY_DRAG_FALG, QByteArray());
for (auto index : indexes) {
if (!index.isValid())
continue;
int itemIndex = index.row();
auto info = m_winInfos.at(itemIndex);
mime->setData("type", QByteArray::number(static_cast<int>(info.type)));
mime->setData("key", info.key.toLatin1());
mime->setData("itemKey", info.itemKey.toLatin1());
mime->setData("winId", QByteArray::number(info.winId));
mime->setData("servicePath", info.servicePath.toLatin1());
mime->setData("isTypeWritting", info.isTypeWriting ? "1" : "0");
mime->setData("expand", info.expand ? "1" : "0");
mime->setImageData(QVariant::fromValue((qulonglong)(info.pluginInter)));
//TODO 支持多个index的数据待支持
}
return mime;
}
QVariant TrayModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int itemIndex = index.row();
const WinInfo &info = m_winInfos[itemIndex];
switch (role) {
case Role::TypeRole:
return info.type;
case Role::KeyRole:
return info.key;
case Role::WinIdRole:
return info.winId;
case Role::ServiceRole:
return info.servicePath;
case Role::PluginInterfaceRole:
return (qulonglong)(info.pluginInter);
case Role::ExpandRole:
return info.expand;
case Role::ItemKeyRole:
return info.itemKey;
case Role::Blank:
return indexDragging(index);
default:
return QVariant();
}
}
bool TrayModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(count);
if (m_winInfos.size() - 1 < row)
return false;
beginRemoveRows(parent, row, row);
m_dragInfo = m_winInfos.takeAt(row);
endRemoveRows();
Q_EMIT rowCountChanged();
return true;
}
bool TrayModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
Q_UNUSED(action)
Q_UNUSED(row)
Q_UNUSED(column)
TrayIconType iconType = parent.data(TrayModel::Role::TypeRole).value<TrayIconType>();
if (iconType == TrayIconType::ExpandIcon)
return false;
return data->formats().contains(TRAY_DRAG_FALG);
}
Qt::ItemFlags TrayModel::flags(const QModelIndex &index) const
{
const Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
Q_EMIT requestOpenEditor(index);
return (defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled) & ~Qt::ItemIsSelectable;
}
int TrayModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_winInfos.size();
}
bool TrayModel::isIconTray()
{
return m_isTrayIcon;
}
bool TrayModel::hasExpand() const
{
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.type == TrayIconType::ExpandIcon)
return true;
}
return false;
}
bool TrayModel::isEmpty() const
{
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.type != TrayIconType::ExpandIcon)
return false;
}
return true;
}
void TrayModel::clear()
{
beginResetModel();
m_winInfos.clear();
endResetModel();
Q_EMIT rowCountChanged();
}
WinInfo TrayModel::getWinInfo(const QModelIndex &index)
{
int row = index.row();
if (row < 0 || row >= m_winInfos.size())
return WinInfo();
return m_winInfos[row];
}
void TrayModel::onXEmbedTrayAdded(quint32 winId)
{
if (!xembedCanExport(winId))
return;
for (const WinInfo &info : m_winInfos) {
if (info.winId == winId)
return;
}
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = XEmbed;
info.key = "wininfo:" + QString::number(winId);
info.itemKey = xembedItemKey(winId);
info.winId = winId;
m_winInfos.append(info);
sortItems();
endInsertRows();
Q_EMIT rowCountChanged();
}
void TrayModel::onXEmbedTrayRemoved(quint32 winId)
{
for (auto info : m_winInfos) {
if (info.winId == winId) {
int index = m_winInfos.indexOf(info);
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
Q_EMIT rowCountChanged();
return;
}
}
}
QString TrayModel::fileNameByServiceName(const QString &serviceName) const
{
QStringList serviceInfo = serviceName.split("/");
if (serviceInfo.size() <= 0)
return QString();
QDBusInterface dbsInterface("org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", QDBusConnection::sessionBus());
QDBusMessage msg = dbsInterface.call("GetConnectionUnixProcessID", serviceInfo[0] );
QList<QVariant> arguments = msg.arguments();
if (arguments.size() == 0)
return QString();
QVariant v = arguments.at(0);
uint pid = v.toUInt();
QString path = QString("/proc/%1/cmdline").arg(pid);
QFile file(path);
if (file.open(QIODevice::ReadOnly)) {
const QString fileName = file.readAll();
file.close();
return fileName;
}
return QString();
}
bool TrayModel::isTypeWriting(const QString &servicePath) const
{
const QString appFilePath = fileNameByServiceName(servicePath);
return (appFilePath.startsWith("/usr/bin/fcitx") || appFilePath.endsWith("chinime-qim"));
}
void TrayModel::saveConfig(int index, const WinInfo &winInfo)
{
if (m_isTrayIcon) {
// 如果是从任务栏将图标移动到托盘,就从配置中移除
if (!m_fixedTrayNames.contains(winInfo.itemKey))
return;
m_fixedTrayNames.removeOne(winInfo.itemKey);
} else {
// 如果是将图标从托盘移到任务栏上面,就增加到配置中
if (m_fixedTrayNames.contains(winInfo.itemKey))
return;
if (index >= 0 && index < m_fixedTrayNames.size()) {
m_fixedTrayNames.insert(index, winInfo.itemKey);
} else {
m_fixedTrayNames << winInfo.itemKey;
}
}
DockSettings::instance()->updateTrayItemsOnDock(m_fixedTrayNames);
}
void TrayModel::removeWinInfo(WinInfo winInfo)
{
for (const WinInfo &info : m_winInfos) {
if (winInfo == info) {
int index = m_winInfos.indexOf(info);
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
Q_EMIT rowCountChanged();
break;
}
}
}
bool TrayModel::inTrayConfig(const QString &itemKey) const
{
if (m_isTrayIcon) {
// 如果是托盘区域,显示所有不在配置中的应用
return !m_fixedTrayNames.contains(itemKey);
}
// 如果是任务栏区域,显示所有在配置中的应用
return m_fixedTrayNames.contains(itemKey);
}
QString TrayModel::xembedItemKey(quint32 winId) const
{
return QString("embed:%1").arg(PlatformUtils::getAppNameForWindow(winId));
}
bool TrayModel::xembedCanExport(quint32 winId) const
{
return inTrayConfig(xembedItemKey(winId));
}
QString TrayModel::sniItemKey(const QString &servicePath) const
{
if (isTypeWriting(servicePath))
return "fcitx";
QString fileName = fileNameByServiceName(servicePath);
return QString("sni:%1").arg(fileName.mid(fileName.lastIndexOf("/") + 1));
}
bool TrayModel::sniCanExport(const QString &servicePath) const
{
return inTrayConfig(sniItemKey(servicePath));
}
bool TrayModel::indicatorCanExport(const QString &indicatorName) const
{
return inTrayConfig(IndicatorTrayItem::toIndicatorKey(indicatorName));
}
QString TrayModel::systemItemKey(const QString &pluginName) const
{
return QString("systemItem:%1").arg(pluginName);
}
bool TrayModel::systemItemCanExport(const QString &pluginName) const
{
return inTrayConfig(systemItemKey(pluginName));
}
void TrayModel::sortItems()
{
// 如果当前是展开托盘的内容,则无需排序
if (m_isTrayIcon)
return;
// 数据排列,展开按钮始终排在最前面,输入法始终排在最后面
WinInfos expandWin;
WinInfos inputMethodWin;
// 从列表中获取输入法和展开按钮
for (const WinInfo &winInfo : m_winInfos) {
switch (winInfo.type) {
case TrayIconType::ExpandIcon: {
expandWin << winInfo;
break;
}
case TrayIconType::Sni: {
if (winInfo.isTypeWriting)
inputMethodWin << winInfo;
break;
}
default:
break;
}
}
// 从列表中移除展开按钮
for (const WinInfo &winInfo : expandWin)
m_winInfos.removeOne(winInfo);
// 从列表中移除输入法
for (const WinInfo &winInfo : inputMethodWin)
m_winInfos.removeOne(winInfo);
// 将展开按钮添加到列表的最前面
for (int i = expandWin.size() - 1; i >= 0; i--)
m_winInfos.push_front(expandWin[i]);
// 将输入法添加到列表的最后面
for (int i = 0; i < inputMethodWin.size(); i++)
m_winInfos.push_back(inputMethodWin[i]);
}
void TrayModel::onSniTrayAdded(const QString &servicePath)
{
if (!sniCanExport(servicePath))
return;
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.servicePath == servicePath)
return;
}
bool typeWriting = isTypeWriting(servicePath);
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = Sni;
info.key = "sni:" + servicePath;
info.itemKey = sniItemKey(servicePath);
info.servicePath = servicePath;
info.isTypeWriting = typeWriting; // 是否为输入法
m_winInfos.append(info);
sortItems();
endInsertRows();
Q_EMIT rowCountChanged();
}
void TrayModel::onSniTrayRemoved(const QString &servicePath)
{
for (const WinInfo &info : m_winInfos) {
if (info.servicePath == servicePath) {
int index = m_winInfos.indexOf(info);
// 如果为输入法则无需立刻删除等100毫秒后再观察是否会删除输入法(因为在100毫秒内如果是切换输入法就会很快发送add信号)
if (info.isTypeWriting) {
QTimer::singleShot(100, this, [ servicePath, this ] {
for (WinInfo info : m_winInfos) {
if (info.servicePath == servicePath) {
int index = m_winInfos.indexOf(info);
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
}
}
});
} else {
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
Q_EMIT rowCountChanged();
}
break;
}
}
}
void TrayModel::onIndicatorFounded(const QString &indicatorName)
{
const QString &itemKey = IndicatorTrayItem::toIndicatorKey(indicatorName);
if (exist(itemKey) || !IndicatorTrayItem::isIndicatorKey(itemKey))
return;
IndicatorPlugin *indicatorTray = nullptr;
if (!m_indicatorMap.keys().contains(indicatorName)) {
indicatorTray = new IndicatorPlugin(indicatorName, this);
m_indicatorMap[indicatorName] = indicatorTray;
} else {
indicatorTray = m_indicatorMap[itemKey];
}
connect(indicatorTray, &IndicatorPlugin::delayLoaded, indicatorTray, [ = ] {
onIndicatorAdded(indicatorName);
}, Qt::UniqueConnection);
connect(indicatorTray, &IndicatorPlugin::removed, this, [ = ] {
onIndicatorRemoved(indicatorName);
}, Qt::UniqueConnection);
}
void TrayModel::onIndicatorAdded(const QString &indicatorName)
{
if (!indicatorCanExport(indicatorName))
return;
const QString &itemKey = IndicatorTrayItem::toIndicatorKey(indicatorName);
for (const WinInfo &info : m_winInfos) {
if (info.itemKey == itemKey)
return;
}
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = Incicator;
info.key = itemKey;
info.itemKey = itemKey;
m_winInfos.append(info);
sortItems();
endInsertRows();
Q_EMIT rowCountChanged();
}
void TrayModel::onIndicatorRemoved(const QString &indicatorName)
{
const QString &itemKey = IndicatorTrayItem::toIndicatorKey(indicatorName);
removeRow(itemKey);
}
void TrayModel::onSystemTrayAdded(PluginsItemInterface *itemInter)
{
if (!systemItemCanExport(itemInter->pluginName()))
return;
for (const WinInfo &info : m_winInfos) {
if (info.pluginInter == itemInter)
return;
}
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = SystemItem;
info.pluginInter = itemInter;
info.itemKey = systemItemKey(itemInter->pluginName());
m_winInfos.append(info);
sortItems();
endInsertRows();
Q_EMIT rowCountChanged();
}
void TrayModel::onSystemTrayRemoved(PluginsItemInterface *itemInter)
{
for (const WinInfo &info : m_winInfos) {
if (info.pluginInter != itemInter)
continue;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_winInfos.removeOne(info);
endInsertRows();
Q_EMIT rowCountChanged();
break;
}
}
void TrayModel::onSettingChanged(const QStringList &value)
{
// 先将其转换为任务栏上的图标列表
m_fixedTrayNames = value;
}
void TrayModel::removeRow(const QString &itemKey)
{
for (const WinInfo &info : m_winInfos) {
if (info.itemKey == itemKey) {
int index = m_winInfos.indexOf(info);
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
Q_EMIT rowCountChanged();
break;
}
}
}
void TrayModel::addRow(WinInfo info)
{
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.key == info.key)
return;
}
beginResetModel();
m_winInfos.append(info);
sortItems();
endResetModel();
Q_EMIT requestRefreshEditor();
Q_EMIT rowCountChanged();
}
void TrayModel::insertRow(int index, WinInfo info)
{
for (int i = 0; i < m_winInfos.size(); i++) {
const WinInfo &wininfo = m_winInfos[i];
if (wininfo.key == info.key) {
beginResetModel();
m_winInfos.swapItemsAt(index, i);
endResetModel();
return;
}
}
beginInsertRows(QModelIndex(), index, index);
m_winInfos.insert(index, info);
sortItems();
endInsertRows();
Q_EMIT requestRefreshEditor();
Q_EMIT rowCountChanged();
}
bool TrayModel::exist(const QString &itemKey)
{
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.key == itemKey)
return true;
}
return false;
}