Commit 39975673 authored by Konrad Materka's avatar Konrad Materka

[SystemTray] Use unified data model everywhere

Summary:
Use the unified data model everywhere, not just in configuration. This simplifies UI code, separetes presentation from data.
This requires the use of ListView and GridView.
This change enables the implementation of more advanced sorting algorithms for systemtray items.

Test Plan: Affects almost all areas of system tray.

Reviewers: #plasma_workspaces, #plasma, davidedmundson, ngraham, broulik

Reviewed By: #plasma_workspaces, #plasma, davidedmundson, ngraham

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D26992
parent 38757fb5
......@@ -28,7 +28,7 @@ PlasmaCore.ToolTipArea {
property bool vertical: plasmoid.formFactor === PlasmaCore.Types.Vertical
implicitWidth: units.iconSizes.smallMedium
implicitHeight: implicitWidth
visible: root.hiddenLayout.children.length > 0
visible: root.hiddenLayout.contentItem.children.length > 0
subText: root.expanded ? i18n("Close popup") : i18n("Show hidden icons")
......
/*
* Copyright 2016 Marco Martin <mart@kde.org>
* Copyright 2020 Konrad Materka <materka@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
......@@ -19,12 +20,13 @@
import QtQuick 2.1
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras
import "items"
PlasmaExtras.ScrollArea {
MouseArea {
id: hiddenTasksView
visible: !root.activeApplet || (root.activeApplet.parent && root.activeApplet.parent.inHiddenLayout)
......@@ -32,40 +34,48 @@ PlasmaExtras.ScrollArea {
property alias layout: hiddenTasksColumn
//Useful to align stuff to the column of icons, both in expanded and shrink modes
property int iconColumnWidth: root.hiddenItemSize + highlight.marginHints.left + highlight.marginHints.right
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
verticalScrollBarPolicy: activeApplet ? Qt.ScrollBarAlwaysOff : Qt.ScrollBarAsNeeded
Flickable {
contentWidth: width
contentHeight: hiddenTasksColumn.height
hoverEnabled: true
onExited: hiddenTasksColumn.currentIndex = -1
MouseArea {
width: parent.width
height: hiddenTasksColumn.height
drag.filterChildren: true
hoverEnabled: true
onExited: hiddenTasksColumn.hoveredItem = null;
PlasmaExtras.ScrollArea {
width: parent.width
height: parent.height
CurrentItemHighLight {
target: root.activeApplet && root.activeApplet.parent.parent == hiddenTasksColumn ? root.activeApplet.parent : null
location: PlasmaCore.Types.LeftEdge
}
PlasmaComponents.Highlight {
id: highlight
visible: hiddenTasksColumn.hoveredItem != null && !root.activeApplet
y: hiddenTasksColumn.hoveredItem ? hiddenTasksColumn.hoveredItem.y : 0
width: hiddenTasksColumn.hoveredItem ? hiddenTasksColumn.hoveredItem.width : 0
height: hiddenTasksColumn.hoveredItem ? hiddenTasksColumn.hoveredItem.height : 0
}
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
verticalScrollBarPolicy: root.activeApplet ? Qt.ScrollBarAlwaysOff : Qt.ScrollBarAsNeeded
ListView {
id: hiddenTasksColumn
spacing: units.smallSpacing
currentIndex: -1
highlight: PlasmaComponents.Highlight {}
highlightMoveDuration: 0
highlightResizeDuration: 0
Column {
id: hiddenTasksColumn
readonly property int iconItemHeight: root.hiddenItemSize + highlight.marginHints.top + highlight.marginHints.bottom
spacing: units.smallSpacing
width: parent.width
property Item hoveredItem
readonly property int iconItemHeight: root.hiddenItemSize + highlight.marginHints.top + highlight.marginHints.bottom
model: PlasmaCore.SortFilterModel {
sourceModel: plasmoid.nativeInterface.systemTrayModel
filterRole: "effectiveStatus"
filterCallback: function(source_row, value) {
return value === PlasmaCore.Types.PassiveStatus
}
}
delegate: ItemLoader {}
}
}
PlasmaComponents.Highlight {
id: highlight
visible: false
}
CurrentItemHighLight {
parent: hiddenTasksColumn.contentItem
target: root.activeApplet && root.activeApplet.parent && root.activeApplet.parent.inHiddenLayout ? root.activeApplet.parent.parent : null
location: PlasmaCore.Types.LeftEdge
}
}
/*
* Copyright 2016 Marco Martin <mart@kde.org>
* Copyright 2020 Konrad Materka <materka@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
......@@ -24,36 +25,22 @@ import org.kde.plasma.components 2.0 as PlasmaComponents
PlasmaCore.ToolTipArea {
id: abstractItem
height: inVisibleLayout ? visibleLayout.iconSize : hiddenLayout.iconItemHeight
width: inVisibleLayout ? visibleLayout.iconSize : hiddenLayout.width
height: inVisibleLayout ? visibleLayout.cellHeight : hiddenLayout.iconItemHeight
width: inVisibleLayout ? visibleLayout.cellWidth : hiddenLayout.width
property string itemId
property string category
property alias text: label.text
property Item iconItem
property int /*PlasmaCore.Types.ItemStatus*/ status
property int /*PlasmaCore.Types.ItemStatus*/ effectiveStatus
readonly property bool inHiddenLayout: effectiveStatus === PlasmaCore.Types.PassiveStatus
readonly property bool inVisibleLayout: effectiveStatus === PlasmaCore.Types.ActiveStatus
property QtObject model
signal clicked(var mouse)
signal pressed(var mouse)
signal wheel(var wheel)
signal contextMenu(var mouse)
property bool forcedHidden: plasmoid.configuration.hiddenItems.indexOf(itemId) !== -1
property bool forcedShown: plasmoid.configuration.showAllItems || plasmoid.configuration.shownItems.indexOf(itemId) !== -1
readonly property int effectiveStatus: {
if (status === PlasmaCore.Types.HiddenStatus) {
return PlasmaCore.Types.HiddenStatus
} else if (forcedShown || (!forcedHidden && status !== PlasmaCore.Types.PassiveStatus)) {
return PlasmaCore.Types.ActiveStatus
} else {
return PlasmaCore.Types.PassiveStatus
}
}
/* subclasses need to assign to this tooltip properties
mainText:
subText:
......@@ -74,18 +61,9 @@ PlasmaCore.ToolTipArea {
//BEGIN CONNECTIONS
property int creationId // used for item order tie breaking
onEffectiveStatusChanged: updateItemVisibility(abstractItem)
onCategoryChanged: updateItemVisibility(abstractItem)
onTextChanged: updateItemVisibility(abstractItem)
Component.onCompleted: {
creationId = root.creationIdCounter++
updateItemVisibility(abstractItem)
}
onContainsMouseChanged: {
if (inHiddenLayout && containsMouse) {
root.hiddenLayout.hoveredItem = abstractItem
root.hiddenLayout.currentIndex = index
}
}
......
/*
* Copyright 2020 Konrad Materka <materka@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import QtQuick 2.0
Loader {
id: itemLoader
Component.onCompleted: {
if (model.itemType === "Plasmoid" && model.hasApplet) {
itemLoader.setSource("PlasmoidItem.qml", {
"applet": model.applet,
"effectiveStatus": model.effectiveStatus
})
} else if (model.itemType === "StatusNotifier") {
itemLoader.setSource("StatusNotifierItem.qml", {
"model": model,
"effectiveStatus": model.effectiveStatus
})
}
}
}
......@@ -28,7 +28,6 @@ AbstractItem {
text: applet ? applet.title : ""
itemId: applet ? applet.pluginName : ""
category: applet ? plasmoid.nativeInterface.plasmoidCategory(applet) : "UnknownCategory"
mainText: applet ? applet.toolTipMainText : ""
subText: applet ? applet.toolTipSubText : ""
icon: applet ? applet.icon : ""
......@@ -80,9 +79,6 @@ AbstractItem {
preloadFullRepresentationItem(applet.fullRepresentationItem)
}
if (!applet) {
plasmoidContainer.destroy();
}
}
Connections {
......
......@@ -23,40 +23,31 @@ import org.kde.plasma.core 2.0 as PlasmaCore
AbstractItem {
id: taskIcon
itemId: Id
text: Title
mainText: ToolTipTitle != "" ? ToolTipTitle : Title
subText: ToolTipSubTitle
icon: ToolTipIcon != "" ? ToolTipIcon : Icon ? Icon : IconName
property var model
itemId: model.Id
text: model.Title
mainText: model.ToolTipTitle !== "" ? model.ToolTipTitle : model.Title
subText: model.ToolTipSubTitle
icon: model.ToolTipIcon !== "" ? model.ToolTipIcon : model.Icon ? model.Icon : model.IconName
textFormat: Text.AutoText
category: Category
status: {
switch (Status) {
case "Active":
return PlasmaCore.Types.ActiveStatus;
case "NeedsAttention":
return PlasmaCore.Types.NeedsAttentionStatus;
//just assume passive
default:
return PlasmaCore.Types.PassiveStatus;
}
}
status: model.status
iconItem: iconItem
PlasmaCore.IconItem {
id: iconItem
source: {
if (taskIcon.status === PlasmaCore.Types.NeedsAttentionStatus) {
if (AttentionIcon) {
return AttentionIcon
if (model.status === PlasmaCore.Types.NeedsAttentionStatus) {
if (model.AttentionIcon) {
return model.AttentionIcon
}
if (AttentionIconName) {
return AttentionIconName
if (model.AttentionIconName) {
return model.AttentionIconName
}
}
return Icon ? Icon : IconName
return model.Icon ? model.Icon : model.IconName
}
width: Math.min(parent.width, parent.height)
......@@ -78,7 +69,7 @@ AbstractItem {
switch (mouse.button) {
case Qt.LeftButton:
var service = statusNotifierSource.serviceForSource(DataEngineSource);
var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource);
var operation = service.operationDescription("Activate");
operation.x = pos.x;
operation.y = pos.y;
......@@ -97,7 +88,7 @@ AbstractItem {
break;
case Qt.MiddleButton:
var service = statusNotifierSource.serviceForSource(DataEngineSource);
var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource);
var operation = service.operationDescription("SecondaryActivate");
operation.x = pos.x;
......@@ -109,7 +100,7 @@ AbstractItem {
}
function openContextMenu(pos) {
var service = statusNotifierSource.serviceForSource(DataEngineSource);
var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource);
var operation = service.operationDescription("ContextMenu");
operation.x = pos.x;
operation.y = pos.y;
......@@ -123,14 +114,14 @@ AbstractItem {
onWheel: {
//don't send activateVertScroll with a delta of 0, some clients seem to break (kmix)
if (wheel.angleDelta.y !== 0) {
var service = statusNotifierSource.serviceForSource(DataEngineSource);
var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource);
var operation = service.operationDescription("Scroll");
operation.delta =wheel.angleDelta.y;
operation.direction = "Vertical";
service.startOperationCall(operation);
}
if (wheel.angleDelta.x !== 0) {
var service = statusNotifierSource.serviceForSource(DataEngineSource);
var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource);
var operation = service.operationDescription("Scroll");
operation.delta =wheel.angleDelta.x;
operation.direction = "Horizontal";
......
......@@ -23,6 +23,12 @@
#include <QList>
static const QList<QString> s_categoryOrder = {QStringLiteral("UnknownCategory"),
QStringLiteral("ApplicationStatus"),
QStringLiteral("Communications"),
QStringLiteral("SystemServices"),
QStringLiteral("Hardware")};
SortedSystemTrayModel::SortedSystemTrayModel(SortingType sorting, QObject *parent)
: QSortFilterProxyModel(parent),
m_sorting(sorting)
......@@ -36,6 +42,8 @@ bool SortedSystemTrayModel::lessThan(const QModelIndex &left, const QModelIndex
switch (m_sorting) {
case SortedSystemTrayModel::SortingType::ConfigurationPage:
return lessThanConfigurationPage(left, right);
case SortedSystemTrayModel::SortingType::SystemTray:
return lessThanSystemTray(left, right);
}
return QSortFilterProxyModel::lessThan(left, right);
......@@ -51,6 +59,21 @@ bool SortedSystemTrayModel::lessThanConfigurationPage(const QModelIndex &left, c
}
}
bool SortedSystemTrayModel::lessThanSystemTray(const QModelIndex &left, const QModelIndex &right) const
{
QVariant itemId = sourceModel()->data(left, static_cast<int>(BaseModel::BaseRole::ItemId));
if (itemId.isValid() && itemId.toString() == QLatin1String("org.kde.plasma.notifications")) {
return true;
}
const int categoriesComparison = compareCategoriesOrderly(left, right);
if (categoriesComparison == 0) {
return QSortFilterProxyModel::lessThan(left, right);
} else {
return categoriesComparison < 0;
}
}
int SortedSystemTrayModel::compareCategoriesAlphabetically(const QModelIndex &left, const QModelIndex &right) const
{
QVariant leftData = sourceModel()->data(left, static_cast<int>(BaseModel::BaseRole::Category));
......@@ -61,3 +84,24 @@ int SortedSystemTrayModel::compareCategoriesAlphabetically(const QModelIndex &le
return QString::localeAwareCompare(leftCategory, rightCategory);
}
int SortedSystemTrayModel::compareCategoriesOrderly(const QModelIndex &left, const QModelIndex &right) const
{
QVariant leftData = sourceModel()->data(left, static_cast<int>(BaseModel::BaseRole::Category));
QString leftCategory = leftData.isNull() ? QStringLiteral("UnknownCategory") : leftData.toString();
QVariant rightData = sourceModel()->data(right, static_cast<int>(BaseModel::BaseRole::Category));
QString rightCategory = rightData.isNull() ? QStringLiteral("UnknownCategory") : rightData.toString();
int leftIndex = s_categoryOrder.indexOf(leftCategory);
if (leftIndex == -1) {
leftIndex = s_categoryOrder.indexOf(QStringLiteral("UnknownCategory"));
}
int rightIndex = s_categoryOrder.indexOf(rightCategory);
if (rightIndex == -1) {
rightIndex = s_categoryOrder.indexOf(QStringLiteral("UnknownCategory"));
}
return leftIndex - rightIndex;
}
......@@ -26,7 +26,8 @@ class SortedSystemTrayModel : public QSortFilterProxyModel {
Q_OBJECT
public:
enum class SortingType {
ConfigurationPage
ConfigurationPage,
SystemTray
};
explicit SortedSystemTrayModel(SortingType sorting, QObject *parent = nullptr);
......@@ -36,8 +37,10 @@ protected:
private:
bool lessThanConfigurationPage(const QModelIndex &left, const QModelIndex &right) const;
bool lessThanSystemTray(const QModelIndex &left, const QModelIndex &right) const;
int compareCategoriesAlphabetically(const QModelIndex &left, const QModelIndex &right) const;
int compareCategoriesOrderly(const QModelIndex &left, const QModelIndex &right) const;
SortingType m_sorting;
};
......
......@@ -43,8 +43,8 @@
SystemTray::SystemTray(QObject *parent, const QVariantList &args)
: Plasma::Containment(parent, args),
m_availablePlasmoidsModel(nullptr),
m_systemTrayModel(new SystemTrayModel(this)),
m_sortedSystemTrayModel(nullptr),
m_configSystemTrayModel(nullptr)
{
setHasConfigurationInterface(true);
......@@ -53,8 +53,10 @@ SystemTray::SystemTray(QObject *parent, const QVariantList &args)
PlasmoidModel *currentPlasmoidsModel = new PlasmoidModel(m_systemTrayModel);
connect(this, &SystemTray::appletAdded, currentPlasmoidsModel, &PlasmoidModel::addApplet);
connect(this, &SystemTray::appletRemoved, currentPlasmoidsModel, &PlasmoidModel::removeApplet);
connect(this, &SystemTray::configurationChanged, currentPlasmoidsModel, &PlasmoidModel::onConfigurationChanged);
m_statusNotifierModel = new StatusNotifierModel(m_systemTrayModel);
connect(this, &SystemTray::configurationChanged, m_statusNotifierModel, &StatusNotifierModel::onConfigurationChanged);
m_systemTrayModel->addSourceModel(currentPlasmoidsModel);
m_systemTrayModel->addSourceModel(m_statusNotifierModel);
......@@ -315,39 +317,47 @@ QPointF SystemTray::popupPosition(QQuickItem* visualParent, int x, int y)
return pos;
}
void SystemTray::reorderItemBefore(QQuickItem* before, QQuickItem* after)
bool SystemTray::isSystemTrayApplet(const QString &appletId)
{
if (!before || !after) {
return;
}
return m_systrayApplets.contains(appletId);
}
before->setVisible(false);
before->setParentItem(after->parentItem());
before->stackBefore(after);
before->setVisible(true);
Plasma::Service *SystemTray::serviceForSource(const QString &source)
{
return m_statusNotifierModel->serviceForSource(source);
}
void SystemTray::reorderItemAfter(QQuickItem* after, QQuickItem* before)
void SystemTray::restoreContents(KConfigGroup &group)
{
if (!before || !after) {
return;
QStringList newKnownItems;
QStringList newExtraItems;
KConfigGroup general = group.group("General");
QStringList knownItems = general.readEntry("knownItems", QStringList());
QStringList extraItems = general.readEntry("extraItems", QStringList());
//Add every plasmoid that is both not enabled explicitly and not already known
for (int i = 0; i < m_defaultPlasmoids.length(); ++i) {
QString candidate = m_defaultPlasmoids[i];
if (!knownItems.contains(candidate)) {
newKnownItems.append(candidate);
if (!extraItems.contains(candidate)) {
newExtraItems.append(candidate);
}
}
}
after->setVisible(false);
after->setParentItem(before->parentItem());
after->stackAfter(before);
after->setVisible(true);
}
if (newExtraItems.length() > 0) {
general.writeEntry("extraItems", extraItems + newExtraItems);
}
if (newKnownItems.length() > 0) {
general.writeEntry("knownItems", knownItems + newKnownItems);
}
bool SystemTray::isSystemTrayApplet(const QString &appletId)
{
return m_systrayApplets.contains(appletId);
}
setAllowedPlasmoids(general.readEntry("extraItems", QStringList()));
void SystemTray::restoreContents(KConfigGroup &group)
{
Q_UNUSED(group);
//NOTE: RestoreContents shouldn't do anything here because is too soon, so have an empty reimplementation
emit configurationChanged(config());
}
void SystemTray::restorePlasmoids()
......@@ -430,26 +440,28 @@ void SystemTray::restorePlasmoids()
initDBusActivatables();
}
QAbstractItemModel *SystemTray::configSystemTrayModel()
void SystemTray::configChanged()
{
if (!m_configSystemTrayModel) {
m_configSystemTrayModel = new SortedSystemTrayModel(SortedSystemTrayModel::SortingType::ConfigurationPage, this);
m_configSystemTrayModel->setSourceModel(m_systemTrayModel);
}
return m_configSystemTrayModel;
Containment::configChanged();
emit configurationChanged(config());
}
QStringList SystemTray::defaultPlasmoids() const
QAbstractItemModel *SystemTray::systemTrayModel()
{
return m_defaultPlasmoids;
if (!m_sortedSystemTrayModel) {
m_sortedSystemTrayModel = new SortedSystemTrayModel(SortedSystemTrayModel::SortingType::SystemTray, this);
m_sortedSystemTrayModel->setSourceModel(m_systemTrayModel);
}
return m_sortedSystemTrayModel;
}
QAbstractItemModel* SystemTray::availablePlasmoids()
QAbstractItemModel *SystemTray::configSystemTrayModel()
{
if (!m_availablePlasmoidsModel) {
m_availablePlasmoidsModel = new PlasmoidModel(this);
if (!m_configSystemTrayModel) {
m_configSystemTrayModel = new SortedSystemTrayModel(SortedSystemTrayModel::SortingType::ConfigurationPage, this);
m_configSystemTrayModel->setSourceModel(m_systemTrayModel);
}
return m_availablePlasmoidsModel;
return m_configSystemTrayModel;
}
QStringList SystemTray::allowedPlasmoids() const
......
......@@ -40,10 +40,9 @@ class SortedSystemTrayModel;
class SystemTray : public Plasma::Containment
{
Q_OBJECT
Q_PROPERTY(QAbstractItemModel* systemTrayModel READ systemTrayModel CONSTANT)
Q_PROPERTY(QAbstractItemModel* configSystemTrayModel READ configSystemTrayModel CONSTANT)
Q_PROPERTY(QAbstractItemModel* availablePlasmoids READ availablePlasmoids CONSTANT)
Q_PROPERTY(QStringList allowedPlasmoids READ allowedPlasmoids WRITE setAllowedPlasmoids NOTIFY allowedPlasmoidsChanged)
Q_PROPERTY(QStringList defaultPlasmoids READ defaultPlasmoids CONSTANT)
public:
SystemTray( QObject *parent, const QVariantList &args );
......@@ -54,11 +53,11 @@ public:
void restoreContents(KConfigGroup &group) override;
void restorePlasmoids();
QAbstractItemModel *configSystemTrayModel();
void configChanged() override;
QStringList defaultPlasmoids() const;
QAbstractItemModel *systemTrayModel();
QAbstractItemModel* availablePlasmoids();
QAbstractItemModel *configSystemTrayModel();
QStringList allowedPlasmoids() const;
void setAllowedPlasmoids(const QStringList &allowed);
......@@ -92,23 +91,13 @@ public:
*/
Q_INVOKABLE QPointF popupPosition(QQuickItem* visualParent, int x, int y);
/**
* Reparent the item "before" with the same parent as the item "after",
* then restack it before it, using QQuickITem::stackBefore.
* used to quickly reorder icons in the systray (or hidden popup)
* @see QQuickITem::stackBefore
*/
Q_INVOKABLE void reorderItemBefore(QQuickItem* before, QQuickItem* after);
Q_INVOKABLE bool isSystemTrayApplet(const QString &appletId);
/**
* Reparent the item "after" with the same parent as the item "before",
* then restack it after it, using QQuickITem::stackAfter.
* used to quickly reorder icons in the systray (or hidden popup)
* @see QQuickITem::stackAfter
* @returns a Plasma::Service given a source name
* @param source source name we want a service of
*/
Q_INVOKABLE void reorderItemAfter(QQuickItem* after, QQuickItem* before);
Q_INVOKABLE bool isSystemTrayApplet(const QString &appletId);
Q_INVOKABLE Plasma::Service *serviceForSource(const QString &source);
private Q_SLOTS:
void serviceNameFetchFinished(QDBusPendingCallWatcher* watcher, const QDBusConnection &connection);
......@@ -120,6 +109,7 @@ private:
Q_SIGNALS:
void allowedPlasmoidsChanged();
void configurationChanged(const KConfigGroup &config);
private: