dock/src/applicationmodel.cpp
2021-06-04 15:11:39 +08:00

464 lines
12 KiB
C++

/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: rekols <revenmartin@gmail.com>
*
* 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 3 of the License, or
* 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 <http://www.gnu.org/licenses/>.
*/
#include "applicationmodel.h"
#include "utils.h"
#include <QProcess>
ApplicationModel::ApplicationModel(QObject *parent)
: QAbstractListModel(parent)
, m_iface(XWindowInterface::instance())
, m_sysAppMonitor(SystemAppMonitor::self())
{
connect(m_iface, &XWindowInterface::windowAdded, this, &ApplicationModel::onWindowAdded);
connect(m_iface, &XWindowInterface::windowRemoved, this, &ApplicationModel::onWindowRemoved);
connect(m_iface, &XWindowInterface::activeChanged, this, &ApplicationModel::onActiveChanged);
initPinnedApplications();
QTimer::singleShot(100, m_iface, &XWindowInterface::startInitWindows);
}
int ApplicationModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_appItems.size();
}
QHash<int, QByteArray> ApplicationModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[AppIdRole] = "appId";
roles[IconNameRole] = "iconName";
roles[VisibleNameRole] = "visibleName";
roles[ActiveRole] = "isActive";
roles[WindowCountRole] = "windowCount";
roles[IsPinnedRole] = "isPinned";
roles[DesktopFileRole] = "desktopFile";
roles[FixedItemRole] = "fixed";
return roles;
}
QVariant ApplicationModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
ApplicationItem *item = m_appItems.at(index.row());
switch (role) {
case AppIdRole:
return item->id;
case IconNameRole:
return item->iconName;
case VisibleNameRole:
return item->visibleName;
case ActiveRole:
return item->isActive;
case WindowCountRole:
return item->wids.count();
case IsPinnedRole:
return item->isPinned;
case DesktopFileRole:
return item->desktopPath;
case FixedItemRole:
return item->fixed;
default:
return QVariant();
}
return QVariant();
}
void ApplicationModel::clicked(const QString &id)
{
ApplicationItem *item = findItemById(id);
if (!item)
return;
// Application Item that has been pinned,
// We need to open it.
if (item->wids.isEmpty()) {
// open application
openNewInstance(item->id);
}
// Multiple windows have been opened and need to switch between them,
// The logic here needs to be improved.
else if (item->wids.count() > 1) {
item->currentActive++;
if (item->currentActive == item->wids.count())
item->currentActive = 0;
m_iface->forceActiveWindow(item->wids.at(item->currentActive));
} else if (m_iface->activeWindow() == item->wids.first()) {
m_iface->minimizeWindow(item->wids.first());
} else {
m_iface->forceActiveWindow(item->wids.first());
}
}
void ApplicationModel::raiseWindow(const QString &id)
{
ApplicationItem *item = findItemById(id);
if (!item)
return;
m_iface->forceActiveWindow(item->wids.at(item->currentActive));
}
bool ApplicationModel::openNewInstance(const QString &appId)
{
ApplicationItem *item = findItemById(appId);
if (!item)
return false;
QProcess process;
if (!item->exec.isEmpty()) {
QStringList args = item->exec.split(" ");
process.setProgram(args.first());
args.removeFirst();
if (!args.isEmpty()) {
process.setArguments(args);
}
} else {
process.setProgram(appId);
}
return process.startDetached();
}
void ApplicationModel::closeAllByAppId(const QString &appId)
{
ApplicationItem *item = findItemById(appId);
if (!item)
return;
for (quint64 wid : item->wids) {
m_iface->closeWindow(wid);
}
}
void ApplicationModel::pin(const QString &appId)
{
ApplicationItem *item = findItemById(appId);
if (!item)
return;
item->isPinned = true;
handleDataChangedFromItem(item);
savePinAndUnPinList();
}
void ApplicationModel::unPin(const QString &appId)
{
ApplicationItem *item = findItemById(appId);
if (!item)
return;
item->isPinned = false;
handleDataChangedFromItem(item);
// Need to be removed after unpin
if (item->wids.isEmpty()) {
int index = indexOf(item->id);
if (index != -1) {
beginRemoveRows(QModelIndex(), index, index);
m_appItems.removeAll(item);
endRemoveRows();
emit itemRemoved();
emit countChanged();
}
}
savePinAndUnPinList();
}
void ApplicationModel::updateGeometries(const QString &id, QRect rect)
{
ApplicationItem *item = findItemById(id);
// If not found
if (!item)
return;
for (quint64 id : item->wids) {
m_iface->setIconGeometry(id, rect);
}
}
void ApplicationModel::move(int from, int to)
{
if (from == to)
return;
m_appItems.move(from, to);
if (from < to)
beginMoveRows(QModelIndex(), from, from, QModelIndex(), to + 1);
else
beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
endMoveRows();
}
ApplicationItem *ApplicationModel::findItemByWId(quint64 wid)
{
for (ApplicationItem *item : m_appItems) {
for (quint64 winId : item->wids) {
if (winId == wid)
return item;
}
}
return nullptr;
}
ApplicationItem *ApplicationModel::findItemById(const QString &id)
{
for (ApplicationItem *item : m_appItems) {
if (item->id == id)
return item;
}
return nullptr;
}
bool ApplicationModel::contains(const QString &id)
{
for (ApplicationItem *item : qAsConst(m_appItems)) {
if (item->id == id)
return true;
}
return false;
}
int ApplicationModel::indexOf(const QString &id)
{
for (ApplicationItem *item : m_appItems) {
if (item->id == id)
return m_appItems.indexOf(item);
}
return -1;
}
void ApplicationModel::initPinnedApplications()
{
QSettings settings(QSettings::UserScope, "cutefishos", "dock_pinned");
QStringList groups = settings.childGroups();
// Launcher
ApplicationItem *item = new ApplicationItem;
item->id = "cutefish-launcher";
item->exec = "cutefish-launcher";
item->iconName = "qrc:/images/launcher.svg";
item->visibleName = tr("Launcher");
item->fixed = true;
m_appItems.append(item);
// Pinned Apps
for (int i = 0; i < groups.size(); ++i) {
for (const QString &id : groups) {
settings.beginGroup(id);
int index = settings.value("Index").toInt();
if (index == i) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
ApplicationItem *item = new ApplicationItem;
item->desktopPath = settings.value("DesktopPath").toString();
item->id = id;
item->isPinned = true;
// Read from desktop file.
if (!item->desktopPath.isEmpty()) {
QMap<QString, QString> desktopInfo = Utils::instance()->readInfoFromDesktop(item->desktopPath);
item->iconName = desktopInfo.value("Icon");
item->visibleName = desktopInfo.value("Name");
item->exec = desktopInfo.value("Exec");
}
// Read from config file.
if (item->iconName.isEmpty())
item->iconName = settings.value("Icon").toString();
if (item->visibleName.isEmpty())
item->visibleName = settings.value("VisibleName").toString();
if (item->exec.isEmpty())
item->exec = settings.value("Exec").toString();
m_appItems.append(item);
endInsertRows();
emit itemAdded();
emit countChanged();
settings.endGroup();
break;
} else {
settings.endGroup();
}
}
}
}
void ApplicationModel::savePinAndUnPinList()
{
QSettings settings(QSettings::UserScope, "cutefishos", "dock_pinned");
settings.clear();
int index = 0;
for (ApplicationItem *item : m_appItems) {
if (item->isPinned) {
settings.beginGroup(item->id);
settings.setValue("Index", index);
settings.setValue("Icon", item->iconName);
settings.setValue("VisibleName", item->visibleName);
settings.setValue("Exec", item->exec);
settings.setValue("DesktopPath", item->desktopPath);
settings.endGroup();
++index;
}
}
settings.sync();
}
void ApplicationModel::handleDataChangedFromItem(ApplicationItem *item)
{
if (!item)
return;
QModelIndex idx = index(indexOf(item->id), 0, QModelIndex());
if (idx.isValid()) {
emit dataChanged(idx, idx);
}
}
void ApplicationModel::onWindowAdded(quint64 wid)
{
QMap<QString, QVariant> info = m_iface->requestInfo(wid);
const QString id = info.value("id").toString();
// Skip...
if (id == "cutefish-launcher")
return;
if (contains(id)) {
for (ApplicationItem *item : m_appItems) {
if (item->id == id) {
item->wids.append(wid);
// Need to update application active status.
item->isActive = info.value("active").toBool();
handleDataChangedFromItem(item);
}
}
} else {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
ApplicationItem *item = new ApplicationItem;
item->id = id;
item->iconName = info.value("iconName").toString();
item->visibleName = info.value("visibleName").toString();
item->isActive = info.value("active").toBool();
item->wids.append(wid);
QString desktopPath = m_iface->desktopFilePath(wid);
if (!desktopPath.isEmpty()) {
QMap<QString, QString> desktopInfo = Utils::instance()->readInfoFromDesktop(desktopPath);
item->iconName = desktopInfo.value("Icon");
item->visibleName = desktopInfo.value("Name");
item->exec = desktopInfo.value("Exec");
item->desktopPath = desktopPath;
}
m_appItems << item;
endInsertRows();
emit itemAdded();
emit countChanged();
}
}
void ApplicationModel::onWindowRemoved(quint64 wid)
{
ApplicationItem *item = findItemByWId(wid);
if (!item)
return;
// Remove from wid list.
item->wids.removeOne(wid);
if (item->currentActive >= item->wids.size())
item->currentActive = 0;
handleDataChangedFromItem(item);
if (item->wids.isEmpty()) {
// If it is not fixed to the dock, need to remove it.
if (!item->isPinned) {
int index = indexOf(item->id);
if (index == -1)
return;
beginRemoveRows(QModelIndex(), index, index);
m_appItems.removeAll(item);
endRemoveRows();
emit itemRemoved();
emit countChanged();
}
}
}
void ApplicationModel::onActiveChanged(quint64 wid)
{
// Using this method will cause the listview scrollbar to reset.
// beginResetModel();
for (ApplicationItem *item : m_appItems) {
if (item->isActive != item->wids.contains(wid)) {
item->isActive = item->wids.contains(wid);
QModelIndex idx = index(indexOf(item->id), 0, QModelIndex());
if (idx.isValid()) {
emit dataChanged(idx, idx);
}
}
}
}