panel: Refactor panel and implement blue ocean mockups

This commit is contained in:
Devin Lin 2021-12-22 23:29:00 +00:00
parent 7f9a9ca60c
commit 6653ad6e54
101 changed files with 4004 additions and 2339 deletions

View file

@ -1,3 +1,3 @@
add_subdirectory(mobilehomescreencomponents)
install(DIRECTORY mobileshell/ DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/mobileshell)
add_subdirectory(mobilehomescreencomponents)
add_subdirectory(mobileshell)

View file

@ -5,7 +5,6 @@ set(mobilehomescreencomponentsplugin_SRCS
applicationlistmodel.cpp
favoritesmodel.cpp
homescreenutils.cpp
quicksettingsmodel.cpp
)
add_library(mobilehomescreencomponentsplugin ${mobilehomescreencomponentsplugin_SRCS})

View file

@ -25,15 +25,11 @@
#include "applicationlistmodel.h"
#include "favoritesmodel.h"
#include "homescreenutils.h"
#include "quicksettingsmodel.h"
void MobileHomeScreenComponentsPlugin::registerTypes(const char *uri)
{
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.mobilehomescreencomponents"));
qmlRegisterType<QuickSetting>(uri, 0, 1, "QuickSetting");
qmlRegisterType<QuickSettingsModel>(uri, 0, 1, "QuickSettingsModel");
qmlRegisterSingletonType<HomeScreenUtils>(uri, 0, 1, "HomeScreenUtils",
[](QQmlEngine *, QJSEngine *) {
return new HomeScreenUtils{};

View file

@ -0,0 +1,40 @@
# SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
# SPDX-License-Identifier: GPL-2.0-or-later
qt_add_dbus_interfaces(DBUS_SRCS dbus/org.kde.KWin.ScreenShot2.xml
dbus/org.kde.KScreen.xml)
set(mobileshellplugin_SRCS
mobileshellplugin.cpp
shellutil.cpp
quicksettingsmodel.cpp
notifications/notificationthumbnailer.cpp
notifications/notificationfilemenu.cpp
${DBUS_SRCS}
)
add_library(mobileshellplugin ${mobileshellplugin_SRCS})
target_link_libraries(mobileshellplugin
PUBLIC
Qt::Core
PRIVATE
Qt::DBus
Qt::Qml
Qt::Gui
Qt::Quick
KF5::ConfigWidgets # for KStandardAction
KF5::KIOGui
KF5::Plasma
KF5::I18n
KF5::Notifications
KF5::PlasmaQuick
KF5::KIOGui
KF5::KIOWidgets # for PreviewJob
KF5::WaylandClient
KF5::Service
)
install(TARGETS mobileshellplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/mobileshell)
install(DIRECTORY qml/ DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/mobileshell)

View file

@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <QQmlContext>
#include <QQuickItem>
#include "mobileshellplugin.h"
#include "notifications/notificationfilemenu.h"
#include "notifications/notificationthumbnailer.h"
#include "quicksettingsmodel.h"
#include "shellutil.h"
void MobileShellPlugin::registerTypes(const char *uri)
{
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.mobileshell"));
qmlRegisterSingletonType<ShellUtil>(uri, 1, 0, "ShellUtil", [](QQmlEngine *, QJSEngine *) -> QObject * {
return ShellUtil::instance();
});
qmlRegisterType<QuickSetting>(uri, 1, 0, "QuickSetting");
qmlRegisterType<QuickSettingsModel>(uri, 1, 0, "QuickSettingsModel");
// notifications
qmlRegisterType<NotificationThumbnailer>(uri, 1, 0, "NotificationThumbnailer");
qmlRegisterType<NotificationFileMenu>(uri, 1, 0, "NotificationFileMenu");
}

View file

@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QUrl>
#include <QQmlEngine>
#include <QQmlExtensionPlugin>
class MobileShellPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
public:
void registerTypes(const char *uri) override;
};

View file

@ -0,0 +1,231 @@
/*
SPDX-FileCopyrightText: 2016, 2019 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "notificationfilemenu.h"
#include <QApplication>
#include <QClipboard>
#include <QIcon>
#include <QMenu>
#include <QMimeData>
#include <QQuickItem>
#include <QQuickWindow>
#include <QTimer>
#include <KConfigGroup>
#include <KFileItemActions>
#include <KFileItemListProperties>
#include <KLocalizedString>
#include <KPropertiesDialog>
#include <KProtocolManager>
#include <KSharedConfig>
#include <KStandardAction>
#include <KUrlMimeData>
#include <KIO/CopyJob> // for KIO::trash
#include <KIO/DeleteJob>
#include <KIO/FileUndoManager>
#include <KIO/JobUiDelegate>
#include <KIO/OpenFileManagerWindowJob>
NotificationFileMenu::NotificationFileMenu(QObject *parent)
: QObject(parent)
{
}
NotificationFileMenu::~NotificationFileMenu() = default;
QUrl NotificationFileMenu::url() const
{
return m_url;
}
void NotificationFileMenu::setUrl(const QUrl &url)
{
if (m_url != url) {
m_url = url;
Q_EMIT urlChanged();
}
}
QQuickItem *NotificationFileMenu::visualParent() const
{
return m_visualParent.data();
}
void NotificationFileMenu::setVisualParent(QQuickItem *visualParent)
{
if (m_visualParent.data() == visualParent) {
return;
}
if (m_visualParent) {
disconnect(m_visualParent.data(), nullptr, this, nullptr);
}
m_visualParent = visualParent;
if (m_visualParent) {
connect(m_visualParent.data(), &QObject::destroyed, this, &NotificationFileMenu::visualParentChanged);
}
Q_EMIT visualParentChanged();
}
bool NotificationFileMenu::visible() const
{
return m_visible;
}
void NotificationFileMenu::setVisible(bool visible)
{
if (m_visible == visible) {
return;
}
if (visible) {
open(0, 0);
}
}
void NotificationFileMenu::open(int x, int y)
{
if (!m_visualParent || !m_visualParent->window()) {
return;
}
if (!m_url.isValid()) {
return;
}
KFileItem fileItem(m_url);
auto menu = new QMenu();
menu->setAttribute(Qt::WA_DeleteOnClose, true);
connect(menu, &QMenu::triggered, this, &NotificationFileMenu::actionTriggered);
connect(menu, &QMenu::aboutToHide, this, [this] {
m_visible = false;
Q_EMIT visibleChanged();
});
if (KProtocolManager::supportsListing(m_url)) {
QAction *openContainingFolderAction = menu->addAction(QIcon::fromTheme(QStringLiteral("folder-open")), i18n("Open Containing Folder"));
connect(openContainingFolderAction, &QAction::triggered, [this] {
KIO::highlightInFileManager({m_url});
});
}
auto actions = new KFileItemActions(menu);
KFileItemListProperties itemProperties(KFileItemList({fileItem}));
actions->setItemListProperties(itemProperties);
actions->setParentWidget(menu);
actions->insertOpenWithActionsTo(nullptr, menu, QStringList());
// KStandardAction? But then the Ctrl+C shortcut makes no sense in this context
QAction *copyAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy"));
connect(copyAction, &QAction::triggered, this, [fileItem] {
// inspired by KDirModel::mimeData()
auto data = new QMimeData(); // who cleans it up?
KUrlMimeData::setUrls({fileItem.url()}, {fileItem.mostLocalUrl()}, data);
QApplication::clipboard()->setMimeData(data);
});
QAction *copyPathAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy-path")), i18nc("@action:incontextmenu", "Copy Location"));
connect(copyPathAction, &QAction::triggered, this, [fileItem] {
QString path = fileItem.localPath();
if (path.isEmpty()) {
path = fileItem.url().toDisplayString();
}
QApplication::clipboard()->setText(path);
});
menu->addSeparator();
const bool canTrash = itemProperties.isLocal() && itemProperties.supportsMoving();
if (canTrash) {
auto moveToTrashLambda = [this] {
const QList<QUrl> urls{m_url};
KIO::JobUiDelegate uiDelegate;
if (uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
auto *job = KIO::trash(urls);
job->uiDelegate()->setAutoErrorHandlingEnabled(true);
KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, urls, QUrl(QStringLiteral("trash:/")), job);
}
};
auto moveToTrashAction = KStandardAction::moveToTrash(this, moveToTrashLambda, menu);
moveToTrashAction->setShortcut({}); // Can't focus notification to press Delete
menu->addAction(moveToTrashAction);
}
KConfigGroup cg(KSharedConfig::openConfig(), "KDE");
const bool showDeleteCommand = cg.readEntry("ShowDeleteCommand", false);
if (itemProperties.supportsDeleting() && (!canTrash || showDeleteCommand)) {
auto deleteLambda = [this] {
const QList<QUrl> urls{m_url};
KIO::JobUiDelegate uiDelegate;
if (uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) {
auto *job = KIO::del(urls);
job->uiDelegate()->setAutoErrorHandlingEnabled(true);
}
};
auto deleteAction = KStandardAction::deleteFile(this, deleteLambda, menu);
deleteAction->setShortcut({});
menu->addAction(deleteAction);
}
menu->addSeparator();
actions->addActionsTo(menu);
menu->addSeparator();
QAction *propertiesAction = menu->addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Properties"));
connect(propertiesAction, &QAction::triggered, [fileItem] {
KPropertiesDialog *dialog = new KPropertiesDialog(fileItem.url());
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
});
// this is a workaround where Qt will fail to realize a mouse has been released
// this happens if a window which does not accept focus spawns a new window that takes focus and X grab
// whilst the mouse is depressed
// https://bugreports.qt.io/browse/QTBUG-59044
// this causes the next click to go missing
// by releasing manually we avoid that situation
auto ungrabMouseHack = [this]() {
if (m_visualParent && m_visualParent->window() && m_visualParent->window()->mouseGrabberItem()) {
m_visualParent->window()->mouseGrabberItem()->ungrabMouse();
}
};
QTimer::singleShot(0, m_visualParent, ungrabMouseHack);
// end workaround
QPoint pos;
if (x == -1 && y == -1) { // align "bottom left of visualParent"
menu->adjustSize();
pos = m_visualParent->mapToGlobal(QPointF(0, m_visualParent->height())).toPoint();
if (!qApp->isRightToLeft()) {
pos.rx() += m_visualParent->width();
pos.rx() -= menu->width();
}
} else {
pos = m_visualParent->mapToGlobal(QPointF(x, y)).toPoint();
}
menu->setAttribute(Qt::WA_TranslucentBackground);
menu->winId();
menu->windowHandle()->setTransientParent(m_visualParent->window());
menu->popup(pos);
m_visible = true;
Q_EMIT visibleChanged();
}

View file

@ -0,0 +1,49 @@
/*
SPDX-FileCopyrightText: 2016, 2019 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include <QObject>
#include <QPointer>
#include <QQuickItem>
#include <QUrl>
class QAction;
class NotificationFileMenu : public QObject
{
Q_OBJECT
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(QQuickItem *visualParent READ visualParent WRITE setVisualParent NOTIFY visualParentChanged)
Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged)
public:
explicit NotificationFileMenu(QObject *parent = nullptr);
~NotificationFileMenu() override;
QUrl url() const;
void setUrl(const QUrl &url);
QQuickItem *visualParent() const;
void setVisualParent(QQuickItem *visualParent);
bool visible() const;
void setVisible(bool visible);
Q_INVOKABLE void open(int x, int y);
Q_SIGNALS:
void actionTriggered(QAction *action);
void urlChanged();
void visualParentChanged();
void visibleChanged();
private:
QUrl m_url;
QPointer<QQuickItem> m_visualParent;
bool m_visible = false;
};

View file

@ -0,0 +1,157 @@
/*
SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "notificationthumbnailer.h"
#include <KIO/PreviewJob>
#include <QApplication>
#include <QClipboard>
#include <QIcon>
#include <QMenu>
#include <QMimeData>
#include <QQuickItem>
#include <QQuickWindow>
#include <QTimer>
#include <KConfigGroup>
#include <KFileItemListProperties>
#include <KLocalizedString>
#include <KPropertiesDialog>
#include <KProtocolManager>
#include <KSharedConfig>
#include <KUrlMimeData>
#include <KIO/OpenFileManagerWindowJob>
NotificationThumbnailer::NotificationThumbnailer(QObject *parent)
: QObject(parent)
{
}
NotificationThumbnailer::~NotificationThumbnailer() = default;
void NotificationThumbnailer::classBegin()
{
}
void NotificationThumbnailer::componentComplete()
{
m_inited = true;
generatePreview();
}
QUrl NotificationThumbnailer::url() const
{
return m_url;
}
void NotificationThumbnailer::setUrl(const QUrl &url)
{
if (m_url != url) {
m_url = url;
Q_EMIT urlChanged();
generatePreview();
}
}
QSize NotificationThumbnailer::size() const
{
return m_size;
}
void NotificationThumbnailer::setSize(const QSize &size)
{
if (m_size != size) {
m_size = size;
Q_EMIT sizeChanged();
generatePreview();
}
}
bool NotificationThumbnailer::busy() const
{
return m_busy;
}
bool NotificationThumbnailer::hasPreview() const
{
return !m_pixmap.isNull();
}
QPixmap NotificationThumbnailer::pixmap() const
{
return m_pixmap;
}
QSize NotificationThumbnailer::pixmapSize() const
{
return m_pixmap.size();
}
QString NotificationThumbnailer::iconName() const
{
return m_iconName;
}
bool NotificationThumbnailer::menuVisible() const
{
return m_menuVisible;
}
void NotificationThumbnailer::generatePreview()
{
if (!m_inited) {
return;
}
if (!m_url.isValid() || !m_url.isLocalFile() || !m_size.isValid() || m_size.isEmpty()) {
return;
}
auto maxSize = qMax(m_size.width(), m_size.height());
KConfigGroup previewSettings(KSharedConfig::openConfig(QStringLiteral("dolphinrc")), "PreviewSettings");
const QStringList enabledPlugins = previewSettings.readEntry("Plugins", KIO::PreviewJob::defaultPlugins());
KIO::PreviewJob *job = KIO::filePreview(KFileItemList({KFileItem(m_url)}), QSize(maxSize, maxSize), &enabledPlugins);
job->setScaleType(KIO::PreviewJob::Scaled);
job->setIgnoreMaximumSize(true);
connect(job, &KIO::PreviewJob::gotPreview, this, [this](const KFileItem &item, const QPixmap &preview) {
Q_UNUSED(item);
m_pixmap = preview;
Q_EMIT pixmapChanged();
if (!m_iconName.isEmpty()) {
m_iconName.clear();
Q_EMIT iconNameChanged();
}
});
connect(job, &KIO::PreviewJob::failed, this, [this](const KFileItem &item) {
m_pixmap = QPixmap();
Q_EMIT pixmapChanged();
const QString &iconName = item.determineMimeType().iconName();
if (m_iconName != iconName) {
m_iconName = iconName;
Q_EMIT iconNameChanged();
}
});
connect(job, &KJob::result, this, [this] {
m_busy = false;
Q_EMIT busyChanged();
});
m_busy = true;
Q_EMIT busyChanged();
job->start();
}

View file

@ -0,0 +1,79 @@
/*
SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include <QObject>
#include <QQmlParserStatus>
#include <QPixmap>
#include <QSize>
#include <QUrl>
class NotificationThumbnailer : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(QSize size READ size WRITE setSize NOTIFY sizeChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
Q_PROPERTY(bool hasPreview READ hasPreview NOTIFY pixmapChanged)
Q_PROPERTY(QPixmap pixmap READ pixmap NOTIFY pixmapChanged)
Q_PROPERTY(QSize pixmapSize READ pixmapSize NOTIFY pixmapChanged)
Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged)
Q_PROPERTY(bool menuVisible READ menuVisible NOTIFY menuVisibleChanged)
public:
explicit NotificationThumbnailer(QObject *parent = nullptr);
~NotificationThumbnailer() override;
QUrl url() const;
void setUrl(const QUrl &url);
QSize size() const;
void setSize(const QSize &size);
bool busy() const;
bool hasPreview() const;
QPixmap pixmap() const;
QSize pixmapSize() const;
QString iconName() const;
bool menuVisible() const;
void classBegin() override;
void componentComplete() override;
Q_SIGNALS:
void menuVisibleChanged();
void urlChanged();
void sizeChanged();
void busyChanged();
void pixmapChanged();
void iconNameChanged();
private:
void generatePreview();
bool m_inited = false;
bool m_menuVisible = false;
QUrl m_url;
QSize m_size;
bool m_busy = false;
QPixmap m_pixmap;
QString m_iconName;
};

View file

@ -0,0 +1,256 @@
/*
* SPDX-FileCopyrightText: 2014 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import "../components" as Components
/**
* Swipe top left - minimized quick settings, fully shown notifications list
* Swipe top right - full quick settings, minimized notifications list
* Swiping up and down on notifications list toggle minimized/maximized
* Swiping up and down on panel hides and shows the panel
*/
NanoShell.FullScreenOverlay {
id: window
/**
* The amount of pixels moved by touch/mouse in the process of opening/closing the panel.
*/
property real offset: 0
/**
* Whether the panel is being dragged.
*/
property bool dragging: false
/**
* Whether the panel is open after touch/mouse release from the first opening swipe.
*/
property bool opened: false
/**
* Direction the panel is currently moving in.
*/
property int direction: Components.Direction.None
property int mode: (height > width && width <= largePortraitThreshold) ? ActionDrawer.Portrait : ActionDrawer.Landscape
/**
* At some point, even if the screen is technically portrait, if we have a ton of width it'd be best to just show the landscape mode.
*/
readonly property real largePortraitThreshold: PlasmaCore.Units.gridUnit * 35
enum Mode {
Portrait = 0,
Landscape
}
width: Screen.width
height: Screen.height
color: "transparent"
onOpenedChanged: {
if (opened) flickable.focus = true;
}
onActiveChanged: {
if (!active) {
close();
}
}
property real oldOffset
onOffsetChanged: {
if (offset < 0) {
offset = 0;
}
window.direction = (oldOffset === offset)
? Components.Direction.None
: (offset > oldOffset ? Components.Direction.Down : Components.Direction.Up);
oldOffset = offset;
// close panel immediately after panel is not shown, and the flickable is not being dragged
if (opened && window.offset <= 0 && !flickable.dragging && !closeAnim.running && !openAnim.running) {
window.updateState();
focus = false;
}
}
function cancelAnimations() {
closeAnim.stop();
openAnim.stop();
}
function open() {
cancelAnimations();
openAnim.restart();
}
function closeImmediately() {
cancelAnimations();
offset = 0;
closeAnim.finished();
}
function close() {
cancelAnimations();
closeAnim.restart();
}
function expand() {
cancelAnimations();
expandAnim.restart();
}
function updateState() {
cancelAnimations();
let openThreshold = PlasmaCore.Units.gridUnit;
if (window.offset <= 0) {
// close immediately, so that we don't have to wait PlasmaCore.Units.longDuration
window.visible = false;
close();
} else if (window.direction === Components.Direction.None || !window.opened) {
if (window.offset < openThreshold) {
close();
} else {
open();
}
} else if (window.offset > contentContainerLoader.maximizedQuickSettingsOffset) {
expand();
} else if (window.offset > contentContainerLoader.minimizedQuickSettingsOffset) {
if (window.direction === Components.Direction.Down) {
expand();
} else {
open();
}
} else if (window.direction === Components.Direction.Down) {
open();
} else {
close();
}
}
Timer {
id: updateStateTimer
interval: 0
onTriggered: updateState()
}
PropertyAnimation on offset {
id: closeAnim
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
to: 0
onFinished: {
window.visible = false;
window.opened = false;
}
}
PropertyAnimation on offset {
id: openAnim
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
to: contentContainerLoader.minimizedQuickSettingsOffset
onFinished: window.opened = true
}
PropertyAnimation on offset {
id: expandAnim
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
to: contentContainerLoader.maximizedQuickSettingsOffset
onFinished: window.opened = true;
}
Flickable {
id: flickable
anchors.fill: parent
contentWidth: window.width
contentHeight: window.height + 999999
contentY: contentHeight / 2
// if the recent window.offset change was due to this flickable
property bool offsetChangedDueToContentY: false
Connections {
target: window
function onOffsetChanged() {
if (!flickable.offsetChangedDueToContentY) {
// ensure the flickable's contentY is not moving when other sources change window.offset
flickable.cancelFlick();
}
flickable.offsetChangedDueToContentY = false;
}
}
property real oldContentY
onContentYChanged: {
offsetChangedDueToContentY = true;
window.offset += oldContentY - contentY;
oldContentY = contentY;
}
onMovementStarted: {
window.cancelAnimations();
window.dragging = true;
}
onFlickStarted: window.dragging = true;
onMovementEnded: {
window.dragging = false;
window.updateState();
}
onFlickEnded: {
window.dragging = true;
window.updateState();
}
onDraggingChanged: {
if (!dragging) {
window.dragging = false;
flickable.cancelFlick();
window.updateState();
}
}
// the flickable is only used to measure drag changes, we implement our own UI component movements
// the window element is not affected by contentY changes (it's effectively anchored to the flickable)
Loader {
id: contentContainerLoader
property real minimizedQuickSettingsOffset: item ? item.minimizedQuickSettingsOffset : 0
property real maximizedQuickSettingsOffset: item ? item.maximizedQuickSettingsOffset : 0
y: flickable.contentY
width: window.width
height: window.height
sourceComponent: window.mode == ActionDrawer.Portrait ? portraitContentContainer : landscapeContentContainer
}
Component {
id: portraitContentContainer
PortraitContentContainer {
actionDrawer: window
width: window.width
height: window.height
}
}
Component {
id: landscapeContentContainer
LandscapeContentContainer {
actionDrawer: window
width: window.width
height: window.height
}
}
}
}

View file

@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
/**
* Component that triggers the opening and closing of an ActionDrawer when dragged on with touch or mouse.
*/
MouseArea {
id: root
required property ActionDrawer actionDrawer
property int oldMouseY: 0
function startSwipe() {
actionDrawer.cancelAnimations();
actionDrawer.dragging = true;
actionDrawer.opened = false;
// must be after properties other are set, we cannot have actionDrawer.updateState() be called
actionDrawer.offset = 0;
actionDrawer.oldOffset = 0;
actionDrawer.visible = true;
}
function endSwipe() {
actionDrawer.dragging = false;
actionDrawer.updateState();
}
function updateOffset(offsetY) {
actionDrawer.offset += offsetY;
}
anchors.fill: parent
onPressed: {
oldMouseY = mouse.y;
startSwipe();
}
onReleased: endSwipe()
onCanceled: endSwipe()
onPositionChanged: {
updateOffset(mouse.y - oldMouseY);
oldMouseY = mouse.y;
}
}

View file

@ -0,0 +1,119 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import "../components" as Components
import "../widgets" as Widgets
import "quicksettings"
/**
* Root element that contains all of the ActionDrawer's contents, and is anchored to the screen.
*/
PlasmaCore.ColorScope {
id: root
required property var actionDrawer
readonly property real minimizedQuickSettingsOffset: height
readonly property real maximizedQuickSettingsOffset: height
colorGroup: PlasmaCore.Theme.ViewColorGroup
function applyMinMax(val) {
return Math.max(0, Math.min(1, val));
}
// fullscreen background
Rectangle {
anchors.fill: parent
color: Qt.rgba(PlasmaCore.Theme.backgroundColor.r, PlasmaCore.Theme.backgroundColor.g, PlasmaCore.Theme.backgroundColor.b, 0.95)
opacity: Math.max(0, Math.min(1, actionDrawer.offset / root.minimizedQuickSettingsOffset))
}
PlasmaCore.DataSource {
id: timeSource
engine: "time"
connectedSources: ["Local"]
interval: 60 * 1000
}
// left side
ColumnLayout {
opacity: applyMinMax(root.actionDrawer.offset / root.maximizedQuickSettingsOffset)
spacing: 0
anchors {
top: parent.top
topMargin: Math.min(root.width, root.height) * 0.06
bottom: parent.bottom
bottomMargin: Math.min(root.width, root.height) * 0.06
right: quickSettings.left
rightMargin: Math.min(root.width, root.height) * 0.06
left: parent.left
leftMargin: Math.min(root.width, root.height) * 0.06
}
PlasmaComponents.Label {
id: clock
text: Qt.formatTime(timeSource.data.Local.DateTime, MobileShell.ShellUtil.isSystem24HourFormat ? "h:mm" : "h:mm ap")
verticalAlignment: Qt.AlignTop
Layout.fillWidth: true
font.pixelSize: Math.min(40, Math.min(root.width, root.height) * 0.1)
font.weight: Font.ExtraLight
elide: Text.ElideRight
}
PlasmaComponents.Label {
id: date
text: Qt.formatDate(timeSource.data.Local.DateTime, "ddd MMMM d")
verticalAlignment: Qt.AlignTop
color: PlasmaCore.ColorScope.disabledTextColor
Layout.fillWidth: true
Layout.topMargin: PlasmaCore.Units.smallSpacing
font.pixelSize: Math.min(20, Math.min(root.width, root.height) * 0.05)
font.weight: Font.Light
}
Widgets.NotificationsWidget {
// don't allow notifications widget to get too wide
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: Math.min(root.width, root.height) * 0.02
}
}
// right sidebar
QuickSettingsPanel {
id: quickSettings
height: Math.min(root.height, Math.max(quickSettings.minimizedHeight, actionDrawer.offset))
width: intendedWidth
readonly property real intendedWidth: 360
anchors.top: parent.top
anchors.right: parent.right
actionDrawer: root.actionDrawer
fullHeight: root.height
transform: Translate {
id: translate
y: Math.min(root.actionDrawer.offset - quickSettings.minimizedHeight, 0)
}
}
}

View file

@ -0,0 +1,87 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import "../components" as Components
import "../widgets" as Widgets
import "quicksettings"
/**
* Root element that contains all of the ActionDrawer's contents, and is anchored to the screen.
*/
PlasmaCore.ColorScope {
id: root
required property var actionDrawer
readonly property real minimizedQuickSettingsOffset: quickSettings.minimizedHeight
readonly property real maximizedQuickSettingsOffset: minimizedQuickSettingsOffset + quickSettings.maxAddedHeight
colorGroup: PlasmaCore.Theme.ViewColorGroup
function applyMinMax(val) {
return Math.max(0, Math.min(1, val));
}
// fullscreen background
Rectangle {
anchors.fill: parent
color: Qt.rgba(PlasmaCore.Theme.backgroundColor.r, PlasmaCore.Theme.backgroundColor.g, PlasmaCore.Theme.backgroundColor.b, 0.95)
opacity: Math.max(0, Math.min(1, actionDrawer.offset / root.minimizedQuickSettingsOffset))
}
QuickSettingsDrawer {
id: quickSettings
z: 1 // ensure it's above notifications
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
actionDrawer: root.actionDrawer
// opacity and move animation
property real offsetDist: actionDrawer.offset - minimizedQuickSettingsOffset
property real totalOffsetDist: maximizedQuickSettingsOffset - minimizedQuickSettingsOffset
minimizedToFullProgress: actionDrawer.opened ? applyMinMax(offsetDist / totalOffsetDist) : 0
addedHeight: {
if (!actionDrawer.opened) {
// over-scroll effect for initial opening
let progress = (root.actionDrawer.offset - minimizedQuickSettingsOffset) / quickSettings.maxAddedHeight;
let effectProgress = Math.atan(Math.max(0, progress));
return quickSettings.maxAddedHeight * 0.25 * effectProgress;
} else {
return Math.max(0, Math.min(quickSettings.maxAddedHeight, root.actionDrawer.offset - minimizedQuickSettingsOffset));
}
}
transform: Translate {
id: translate
y: Math.min(root.actionDrawer.offset - minimizedQuickSettingsOffset, 0)
}
}
Widgets.NotificationsWidget {
anchors {
top: quickSettings.top
topMargin: quickSettings.height + translate.y
bottom: parent.bottom
bottomMargin: PlasmaCore.Units.largeSpacing
left: parent.left
leftMargin: PlasmaCore.Units.largeSpacing
right: parent.right
rightMargin: PlasmaCore.Units.largeSpacing
}
opacity: applyMinMax(root.actionDrawer.offset / root.minimizedQuickSettingsOffset)
}
}

View file

@ -1,13 +1,12 @@
/*
* SPDX-FileCopyrightText: 2012-2013 Daniel Nicoletti <dantti12@gmail.com>
* SPDX-FileCopyrightText: 2013, 2015 Kai Uwe Broulik <kde@privat.broulik.de>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.0
import QtQuick.Controls 2.15
import QtQuick 2.15
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.12 as Kirigami
@ -15,7 +14,7 @@ import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PC3
Item {
id: brightnessRoot
id: root
implicitHeight: brightnessRow.implicitHeight
@ -23,14 +22,31 @@ Item {
property bool disableBrightnessUpdate: true
readonly property int maximumScreenBrightness: pmSource.data["PowerDevil"] ? pmSource.data["PowerDevil"]["Maximum Screen Brightness"] || 0 : 0
property QtObject updateScreenBrightnessJob
function updateBrightnessUI() {
if (updateScreenBrightnessJob)
return;
root.disableBrightnessUpdate = true;
console.log(pmSource.data["PowerDevil"]["Screen Brightness"]);
root.screenBrightness = pmSource.data["PowerDevil"]["Screen Brightness"];
root.disableBrightnessUpdate = false;
}
onScreenBrightnessChanged: {
brightnessSlider.value = brightnessRoot.screenBrightness
brightnessSlider.value = root.screenBrightness
if (!disableBrightnessUpdate) {
var service = pmSource.serviceForSource("PowerDevil");
var operation = service.operationDescription("setBrightness");
operation.brightness = screenBrightness;
operation.silent = true
service.startOperationCall(operation);
operation.silent = true; // don't show OSD
updateScreenBrightnessJob = service.startOperationCall(operation);
updateScreenBrightnessJob.finished.connect(function (job) {
root.updateBrightnessUI();
});
}
}
@ -44,18 +60,7 @@ Item {
connectSource(source);
}
}
onDataChanged: {
disableBrightnessUpdate = true;
brightnessRoot.screenBrightness = pmSource.data["PowerDevil"]["Screen Brightness"];
disableBrightnessUpdate = false;
}
}
Component.onCompleted: {
brightnessSlider.moved.connect(function() {
brightnessRoot.screenBrightness = brightnessSlider.value;
});
disableBrightnessUpdate = false;
onDataChanged: root.updateBrightnessUI()
}
RowLayout {
@ -69,24 +74,26 @@ Item {
PlasmaCore.IconItem {
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: PlasmaCore.Units.smallSpacing
Layout.preferredWidth: Math.round(PlasmaCore.Units.gridUnit * 1.75)
Layout.preferredWidth: PlasmaCore.Units.iconSizes.smallMedium
Layout.preferredHeight: width
source: "low-brightness"
}
Slider {
PC3.Slider {
id: brightnessSlider
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
value: screenBrightness
from: 1
to: maximumScreenBrightness
to: root.maximumScreenBrightness
value: root.screenBrightness
onMoved: root.screenBrightness = value;
}
PlasmaCore.IconItem {
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: PlasmaCore.Units.smallSpacing
Layout.preferredWidth: Math.round(PlasmaCore.Units.gridUnit * 1.75)
Layout.preferredWidth: PlasmaCore.Units.iconSizes.smallMedium
Layout.preferredHeight: width
source: "high-brightness"
}

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import org.kde.plasma.core 2.0 as PlasmaCore
Rectangle {
id: handle
signal tapped()
implicitWidth: PlasmaCore.Units.gridUnit * 3
implicitHeight: 3
radius: height
color: PlasmaCore.Theme.textColor
opacity: 0.5
TapHandler {
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor
onTapped: handle.tapped()
}
}

View file

@ -0,0 +1,129 @@
/*
* SPDX-FileCopyrightText: 2014 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.plasma.core 2.0 as PlasmaCore
import "../../components" as Components
import "../../components/util.js" as Util
/**
* Quick settings elements layout, change the height to clip.
*/
Item {
id: root
clip: true
required property var actionDrawer
readonly property real columns: Math.round(Util.applyMinMaxRange(3, 6, width / intendedColumnWidth))
readonly property real columnWidth: Math.floor(width / columns)
readonly property real minimizedColumns: Math.round(Util.applyMinMaxRange(5, 8, width / intendedMinimizedColumnWidth))
readonly property real minimizedColumnWidth: Math.floor(width / minimizedColumns)
readonly property real rowHeight: columnWidth * 0.7
readonly property real fullHeight: fullView.implicitHeight
readonly property real intendedColumnWidth: 120
readonly property real intendedMinimizedColumnWidth: PlasmaCore.Units.gridUnit * 3 + PlasmaCore.Units.largeSpacing
readonly property real minimizedRowHeight: PlasmaCore.Units.gridUnit * 3 + PlasmaCore.Units.largeSpacing
property real minimizedViewProgress: 0
property real fullViewProgress: 1
readonly property SettingsModel quickSettingsModel: SettingsModel {
actionDrawer: root.actionDrawer
}
// view when fully open
ColumnLayout {
id: fullView
opacity: root.fullViewProgress
visible: opacity !== 0
transform: Translate { y: (1 - fullView.opacity) * root.rowHeight }
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
// TODO add pages
Flow {
id: flow
spacing: 0
Layout.fillWidth: true
Repeater {
model: root.quickSettingsModel
delegate: Components.BaseItem {
required property var modelData
height: root.rowHeight
width: root.columnWidth
padding: PlasmaCore.Units.smallSpacing
contentItem: QuickSettingsFullDelegate {
text: modelData.text
status: modelData.status
icon: modelData.icon
enabled: modelData.enabled
settingsCommand: modelData.settingsCommand
toggleFunction: modelData.toggle
}
}
}
}
BrightnessItem {
id: brightnessItem
Layout.topMargin: PlasmaCore.Units.smallSpacing * 2
Layout.bottomMargin: PlasmaCore.Units.smallSpacing * 2
Layout.leftMargin: PlasmaCore.Units.smallSpacing
Layout.rightMargin: PlasmaCore.Units.smallSpacing
Layout.fillWidth: true
}
}
// view when in minimized mode
RowLayout {
id: minimizedView
spacing: 0
opacity: root.minimizedViewProgress
visible: opacity !== 0
transform: Translate { y: (1 - minimizedView.opacity) * -root.rowHeight }
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
Repeater {
model: root.quickSettingsModel
delegate: Components.BaseItem {
required property var modelData
required property var index
implicitHeight: root.minimizedRowHeight
implicitWidth: root.minimizedColumnWidth
horizontalPadding: (width - PlasmaCore.Units.gridUnit * 3) / 2
verticalPadding: (height - PlasmaCore.Units.gridUnit * 3) / 2
visible: index <= root.minimizedColumns
contentItem: QuickSettingsMinimizedDelegate {
text: modelData.text
status: modelData.status
icon: modelData.icon
enabled: modelData.enabled
settingsCommand: modelData.settingsCommand
toggleFunction: modelData.toggle
}
}
}
}
}

View file

@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.components 3.0 as PlasmaComponents
import "../../components" as Components
Components.BaseItem {
id: root
// Model interface
required property string text
required property string status
required property string icon
required property bool enabled
required property string settingsCommand
required property var toggleFunction
// set by children
property var iconItem
readonly property color enabledButtonBorderColor: Qt.darker(Kirigami.ColorUtils.adjustColor(PlasmaCore.ColorScope.highlightColor, {}), 1.25)
readonly property color disabledButtonBorderColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.ColorScope.textColor, {"alpha": 0.2*255})
readonly property color enabledButtonColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.ColorScope.highlightColor, {alpha: 0.4*255})
readonly property color enabledButtonPressedColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.ColorScope.highlightColor, {alpha: 0.6*255});
readonly property color disabledButtonColor: PlasmaCore.Theme.backgroundColor
readonly property color disabledButtonPressedColor: Qt.darker(disabledButtonColor, 1.1)
function delegateClick() {
if (root.toggle) {
root.toggle();
} else if (root.toggleFunction) {
root.toggleFunction();
} else if (root.settingsCommand) {
NanoShell.StartupFeedback.open(
root.icon,
root.text,
iconItem.Kirigami.ScenePosition.x + iconItem.width/2,
iconItem.Kirigami.ScenePosition.y + iconItem.height/2,
Math.min(iconItem.width, iconItem.height))
MobileShell.ShellUtil.executeCommand(root.settingsCommand);
root.closeRequested();
}
}
function delegatePressAndHold() {
if (root.settingsCommand) {
NanoShell.StartupFeedback.open(
root.icon,
root.text,
iconItem.Kirigami.ScenePosition.x + iconItem.width/2,
iconItem.Kirigami.ScenePosition.y + iconItem.height/2,
Math.min(iconItem.width, iconItem.height))
closeRequested();
MobileShell.ShellUtil.executeCommand(root.settingsCommand);
} else if (root.toggleFunction) {
root.toggleFunction();
}
}
}

View file

@ -0,0 +1,129 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import "../../statusbar" as StatusBar
import "../../components" as Components
import "../../widgets" as Widgets
import "../"
/**
* Quick settings drawer pulled down from the top (for portrait mode).
* For the landscape view quicksettings container, see QuickSettingsPanel.
*/
Components.BaseItem {
id: root
required property var actionDrawer
/**
* The amount of height to add to the panel (increasing the height of the quick settings area).
*/
property real addedHeight: 0
/**
* The maximum amount of added height to snap to the full height of the quick settings panel.
*/
readonly property real maxAddedHeight: quickSettings.fullHeight - minimizedQuickSettingsHeight // first row is part of minimized height
/**
* Height of panel when in minimized mode.
*/
readonly property real minimizedHeight: bottomPadding + topPadding + statusBar.height + minimizedQuickSettingsHeight + mediaWidget.height + handle.fullHeight
/**
* Height of just the QuickSettings component in minimized mode.
*/
readonly property real minimizedQuickSettingsHeight: quickSettings.minimizedRowHeight + PlasmaCore.Units.gridUnit
/**
* Progress of showing the full quick settings view from pinned.
*/
property real minimizedToFullProgress: 1
// we need extra padding if the background side border is enabled
topPadding: PlasmaCore.Units.smallSpacing
leftPadding: PlasmaCore.Units.smallSpacing
rightPadding: PlasmaCore.Units.smallSpacing
bottomPadding: PlasmaCore.Units.smallSpacing * 4
background: PlasmaCore.FrameSvgItem {
enabledBorders: PlasmaCore.FrameSvg.BottomBorder
imagePath: "widgets/background"
}
contentItem: Item {
id: containerItem
implicitHeight: column.implicitHeight
// use container item so that our column doesn't get stretched if base item is anchored
ColumnLayout {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: 0
StatusBar.StatusBar {
id: statusBar
Layout.fillWidth: true
Layout.preferredHeight: MobileShell.TopPanelControls.panelHeight + PlasmaCore.Units.gridUnit * 0.8
colorGroup: PlasmaCore.Theme.NormalColorGroup
backgroundColor: "transparent"
showSecondRow: true
showDropShadow: false
}
QuickSettings {
id: quickSettings
Layout.preferredHeight: root.minimizedQuickSettingsHeight + root.addedHeight
Layout.topMargin: PlasmaCore.Units.smallSpacing
Layout.fillWidth: true
actionDrawer: root.actionDrawer
minimizedViewProgress: 1 - root.minimizedToFullProgress
fullViewProgress: root.minimizedToFullProgress
height: root.minimizedQuickSettingsHeight + root.addedHeight
width: parent.width
}
Widgets.MediaControlsWidget {
id: mediaWidget
property real fullHeight: height + Layout.topMargin
Layout.fillWidth: true
Layout.topMargin: PlasmaCore.Units.smallSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
}
Handle {
id: handle
property real fullHeight: height + Layout.topMargin
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: PlasmaCore.Units.smallSpacing * 2
onTapped: {
if (root.minimizedToFullProgress < 0.5) {
root.actionDrawer.expand();
} else {
root.actionDrawer.open();
}
}
}
}
}
}

View file

@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.components 3.0 as PlasmaComponents
import "../../components" as Components
QuickSettingsDelegate {
id: root
padding: PlasmaCore.Units.smallSpacing * 2
iconItem: icon
background: Rectangle {
radius: PlasmaCore.Units.smallSpacing
border.color: root.enabled ? root.enabledButtonBorderColor : root.disabledButtonBorderColor
color: {
if (root.enabled) {
return mouseArea.pressed ? root.enabledButtonPressedColor : root.enabledButtonColor
} else {
return mouseArea.pressed ? root.disabledButtonPressedColor : root.disabledButtonColor
}
}
}
contentItem: MouseArea {
id: mouseArea
onClicked: root.delegateClick()
onPressAndHold: root.delegatePressAndHold()
PlasmaCore.IconItem {
id: icon
anchors.top: parent.top
anchors.left: parent.left
implicitWidth: PlasmaCore.Units.iconSizes.small
implicitHeight: width
source: root.icon
}
ColumnLayout {
id: column
spacing: PlasmaCore.Units.smallSpacing
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
PlasmaComponents.Label {
Layout.fillWidth: true
elide: Text.ElideRight
text: root.text
font.pixelSize: PlasmaCore.Theme.defaultFont.pixelSize * 0.8 // TODO base height off of size of delegate
}
PlasmaComponents.Label {
Layout.fillWidth: true
elide: Text.ElideRight
// if no status is given, just use On/Off
text: root.status ? root.status : (root.enabled ? i18n("On") : i18n("Off"))
opacity: 0.6
font.pixelSize: PlasmaCore.Theme.defaultFont.pixelSize * 0.8
}
}
}
}

View file

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.components 3.0 as PlasmaComponents
import "../../components" as Components
QuickSettingsDelegate {
id: root
iconItem: icon
background: Rectangle {
radius: PlasmaCore.Units.smallSpacing
border.color: root.enabled ? root.enabledButtonBorderColor : root.disabledButtonBorderColor
color: {
if (root.enabled) {
return mouseArea.pressed ? root.enabledButtonPressedColor : root.enabledButtonColor
} else {
return mouseArea.pressed ? root.disabledButtonPressedColor : root.disabledButtonColor
}
}
}
contentItem: MouseArea {
id: mouseArea
onClicked: root.delegateClick()
onPressAndHold: root.delegatePressAndHold()
PlasmaCore.IconItem {
id: icon
anchors.centerIn: parent
implicitWidth: PlasmaCore.Units.iconSizes.smallMedium
implicitHeight: width
source: root.icon
}
}
}

View file

@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import "../../statusbar" as StatusBar
import "../../components" as Components
import "../../widgets" as Widgets
import "../"
/**
* Quick settings panel for landscape view (right sidebar).
* For the portrait view quicksettings container, see QuickSettingsDrawer.
*/
Components.BaseItem {
id: root
required property var actionDrawer
required property real fullHeight
/**
* Height of panel when first pulled down.
*/
readonly property real minimizedHeight: bottomPadding + topPadding + statusBar.height + quickSettings.rowHeight
/**
* Implicit height of the contents of the panel.
*/
readonly property real contentImplicitHeight: column.implicitHeight
// we need extra padding since the background side border is enabled
topPadding: PlasmaCore.Units.smallSpacing * 4
leftPadding: PlasmaCore.Units.smallSpacing * 4
rightPadding: PlasmaCore.Units.smallSpacing * 4
bottomPadding: PlasmaCore.Units.smallSpacing * 4
background: PlasmaCore.FrameSvgItem {
enabledBorders: PlasmaCore.FrameSvg.AllBorders
imagePath: "widgets/background"
}
contentItem: Item {
id: containerItem
clip: true
// use container item so that our column doesn't get stretched if base item is anchored
ColumnLayout {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: root.fullHeight
spacing: 0
StatusBar.StatusBar {
id: statusBar
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5
Layout.maximumHeight: Kirigami.Units.gridUnit * 1.5
colorGroup: PlasmaCore.Theme.NormalColorGroup
backgroundColor: "transparent"
showSecondRow: false
showDropShadow: false
showTime: false
}
PlasmaComponents.ScrollView {
id: scrollView
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.maximumHeight: root.fullHeight - root.topPadding - root.bottomPadding - statusBar.height - mediaWidget.fullHeight - PlasmaCore.Units.smallSpacing
Layout.maximumWidth: column.width
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
clip: true
QuickSettings {
id: quickSettings
width: column.width
implicitHeight: quickSettings.fullHeight
actionDrawer: root.actionDrawer
minimizedViewProgress: 0
fullViewProgress: 1
}
}
Item { Layout.fillHeight: true }
}
Widgets.MediaControlsWidget {
id: mediaWidget
property real fullHeight: visible ? height + PlasmaCore.Units.smallSpacing * 6 : 0
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: column.bottom
anchors.bottomMargin: root.bottomPadding * 2 + PlasmaCore.Units.smallSpacing * 2 // HACK: can't figure out a cleaner way to position
}
Handle {
id: handle
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
}
}
}

View file

@ -1,6 +1,6 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
@ -9,35 +9,29 @@
import QtQuick 2.14
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
import org.kde.bluezqt 1.0 as BluezQt
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PC3
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
HomeScreenComponents.QuickSettingsModel
{
id: modelItem
MobileShell.QuickSettingsModel {
id: root
required property var actionDrawer
property bool screenshotRequested: false
signal panelClosed()
onPanelClosed: {
if (screenshotRequested) {
plasmoid.nativeInterface.takeScreenshot();
screenshotRequested = false;
}
}
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
text: i18n("Settings")
status: i18n("Tap to open")
icon: "configure"
enabled: false
settingsCommand: "plasma-open-settings"
}
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
PlasmaNM.Handler {
id: nmHandler
}
@ -54,7 +48,7 @@ HomeScreenComponents.QuickSettingsModel
}
enabled: enabledConnections.wirelessEnabled
}
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
text: i18n("Bluetooth")
icon: "network-bluetooth"
settingsCommand: "plasma-open-settings kcm_bluetooth"
@ -69,7 +63,7 @@ HomeScreenComponents.QuickSettingsModel
}
enabled: BluezQt.Manager.bluetoothOperational
}
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
text: i18n("Mobile Data")
icon: "network-modem"
settingsCommand: "plasma-open-settings kcm_mobile_broadband"
@ -78,35 +72,70 @@ HomeScreenComponents.QuickSettingsModel
nmHandler.enableWwan(!enabledConnections.wwanEnabled)
}
}
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
text: i18n("Flashlight")
icon: "flashlight-on"
enabled: plasmoid.nativeInterface.torchEnabled
enabled: MobileShell.ShellUtil.torchEnabled
function toggle() {
plasmoid.nativeInterface.toggleTorch()
MobileShell.ShellUtil.toggleTorch()
}
}
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
text: i18n("Location")
icon: "gps"
enabled: false
}
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
text: i18n("Screenshot")
status: i18n("Tap to screenshot")
icon: "spectacle"
enabled: false
function toggle() {
modelItem.screenshotRequested = true;
root.closeRequested();
root.screenshotRequested = true;
root.actionDrawer.close();
}
Connections {
target: root.actionDrawer
function onVisibleChanged(visible) {
if (!visible && screenshotRequested) {
timer.restart();
root.screenshotRequested = false
}
}
}
// HACK: KWin's fade effect may have the window ending up being in the screenshot if taken too fast
Timer {
id: timer
interval: 500
onTriggered: MobileShell.ShellUtil.takeScreenshot()
}
}
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
text: i18n("Auto-rotate")
icon: "rotation-allowed"
settingsCommand: "plasma-open-settings kcm_kscreen"
enabled: plasmoid.nativeInterface.autoRotateEnabled
enabled: MobileShell.ShellUtil.autoRotateEnabled
function toggle() {
plasmoid.nativeInterface.autoRotateEnabled = !enabled
MobileShell.ShellUtil.autoRotateEnabled = !enabled
}
}
MobileShell.QuickSetting {
text: i18n("Battery")
status: i18n("%1%", MobileShell.BatteryProvider.percent)
icon: "battery-full" + (MobileShell.BatteryProvider.pluggedIn ? "-charging" : "")
enabled: false
settingsCommand: "plasma-open-settings kcm_mobile_power"
}
MobileShell.QuickSetting {
text: i18n("Sound")
icon: "audio-speakers-symbolic"
status: i18n("%1%", MobileShell.VolumeProvider.volumeValue)
enabled: false
settingsCommand: "plasma-open-settings kcm_pulseaudio"
function toggle() {
volumeProvider.showVolumeOverlay()
}
}
}

View file

@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
/**
* Serves a similar function as a QQC2.Control, but does not
* take input events, preventing conflicts with Flickable.
*/
Item {
id: root
property real topInset: 0
property real bottomInset: 0
property real leftInset: 0
property real rightInset: 0
property real padding: 0
property real verticalPadding: padding
property real horizontalPadding: padding
property real topPadding: verticalPadding
property real bottomPadding: verticalPadding
property real leftPadding: horizontalPadding
property real rightPadding: horizontalPadding
property Item contentItem: Item {}
property Item background: Item {}
implicitHeight: topPadding + bottomPadding + contentItem.implicitHeight
implicitWidth: leftPadding + rightPadding + contentItem.implicitWidth
onContentItemChanged: {
contentItem.parent = contentItemLoader;
contentItem.anchors.fill = contentItemLoader;
contentItemLoader.children.push(contentItem);
}
onBackgroundChanged: {
background.parent = backgroundLoader;
background.anchors.fill = backgroundLoader;
backgroundLoader.children.push(background);
}
Item {
id: backgroundLoader
anchors.fill: parent
anchors.leftMargin: root.leftInset
anchors.rightMargin: root.rightInset
anchors.topMargin: root.topInset
anchors.bottomMargin: root.bottomInset
}
Item {
id: contentItemLoader
anchors.fill: parent
anchors.leftMargin: root.leftPadding
anchors.rightMargin: root.rightPadding
anchors.topMargin: root.topPadding
anchors.bottomMargin: root.bottomPadding
}
}

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
QtObject {
enum Type {
None = 0,
Up,
Down,
Left,
Right
}
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
function applyMinMaxRange(min, max, num) {
return Math.min(max, Math.max(min, num));
}

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
@ -12,6 +12,8 @@ import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.workspace.components 2.0 as PW
pragma Singleton
Item {
property bool isVisible: pmSource.data["Battery"]["Has Cumulative"]
property int percent: pmSource.data["Battery"]["Percent"]

View file

@ -1,5 +1,5 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2013-2017 Jan Grulich <jgrulich@redhat.com>
@ -11,6 +11,8 @@ import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.bluezqt 1.0 as BluezQt
pragma Singleton
QtObject {
property bool isVisible: BluezQt.Manager.bluetoothOperational
property string icon: deviceConnected ? "preferences-system-bluetooth-activated" : "preferences-system-bluetooth"

View file

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
import QtQuick 2.2
import org.kde.notificationmanager 1.0 as NotificationManager
pragma Singleton
QtObject {
property var notificationSettings: NotificationManager.Settings {}
property var historyModel: NotificationManager.Notifications {
showExpired: true
showDismissed: true
showJobs: notificationSettings.jobsInNotifications
sortMode: NotificationManager.Notifications.SortByTypeAndUrgency
groupMode: NotificationManager.Notifications.GroupApplicationsFlat
groupLimit: 2
expandUnread: true
blacklistedDesktopEntries: notificationSettings.historyBlacklistedApplications
blacklistedNotifyRcNames: notificationSettings.historyBlacklistedServices
urgencies: {
var urgencies = NotificationManager.Notifications.CriticalUrgency
| NotificationManager.Notifications.NormalUrgency;
if (notificationSettings.lowPriorityHistory) {
urgencies |= NotificationManager.Notifications.LowUrgency;
}
return urgencies;
}
}
}

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
*
@ -9,6 +9,8 @@
import QtQuick 2.1
import org.kde.plasma.mm 1.0
pragma Singleton
QtObject {
property string icon: "network-mobile-" + Math.floor(SignalIndicator.strength / 20) * 20

View file

@ -1,5 +1,5 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
SPDX-FileCopyrightText: 2019 Aditya Mehra <Aix.m@outlook.com>
SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
@ -11,10 +11,21 @@ import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.private.volume 0.1
import "../../volumeosd"
import "../osd/volume"
pragma Singleton
QtObject {
/**
* Whether or not to bind the volume global key shortcuts.
* We should never bind the shortcut multiple times in the shell, or else they may not work at all.
*
* We only set this to true when loaded for the panel containment (and NOT in the lockscreen).
*/
property bool bindShortcuts: false
property bool isVisible: paSinkModel.preferredSink && paSinkModel.preferredSink.muted
property string icon: paSinkModel.preferredSink && !isDummyOutput(paSinkModel.preferredSink)
? iconName(paSinkModel.preferredSink.volume, paSinkModel.preferredSink.muted)
: iconName(0, true)
@ -22,6 +33,8 @@ QtObject {
property bool volumeFeedback: true
property int maxVolumeValue: Math.round(100 * PulseAudio.NormalVolume / 100.0)
property int volumeStep: Math.round(5 * PulseAudio.NormalVolume / 100.0)
property int volumeValue
readonly property string dummyOutputName: "auto_null"
function showVolumeOverlay() {
@ -80,7 +93,7 @@ QtObject {
var percent = volumePercent(volume, maxVolumeValue);
paSinkModel.preferredSink.muted = percent == 0;
paSinkModel.preferredSink.volume = volume;
osd.volume = percent;
volumeValue = percent;
osd.showOverlay();
playFeedback();
@ -95,7 +108,7 @@ QtObject {
var percent = volumePercent(volume, maxVolumeValue);
paSinkModel.preferredSink.muted = percent == 0;
paSinkModel.preferredSink.volume = volume;
osd.volume = percent;
volumeValue = percent;
osd.showOverlay();
playFeedback();
}
@ -110,7 +123,7 @@ QtObject {
var toMute = !paSinkModel.preferredSink.muted;
paSinkModel.preferredSink.muted = toMute;
osd.volume = toMute ? 0 : volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue);
volumeValue = toMute ? 0 : volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue);
osd.showOverlay();
if (!toMute) {
@ -123,7 +136,7 @@ QtObject {
function onVolumeChanged() {
var percent = volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue);
osd.volume = percent;
volumeValue = percent;
}
}
property var updateVolumeOnSinkChange: Connections {
@ -132,49 +145,56 @@ QtObject {
function onPreferredSinkChanged() {
if (paSinkModel.preferredSink) {
var percent = volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue);
osd.volume = percent;
volumeValue = percent;
}
}
}
property SinkModel paSinkModel: SinkModel {}
property VolumeOsd osd: VolumeOsd {}
property VolumeOSD osd: VolumeOSD {
volume: volumeValue
}
property VolumeFeedback feedback: VolumeFeedback {}
property GlobalActionCollection actionCollection: GlobalActionCollection {
// KGlobalAccel cannot transition from kmix to something else, so if
// the user had a custom shortcut set for kmix those would get lost.
// To avoid this we hijack kmix name and actions. Entirely mental but
// best we can do to not cause annoyance for the user.
// The display name actually is updated to whatever registered last
// though, so as far as user visible strings go we should be fine.
// As of 2015-07-21:
// componentName: kmix
// actions: increase_volume, decrease_volume, mute
name: "kmix"
displayName: i18n("Audio")
// only bind the global shortcuts when told to
property var actionCollection: Loader {
active: bindShortcuts
sourceComponent: GlobalActionCollection {
// KGlobalAccel cannot transition from kmix to something else, so if
// the user had a custom shortcut set for kmix those would get lost.
// To avoid this we hijack kmix name and actions. Entirely mental but
// best we can do to not cause annoyance for the user.
// The display name actually is updated to whatever registered last
// though, so as far as user visible strings go we should be fine.
// As of 2015-07-21:
// componentName: kmix
// actions: increase_volume, decrease_volume, mute
name: "kmix"
displayName: i18n("Audio")
GlobalAction {
objectName: "increase_volume"
text: i18n("Increase Volume")
shortcut: Qt.Key_VolumeUp
onTriggered: increaseVolume()
}
GlobalAction {
objectName: "increase_volume"
text: i18n("Increase Volume")
shortcut: Qt.Key_VolumeUp
onTriggered: increaseVolume()
}
GlobalAction {
objectName: "decrease_volume"
text: i18n("Decrease Volume")
shortcut: Qt.Key_VolumeDown
onTriggered: decreaseVolume()
}
GlobalAction {
objectName: "decrease_volume"
text: i18n("Decrease Volume")
shortcut: Qt.Key_VolumeDown
onTriggered: decreaseVolume()
}
GlobalAction {
objectName: "mute"
text: i18n("Mute")
shortcut: Qt.Key_VolumeMute
onTriggered: muteVolume()
GlobalAction {
objectName: "mute"
text: i18n("Mute")
shortcut: Qt.Key_VolumeMute
onTriggered: muteVolume()
}
}
}
}

View file

@ -1,5 +1,5 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2013-2017 Jan Grulich <jgrulich@redhat.com>
@ -11,24 +11,25 @@ import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
Item {
visible: false
pragma Singleton
QtObject {
property string icon: connectionIconProvider.connectionIcon
property bool indicatorRunning: connectionIconProvider.connecting
PlasmaNM.NetworkStatus {
property var networkStatus: PlasmaNM.NetworkStatus {
id: networkStatus
}
PlasmaNM.NetworkModel {
property var networkModel: PlasmaNM.NetworkModel {
id: connectionModel
}
PlasmaNM.Handler {
property var handler: PlasmaNM.Handler {
id: handler
}
PlasmaNM.ConnectionIcon {
property var connectionIcon: PlasmaNM.ConnectionIcon {
id: connectionIconProvider
}
}

View file

@ -14,7 +14,6 @@ import QtGraphicalEffects 1.12
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtra
import org.kde.plasma.plasmoid 2.0
import org.kde.kquickcontrolsaddons 2.0 as KQCAddons
import org.kde.plasma.private.volume 0.1

View file

@ -0,0 +1,34 @@
# SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
# SPDX-License-Identifier: GPL-2.0-or-later
module org.kde.plasma.private.mobileshell
plugin mobileshellplugin
# /actiondrawer
ActionDrawer 1.0 actiondrawer/ActionDrawer.qml
ActionDrawerOpenSurface 1.0 actiondrawer/ActionDrawerOpenSurface.qml
# /components
BaseItem 1.0 components/BaseItem.qml
Direction 1.0 components/Direction.qml
PanelBackground 1.0 components/PanelBackground.qml
# /dataproviders
singleton BatteryProvider 1.0 dataproviders/BatteryProvider.qml
singleton BluetoothProvider 1.0 dataproviders/BluetoothProvider.qml
singleton NotificationProvider 1.0 dataproviders/NotificationProvider.qml
singleton SignalStrengthProvider 1.0 dataproviders/SignalStrengthProvider.qml
singleton VolumeProvider 1.0 dataproviders/VolumeProvider.qml
singleton WifiProvider 1.0 dataproviders/WifiProvider.qml
# /statusbar
StatusBar 1.0 statusbar/StatusBar.qml
# /widgets
MediaControlsWidget 1.0 widgets/MediaControlsWidget.qml
NotificationsWidget 1.0 widgets/NotificationsWidget.qml
# /
singleton HomeScreenControls 1.0 HomeScreenControls.qml
singleton TopPanelControls 1.0 TopPanelControls.qml

View file

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick.Layouts 1.15
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import "indicators" as Indicators
PlasmaComponents.Label {
id: clock
required property PlasmaCore.DataSource source
property bool is24HourTime: MobileShell.ShellUtil.isSystem24HourFormat
text: Qt.formatTime(source.data.Local.DateTime, is24HourTime ? "h:mm" : "h:mm ap")
color: PlasmaCore.ColorScope.textColor
verticalAlignment: Qt.AlignVCenter
TapHandler {
onTapped: {
MobileShell.ShellUtil.launchApp("org.kde.kclock");
}
}
}

View file

@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
@ -12,39 +12,75 @@ import QtQuick.Layouts 1.3
import QtQml.Models 2.12
import QtGraphicalEffects 1.12
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.workspace.components 2.0 as PlasmaWorkspace
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import "LayoutManager.js" as LayoutManager
import "indicators" as Indicators
Item {
id: indicatorsRow
required property var colorGroup
required property bool showDropShadow
required property color backgroundColor
id: root
/**
* The color group used for status bar elements.
*/
required property var colorGroup
/**
* Whether to show a drop shadow under the status bar.
*/
property bool showDropShadow: false
/**
* The background color of the status bar.
*/
property color backgroundColor: "transparent"
/**
* Whether to show a second row of the status bar, with more information.
*/
property bool showSecondRow: false // show extra row with date and mobile provider
/**
* Whether to show time. If set to false, the signal strength indicator is moved in its place.
*/
property bool showTime: true
/**
* Disables showing system tray indicators, preventing SIGABRT when used on the lockscreen.
*/
property bool disableSystemTray: false
property alias colorScopeColor: icons.backgroundColor
property alias applets: appletIconsRow
property real textPixelSize: PlasmaCore.Units.gridUnit * 0.6
property real elementSpacing: PlasmaCore.Units.smallSpacing * 1.5
readonly property real textPixelSize: PlasmaCore.Units.gridUnit * 0.6
readonly property real elementSpacing: PlasmaCore.Units.smallSpacing * 1.5
PlasmaCore.DataSource {
id: timeSource
engine: "time"
connectedSources: ["Local"]
interval: 60 * 1000
intervalAlignment: PlasmaCore.Types.AlignToMinute
}
property alias statusNotifierSource: statusNotifierSourceLoader.item
Loader {
id: statusNotifierSourceLoader
active: !disableSystemTray
sourceComponent: PlasmaCore.DataSource {
id: statusNotifierSource
engine: "statusnotifieritem"
interval: 0
onSourceAdded: {
connectSource(source)
}
Component.onCompleted: {
connectedSources = sources
}
}
}
DropShadow {
@ -63,7 +99,7 @@ Item {
PlasmaCore.ColorScope {
id: icons
z: 1
colorGroup: indicatorsRow.colorGroup
colorGroup: root.colorGroup
anchors.fill: parent
Controls.Control {
@ -88,21 +124,18 @@ Item {
spacing: 0
// clock
PlasmaComponents.Label {
id: clock
property bool is24HourTime: plasmoid.nativeInterface.isSystem24HourFormat
ClockText {
visible: root.showTime
Layout.fillHeight: true
text: Qt.formatTime(timeSource.data.Local.DateTime, is24HourTime ? "h:mm" : "h:mm ap")
color: PlasmaCore.ColorScope.textColor
verticalAlignment: Qt.AlignVCenter
font.pixelSize: textPixelSize
TapHandler {
onTapped: {
plasmoid.nativeInterface.launchApp("org.kde.kclock");
}
}
source: timeSource
}
Indicators.SignalStrengthIndicator {
Layout.fillHeight: true
showLabel: true
visible: !root.showTime
textPixelSize: root.textPixelSize
}
// spacing in the middle
@ -117,21 +150,21 @@ Item {
id: filteredStatusNotifiers
filterRole: "Title"
sourceModel: PlasmaCore.DataModel {
dataSource: statusNotifierSource
dataSource: statusNotifierSource ? statusNotifierSource : null
}
}
delegate: TaskWidget {
Layout.leftMargin: indicatorsRow.elementSpacing
Layout.leftMargin: root.elementSpacing
}
}
// applet indicators
RowLayout {
id: appletIconsRow
Layout.leftMargin: indicatorsRow.elementSpacing
Layout.leftMargin: root.elementSpacing
Layout.fillHeight: true
spacing: indicatorsRow.elementSpacing
spacing: root.elementSpacing
visible: children.length > 0
}
@ -140,28 +173,25 @@ Item {
id: indicators
Layout.leftMargin: PlasmaCore.Units.smallSpacing // applets have different spacing needs
Layout.fillHeight: true
spacing: indicatorsRow.elementSpacing
spacing: root.elementSpacing
Indicators.SignalStrength {
provider: signalStrengthProvider
Indicators.SignalStrengthIndicator {
Layout.fillHeight: true
showLabel: false
visible: root.showTime
}
Indicators.BluetoothIndicator {
Layout.fillHeight: true
}
Indicators.Bluetooth {
provider: bluetoothProvider
Indicators.WifiIndicator {
Layout.fillHeight: true
}
Indicators.Wifi {
provider: wifiProvider
Indicators.VolumeIndicator {
Layout.fillHeight: true
}
Indicators.Volume {
provider: volumeProvider
Layout.fillHeight: true
}
Indicators.Battery {
provider: batteryProvider
spacing: indicatorsRow.elementSpacing
labelHeight: textPixelSize
Indicators.BatteryIndicator {
spacing: root.elementSpacing
textPixelSize: root.textPixelSize
Layout.fillHeight: true
}
}
@ -170,19 +200,20 @@ Item {
// extra row with date and mobile provider (for quicksettings panel)
RowLayout {
spacing: 0
visible: indicatorsRow.showSecondRow
visible: root.showSecondRow
Layout.fillWidth: true
PlasmaComponents.Label {
text: Qt.formatDate(timeSource.data.Local.DateTime, "ddd. MMMM d")
color: PlasmaCore.ColorScope.disabledTextColor
font.pixelSize: indicatorsRow.textPixelSize * 0.8
font.pixelSize: root.textPixelSize * 0.8
}
Item { Layout.fillWidth: true }
PlasmaComponents.Label {
text: signalStrengthProvider.label
visible: root.showTime
text: MobileShell.SignalStrengthProvider.label
color: PlasmaCore.ColorScope.disabledTextColor
font.pixelSize: indicatorsRow.textPixelSize * 0.8
font.pixelSize: root.textPixelSize * 0.8
horizontalAlignment: Qt.AlignRight
}
}

View file

@ -11,12 +11,11 @@ import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.workspace.components 2.0 as PW
import "providers"
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
RowLayout {
required property BatteryProvider provider
property real labelHeight
property MobileShell.BatteryProvider provider: MobileShell.BatteryProvider
property real textPixelSize: PlasmaCore.Units.gridUnit * 0.6
visible: provider.isVisible
PW.BatteryIcon {
@ -37,6 +36,6 @@ RowLayout {
Layout.alignment: Qt.AlignVCenter
color: PlasmaCore.ColorScope.textColor
font.pixelSize: labelHeight
font.pixelSize: textPixelSize
}
}

View file

@ -8,16 +8,17 @@
import QtQuick 2.2
import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.bluezqt 1.0 as BluezQt
import "providers"
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.bluezqt 1.0 as BluezQt
PlasmaCore.IconItem {
id: connectionIcon
required property BluetoothProvider provider
property MobileShell.BluetoothProvider provider: MobileShell.BluetoothProvider
source: provider.icon;
source: provider.icon
colorGroup: PlasmaCore.ColorScope.colorGroup
visible: provider.isVisible

View file

@ -10,14 +10,16 @@ import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
Item {
required property QtObject provider
property real labelPixelSize
property MobileShell.SignalStrengthProvider provider: MobileShell.SignalStrengthProvider
width: strengthIcon.height + label.width
Layout.minimumWidth: strengthIcon.height + label.width
property bool showLabel: true
property real textPixelSize: PlasmaCore.Units.gridUnit * 0.6
width: strengthIcon.width + label.width
Layout.minimumWidth: strengthIcon.width + label.width
PlasmaCore.IconItem {
id: strengthIcon
@ -29,15 +31,17 @@ Item {
source: provider.icon
}
PlasmaComponents.Label {
id: label
visible: showLabel
width: visible ? implicitWidth : 0
anchors.leftMargin: PlasmaCore.Units.smallSpacing
anchors.left: strengthIcon.right
anchors.verticalCenter: parent.verticalCenter
text: provider.label
color: PlasmaCore.ColorScope.textColor
font.pixelSize: labelPixelSize
font.pixelSize: textPixelSize
}
}

View file

@ -10,12 +10,11 @@ import QtQuick 2.2
import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.private.volume 0.1
import "providers"
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
PlasmaCore.IconItem {
id: paIcon
required property VolumeProvider provider
property MobileShell.VolumeProvider provider: MobileShell.VolumeProvider
Layout.fillHeight: true
Layout.preferredWidth: height

View file

@ -11,12 +11,11 @@ import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
import "providers"
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
PlasmaCore.IconItem {
id: connectionIcon
required property WifiProvider provider
property MobileShell.WifiProvider provider: MobileShell.WifiProvider
source: provider.icon
colorGroup: PlasmaCore.ColorScope.colorGroup

View file

@ -0,0 +1,127 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 as QQC2
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.plasma.extras 2.0 as PlasmaExtras
import "../components" as Components
import "mediacontrols"
Components.BaseItem {
id: root
visible: mpris2Source.hasPlayer
padding: visible ? Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0
implicitHeight: visible ? bottomPadding + topPadding + PlasmaCore.Units.gridUnit * 2 + PlasmaCore.Units.smallSpacing : 0
background: BlurredBackground {
imageSource: mpris2Source.albumArt
}
contentItem: PlasmaCore.ColorScope {
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
width: root.width - root.leftPadding - root.rightPadding
MediaControlsSource {
id: mpris2Source
}
RowLayout {
id: controlsRow
width: parent.width
height: parent.height
spacing: 0
enabled: mpris2Source.canControl
Image {
id: albumArt
Layout.preferredWidth: height
Layout.fillHeight: true
asynchronous: true
fillMode: Image.PreserveAspectFit
source: mpris2Source.albumArt
sourceSize.height: height
visible: status === Image.Loading || status === Image.Ready
}
ColumnLayout {
Layout.leftMargin: albumArt.visible ? Kirigami.Units.largeSpacing : 0
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
QQC2.Label {
Layout.fillWidth: true
wrapMode: Text.NoWrap
elide: Text.ElideRight
text: mpris2Source.track || i18n("No media playing")
textFormat: Text.PlainText
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize
maximumLineCount: 1
color: "white"
}
QQC2.Label {
Layout.fillWidth: true
wrapMode: Text.NoWrap
elide: Text.ElideRight
// if no artist is given, show player name instead
text: mpris2Source.artist || mpris2Source.identity || ""
textFormat: Text.PlainText
font.pointSize: PlasmaCore.Theme.smallestFont.pointSize
maximumLineCount: 1
opacity: 0.9
color: "white"
}
}
PlasmaComponents3.ToolButton {
Layout.fillHeight: true
Layout.preferredWidth: height
enabled: mpris2Source.canGoBack
icon.name: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward"
icon.width: PlasmaCore.Units.iconSizes.small
icon.height: PlasmaCore.Units.iconSizes.small
onClicked: mpris2Source.goPrevious()
visible: mpris2Source.canGoBack || mpris2Source.canGoNext
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Previous track")
}
PlasmaComponents3.ToolButton {
Layout.fillHeight: true
Layout.preferredWidth: height
icon.name: mpris2Source.playing ? "media-playback-pause" : "media-playback-start"
icon.width: PlasmaCore.Units.iconSizes.small
icon.height: PlasmaCore.Units.iconSizes.small
onClicked: mpris2Source.playPause()
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Play or Pause media")
}
PlasmaComponents3.ToolButton {
Layout.fillHeight: true
Layout.preferredWidth: height
enabled: mpris2Source.canGoNext
icon.name: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward"
icon.width: PlasmaCore.Units.iconSizes.small
icon.height: PlasmaCore.Units.iconSizes.small
onClicked: mpris2Source.goNext()
visible: mpris2Source.canGoBack || mpris2Source.canGoNext
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Next track")
}
}
}
}

View file

@ -0,0 +1,178 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.notificationmanager 1.0 as NotificationManager
import "notifications"
/**
* Embeddable notification list widget optimized for mobile and touch.
* Used on the lockscreen and action drawer.
*/
Item {
id: root
property var historyModel: MobileShell.NotificationProvider.historyModel
property var notificationSettings: MobileShell.NotificationProvider.notificationSettings
function clearHistory() {
historyModel.clear(NotificationManager.Notifications.ClearExpired);
}
function openNotificationSettings() {
MobileShell.ShellUtil.executeCommand("plasma-open-settings kcm_notifications");
}
PlasmaCore.DataSource {
id: timeDataSource
engine: "time"
connectedSources: ["Local"]
interval: 60000 // 1 min
intervalAlignment: PlasmaCore.Types.AlignToMinute
}
ListView {
id: list
model: historyModel
currentIndex: -1
boundsBehavior: Flickable.StopAtBounds
spacing: Kirigami.Units.largeSpacing
anchors.fill: parent
// TODO keyboard focus
highlightMoveDuration: 0
highlightResizeDuration: 0
highlight: Item {}
section {
property: "isInGroup"
criteria: ViewSection.FullString
}
PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (PlasmaCore.Units.largeSpacing * 4)
text: i18n("Notification service not available")
visible: list.count === 0 && !NotificationManager.Server.valid
PlasmaComponents3.Label {
// Checking valid to avoid creating ServerInfo object if everything is alright
readonly property NotificationManager.ServerInfo currentOwner: !NotificationManager.Server.valid ? NotificationManager.Server.currentOwner : null
// PlasmaExtras.PlaceholderMessage is internally a ColumnLayout, so we can use Layout.whatever properties here
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: currentOwner ? i18nc("Vendor and product name", "Notifications are currently provided by '%1 %2'", currentOwner.vendor, currentOwner.name) : ""
visible: currentOwner && currentOwner.vendor && currentOwner.name
}
}
add: Transition {
SequentialAnimation {
PropertyAction { property: "opacity"; value: 0 }
PauseAnimation { duration: PlasmaCore.Units.longDuration }
ParallelAnimation {
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: PlasmaCore.Units.longDuration }
NumberAnimation { property: "height"; from: 0; duration: PlasmaCore.Units.longDuration }
}
}
}
addDisplaced: Transition {
NumberAnimation { properties: "y"; duration: PlasmaCore.Units.longDuration }
}
removeDisplaced: Transition {
SequentialAnimation {
PauseAnimation { duration: PlasmaCore.Units.longDuration }
NumberAnimation { properties: "y"; duration: PlasmaCore.Units.longDuration }
}
}
function isRowExpanded(row) {
var idx = historyModel.index(row, 0);
return historyModel.data(idx, NotificationManager.Notifications.IsGroupExpandedRole);
}
function setGroupExpanded(row, expanded) {
var rowIdx = historyModel.index(row, 0);
var persistentRowIdx = historyModel.makePersistentModelIndex(rowIdx);
var persistentGroupIdx = historyModel.makePersistentModelIndex(historyModel.groupIndex(rowIdx));
historyModel.setData(rowIdx, expanded, NotificationManager.Notifications.IsGroupExpandedRole);
// If the current item went away when the group collapsed, scroll to the group heading
if (!persistentRowIdx || !persistentRowIdx.valid) {
if (persistentGroupIdx && persistentGroupIdx.valid) {
list.positionViewAtIndex(persistentGroupIdx.row, ListView.Contain);
// When closed via keyboard, also set a sane current index
if (list.currentIndex > -1) {
list.currentIndex = persistentGroupIdx.row;
}
}
}
}
delegate: Loader {
id: delegateLoader
width: list.width
sourceComponent: model.isGroup ? groupDelegate : notificationDelegate
required property var model
required property int index
Component {
id: groupDelegate
NotificationGroupHeader {
applicationName: model.applicationName
applicationIconSource: model.applicationIconName
originName: model.originName || ""
timeSource: timeDataSource
}
}
Component {
id: notificationDelegate
ColumnLayout {
spacing: PlasmaCore.Units.smallSpacing
NotificationItem {
Layout.fillWidth: true
model: delegateLoader.model
modelIndex: delegateLoader.index
notificationsModel: historyModel
timeSource: timeDataSource
}
PlasmaComponents3.ToolButton {
icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down"
text: model.isGroupExpanded ? i18n("Show Fewer")
: i18nc("Expand to show n more notifications",
"Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount))
visible: (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
&& delegateLoader.ListView.nextSection !== delegateLoader.ListView.section
onClicked: list.setGroupExpanded(model.index, !model.isGroupExpanded)
height: visible ? implicitHeight : 0
}
}
}
}
}
}

View file

@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtGraphicalEffects 1.12
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
id: root
property string imageSource
// clip corners so that the image has rounded corners
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Item {
width: img.width
height: img.height
Rectangle {
anchors.centerIn: parent
width: img.width
height: img.height
radius: PlasmaCore.Units.smallSpacing
}
}
}
Image {
id: img
source: root.imageSource
asynchronous: true
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
// ensure text is readable
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.6)
}
// apply lighten, saturate and blur effect
layer.enabled: true
layer.effect: HueSaturation {
cached: true
lightness: 0.2
saturation: 1.5
layer.enabled: true
layer.effect: FastBlur {
cached: true
radius: 64
transparentBorder: false
}
}
}
}

View file

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import org.kde.plasma.core 2.0 as PlasmaCore
PlasmaCore.DataSource {
id: mpris2Source
readonly property string source: "@multiplex"
readonly property var playerData: data[source]
readonly property bool hasPlayer: sources.length > 1 && !!playerData
readonly property string identity: hasPlayer ? playerData.Identity : ""
readonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing"
readonly property bool canControl: hasPlayer && playerData.CanControl
readonly property bool canGoBack: hasPlayer && playerData.CanGoPrevious
readonly property bool canGoNext: hasPlayer && playerData.CanGoNext
readonly property var currentMetadata: hasPlayer ? playerData.Metadata : ({})
readonly property string track: {
const xesamTitle = currentMetadata["xesam:title"]
if (xesamTitle) {
return xesamTitle
}
// if no track title is given, print out the file name
const xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : ""
if (!xesamUrl) {
return ""
}
const lastSlashPos = xesamUrl.lastIndexOf('/')
if (lastSlashPos < 0) {
return ""
}
const lastUrlPart = xesamUrl.substring(lastSlashPos + 1)
return decodeURIComponent(lastUrlPart)
}
readonly property string artist: currentMetadata["xesam:artist"] || ""
readonly property string albumArt: currentMetadata["mpris:artUrl"] || ""
engine: "mpris2"
connectedSources: [source]
function startOperation(op) {
var service = serviceForSource(source)
var operation = service.operationDescription(op)
return service.startOperationCall(operation)
}
function goPrevious() {
startOperation("Previous");
}
function goNext() {
startOperation("Next");
}
function playPause(source) {
startOperation("PlayPause");
}
}

View file

@ -0,0 +1,141 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.8
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import QtGraphicalEffects 1.12
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.notificationmanager 1.0 as NotificationManager
import org.kde.kcoreaddons 1.0 as KCoreAddons
Item {
id: notificationItem
required property NotificationManager.Notifications notificationsModel
property var model
property int modelIndex
property PlasmaCore.DataSource timeSource
readonly property int notificationType: model.type
readonly property bool inGroup: model.isInGroup
readonly property bool inHistory: true
readonly property string applicationIconSource: model.applicationIconName
readonly property string applicationName: model.applicationName
readonly property string originName: model.originName || ""
readonly property string summary: model.summary
readonly property var time: model.updated || model.created
readonly property bool hasReplyAction: model.hasReplyAction || false
readonly property string replyActionLabel: model.replyActionLabel || ""
readonly property string replyPlaceholderText: model.replyPlaceholderText || ""
readonly property string replySubmitButtonText: model.replySubmitButtonText || ""
readonly property string replySubmitButtonIconName: model.replySubmitButtonIconName || ""
// configure button on every single notifications is bit overwhelming
readonly property bool configurable: !inGroup && model.configurable
readonly property bool dismissable: model.type === NotificationManager.Notifications.JobType
&& model.jobState !== NotificationManager.Notifications.JobStateStopped
&& model.dismissed
&& notificationSettings.permanentJobPopups
readonly property bool dismissed: model.dismissed || false
readonly property bool closable: model.closable
readonly property string body: model.body || ""
readonly property var icon: model.image || model.iconName
readonly property var urls: model.urls || []
readonly property int jobState: model.jobState || 0
readonly property int percentage: model.percentage || 0
readonly property int jobError: model.jobError || 0
readonly property bool suspendable: !!model.suspendable
readonly property bool killable: !!model.killable
readonly property QtObject jobDetails: model.jobDetails || null
readonly property string configureActionLabel: model.configureActionLabel || ""
readonly property bool hasDefaultAction: model.hasDefaultAction
readonly property bool addDefaultAction: (model.hasDefaultAction
&& model.defaultActionLabel
&& (model.actionLabels || []).indexOf(model.defaultActionLabel) === -1) ? true : false
readonly property var actionNames: {
var actions = (model.actionNames || []);
if (addDefaultAction) {
actions.unshift("default"); // prepend
}
return actions;
}
readonly property var actionLabels: {
var labels = (model.actionLabels || []);
if (addDefaultAction) {
labels.unshift(model.defaultActionLabel);
}
return labels;
}
signal actionInvoked(string actionName)
signal replied(string text)
signal openUrl(string url)
signal fileActionInvoked(QtObject action)
signal suspendJobClicked
signal resumeJobClicked
signal killJobClicked
onActionInvoked: {
if (actionName === "default") {
notificationsModel.invokeDefaultAction(notificationsModel.index(modelIndex, 0));
} else {
notificationsModel.invokeAction(notificationsModel.index(modelIndex, 0), actionName);
}
expire();
}
onOpenUrl: {
Qt.openUrlExternally(url);
expire();
}
onFileActionInvoked: {
if (action.objectName === "movetotrash" || action.objectName === "deletefile") {
close();
} else {
expire();
}
}
onSuspendJobClicked: notificationsModel.suspendJob(notificationsModel.index(modelIndex, 0))
onResumeJobClicked: notificationsModel.resumeJob(notificationsModel.index(modelIndex, 0))
onKillJobClicked: notificationsModel.killJob(notificationsModel.index(modelIndex, 0))
function expire() {
if (model.resident) {
model.expired = true;
} else {
notificationsModel.expire(notificationsModel.index(modelIndex, 0));
}
}
function close() {
notificationsModel.close(notificationsModel.index(modelIndex, 0));
}
// TODO call
function configure() {
notificationsModel.configure(notificationsModel.index(modelIndex, 0))
}
}

View file

@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2011 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2014, 2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.15
import QtQuick.Window 2.2
import org.kde.plasma.components 3.0 as PlasmaComponents
PlasmaComponents.Label {
id: bodyText
background: Item {}
// Work around Qt bug where NativeRendering breaks for non-integer scale factors
// https://bugreports.qt.io/browse/QTBUG-67007
renderType: Screen.devicePixelRatio % 1 !== 0 ? Text.QtRendering : Text.NativeRendering
opacity: 0.6
maximumLineCount: 3
elide: Text.ElideRight
wrapMode: Text.Wrap
textFormat: TextEdit.RichText
}

View file

@ -0,0 +1,145 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.15
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
Item {
id: root
default property Item contentItem
property bool tapEnabled: false
property bool swipeGestureEnabled: false
property real dragOffset: 0
signal tapped()
signal dismissRequested()
signal configureClicked() // TODO implement settings button
onContentItemChanged: {
contentItem.parent = contentParent;
contentItem.anchors.fill = contentParent;
contentItem.anchors.margins = Kirigami.Units.largeSpacing;
contentParent.children.push(contentItem);
}
implicitHeight: contentParent.implicitHeight
NumberAnimation on dragOffset {
id: dragAnim
duration: PlasmaCore.Units.longDuration
onFinished: {
if (to !== 0) {
root.dismissRequested();
}
}
}
// glow
RectangularGlow {
visible: Math.abs(dragOffset) !== root.width
anchors.topMargin: 1
anchors.leftMargin: 1
anchors.fill: mainCard
cornerRadius: mainCard.radius * 2
glowRadius: 2
spread: 0.2
color: Qt.lighter(PlasmaCore.Theme.backgroundColor, 0.1)
}
// shadow
Rectangle {
visible: Math.abs(dragOffset) !== root.width
anchors.fill: mainCard
anchors.leftMargin: -1
anchors.rightMargin: -1
anchors.bottomMargin: -1
color: Qt.darker(PlasmaCore.Theme.backgroundColor, 1.3)
radius: PlasmaCore.Units.smallSpacing
}
// card
Rectangle {
id: mainCard
anchors.left: parent.left
anchors.leftMargin: root.dragOffset > 0 ? root.dragOffset : 0
anchors.right: parent.right
anchors.rightMargin: root.dragOffset < 0 ? -root.dragOffset : 0
anchors.top: parent.top
color: (root.tapEnabled && mouseArea.pressed) ? Qt.darker(PlasmaCore.Theme.backgroundColor, 1.1) : PlasmaCore.Theme.backgroundColor
radius: PlasmaCore.Units.smallSpacing
implicitHeight: contentParent.implicitHeight
clip: true
// ensure this is behind the content to not interfere
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
if (root.tapEnabled) {
root.tapped()
}
}
}
// content parent
Item {
id: contentParent
anchors.top: parent.top
anchors.left: root.dragOffset > 0 ? parent.left : undefined
anchors.right: root.dragOffset < 0 ? parent.right : undefined
width: root.width
implicitHeight: contentItem.implicitHeight + contentItem.anchors.topMargin + contentItem.anchors.bottomMargin
}
}
DragHandler {
id: dragHandler
enabled: root.swipeGestureEnabled
yAxis.enabled: false
property real startDragOffset: 0
property real startPosition: 0
property bool startActive: false
onTranslationChanged: {
if (startActive) {
startDragOffset = root.dragOffset;
startPosition = translation.x;
startActive = false;
}
root.dragOffset = startDragOffset + (translation.x - startPosition);
}
onActiveChanged: {
dragAnim.stop();
startActive = active;
if (!active) { // release event
let threshold = PlasmaCore.Units.gridUnit * 5; // drag threshold
if (root.dragOffset > threshold) {
dragAnim.to = root.width;
} else if (root.dragOffset < -threshold) {
dragAnim.to = -root.width;
} else {
dragAnim.to = 0;
}
dragAnim.restart();
}
}
}
}

View file

@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.15
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
Item {
id: actionContainer
required property BaseNotificationItem notification
implicitHeight: Math.max(actionFlow.implicitHeight, replyLoader.height)
visible: actionRepeater.count > 0
Flow {
id: actionFlow
width: parent.width
spacing: PlasmaCore.Units.smallSpacing
layoutDirection: Qt.RightToLeft
enabled: !replyLoader.active
opacity: replyLoader.active ? 0 : 1
Behavior on opacity {
NumberAnimation {
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
}
}
// action buttons
Repeater {
id: actionRepeater
model: {
const buttons = [];
var actionNames = (notificationItem.actionNames || []);
var actionLabels = (notificationItem.actionLabels || []);
for (var i = actionNames.length - 1; i >= 0; --i) {
buttons.push({
actionName: actionNames[i],
label: actionLabels[i]
});
}
if (notificationItem.hasReplyAction) {
buttons.unshift({
actionName: "inline-reply",
label: notificationItem.replyActionLabel || i18nc("Reply to message", "Reply")
});
}
return buttons;
}
PlasmaComponents.ToolButton {
flat: false
text: modelData.label || ""
onClicked: {
if (modelData.actionName === "inline-reply") {
replyLoader.beginReply();
return;
}
notificationItem.actionInvoked(modelData.actionName);
}
}
}
}
// inline reply field
Loader {
id: replyLoader
width: parent.width
height: active ? item.implicitHeight : 0
// When there is only one action and it is a reply action, show text field right away
active: false
visible: active
opacity: active ? 1 : 0
x: active ? 0 : parent.width
Behavior on x {
NumberAnimation {
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Behavior on opacity {
NumberAnimation {
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
}
}
function beginReply() {
active = true
replyLoader.item.activate();
}
sourceComponent: NotificationReplyField {
placeholderText: notificationItem.replyPlaceholderText
buttonIconName: notificationItem.replySubmitButtonIconName
buttonText: notificationItem.replySubmitButtonText
onReplied: notificationItem.replied(text)
replying: replyLoader.active
onBeginReplyRequested: replyLoader.beginReply()
}
}
}

View file

@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.8
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.notificationmanager 1.0 as NotificationManager
import org.kde.kcoreaddons 1.0 as KCoreAddons
import "util.js" as Util
RowLayout {
id: notificationHeading
property int notificationType
property var applicationIconSource
property string applicationName
property string originName
property var time
property PlasmaCore.DataSource timeSource
property int jobState
property QtObject jobDetails
property real timeout: 5000
property real remainingTime: 0
spacing: PlasmaCore.Units.smallSpacing
Layout.preferredHeight: Math.max(applicationNameLabel.implicitHeight, PlasmaCore.Units.iconSizes.small)
PlasmaCore.IconItem {
id: applicationIconItem
Layout.topMargin: PlasmaCore.Units.smallSpacing
Layout.bottomMargin: PlasmaCore.Units.smallSpacing
Layout.preferredWidth: PlasmaCore.Units.iconSizes.small
Layout.preferredHeight: PlasmaCore.Units.iconSizes.small
source: notificationHeading.applicationIconSource
usesPlasmaTheme: false
visible: valid
}
PlasmaComponents.Label {
id: applicationNameLabel
Layout.leftMargin: PlasmaCore.Units.smallSpacing
Layout.fillWidth: true
opacity: 0.8
textFormat: Text.PlainText
elide: Text.ElideLeft
text: notificationHeading.applicationName + (notificationHeading.originName ? " · " + notificationHeading.originName : "")
}
Item {
id: spacer
Layout.fillWidth: true
}
}

View file

@ -0,0 +1,171 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.notificationmanager 1.0 as NotificationManager
import org.kde.kirigami 2.12 as Kirigami
import org.kde.kcoreaddons 1.0 as KCoreAddons
import "util.js" as Util
// notification properties are in BaseNotificationItem
BaseNotificationItem {
id: notificationItem
implicitHeight: mainCard.implicitHeight
// notification heading for groups with one element
NotificationGroupHeader {
id: notificationHeading
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
PlasmaCore.ColorScope.colorGroup: PlasmaCore.Theme.HeaderColorGroup
PlasmaCore.ColorScope.inherit: false
visible: !notificationItem.inGroup
height: visible ? implicitHeight : 0
applicationName: notificationItem.applicationName
applicationIconSource: notificationItem.applicationIconSource
originName: notificationItem.originName
notificationType: notificationItem.notificationType
jobState: notificationItem.jobState
jobDetails: notificationItem.jobDetails
time: notificationItem.time
timeSource: notificationItem.timeSource
}
// notification
NotificationCard {
id: mainCard
anchors.topMargin: notificationHeading.visible ? Kirigami.Units.largeSpacing : 0
anchors.top: notificationHeading.bottom
anchors.left: parent.left
anchors.right: parent.right
tapEnabled: notificationItem.hasDefaultAction
onTapped: notificationItem.actionInvoked("default");
swipeGestureEnabled: notificationItem.notificationType != NotificationManager.Notifications.JobType
onDismissRequested: notificationItem.close()
ColumnLayout {
id: column
spacing: 0
// notification summary row
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: PlasmaCore.Units.smallSpacing
// notification summary
PlasmaComponents.Label {
id: summaryLabel
Layout.fillWidth: true
textFormat: Text.PlainText
maximumLineCount: 3
wrapMode: Text.WordWrap
elide: Text.ElideRight
text: Util.determineNotificationHeadingText(notificationItem)
visible: text !== ""
font.weight: Font.DemiBold
}
// notification timestamp
NotificationTimeText {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
notificationType: notificationItem.notificationType
jobState: notificationItem.jobState
jobDetails: notificationItem.jobDetails
time: notificationItem.time
timeSource: notificationItem.timeSource
}
}
// notification contents
RowLayout {
Layout.fillWidth: true
spacing: PlasmaCore.Units.smallSpacing
// notification text
NotificationBodyLabel {
id: bodyLabel
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.fillWidth: true
// HACK RichText does not allow to specify link color and since LineEdit
// does not support StyledText, we have to inject some CSS to force the color,
// cf. QTBUG-81463 and to some extent QTBUG-80354
text: "<style>a { color: " + PlasmaCore.Theme.linkColor + "; }</style>" + notificationItem.body
// Cannot do text !== "" because RichText adds some HTML tags even when empty
visible: notificationItem.body !== ""
}
// notification icon
Item {
id: iconContainer
Layout.preferredWidth: PlasmaCore.Units.iconSizes.large
Layout.preferredHeight: PlasmaCore.Units.iconSizes.large
Layout.topMargin: PlasmaCore.Units.smallSpacing
Layout.bottomMargin: PlasmaCore.Units.smallSpacing
visible: iconItem.active
PlasmaCore.IconItem {
id: iconItem
// don't show two identical icons
readonly property bool active: valid && source != notificationItem.applicationIconSource
anchors.fill: parent
usesPlasmaTheme: false
smooth: true
// don't show a generic "info" icon since this is a notification already
source: notificationItem.icon !== "dialog-information" ? notificationItem.icon : ""
visible: active
}
}
}
// notification actions
NotificationFooterActions {
Layout.fillWidth: true
notification: notificationItem
}
// thumbnails
Loader {
id: thumbnailStripLoader
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
active: notificationItem.urls.length > 0
visible: active
asynchronous: true
sourceComponent: ThumbnailStrip {
leftPadding: -thumbnailStripLoader.Layout.leftMargin
rightPadding: -thumbnailStripLoader.Layout.rightMargin
topPadding: -notificationItem.thumbnailTopPadding
bottomPadding: -thumbnailStripLoader.Layout.bottomMargin
urls: notificationItem.urls
onOpenUrl: notificationItem.openUrl(url)
onFileActionInvoked: notificationItem.fileActionInvoked(action)
}
}
}
}
}

View file

@ -0,0 +1,59 @@
/*
SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.8
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents3
RowLayout {
id: replyRow
signal beginReplyRequested
signal replied(string text)
property bool replying: false
property alias text: replyTextField.text
property string placeholderText
property string buttonIconName
property string buttonText
spacing: PlasmaCore.Units.smallSpacing
function activate() {
replyTextField.forceActiveFocus();
}
PlasmaComponents3.TextField {
id: replyTextField
Layout.fillWidth: true
placeholderText: replyRow.placeholderText || i18nc("Text field placeholder", "Type a reply…")
onAccepted: {
if (replyButton.enabled) {
replyRow.replied(text);
}
}
// Catches mouse click when reply field is already shown to start a reply
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
visible: !replyRow.replying
onPressed: replyRow.beginReplyRequested()
}
}
PlasmaComponents3.Button {
id: replyButton
icon.name: replyRow.buttonIconName || "document-send"
text: replyRow.buttonText || i18nc("@action:button", "Send")
enabled: replyTextField.length > 0
onClicked: replyRow.replied(replyTextField.text)
}
}

View file

@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.8
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.notificationmanager 1.0 as NotificationManager
import org.kde.kcoreaddons 1.0 as KCoreAddons
import "util.js" as Util
PlasmaComponents.Label {
id: ageLabel
property int notificationType: model.type
property int jobState
property QtObject jobDetails
property var time
property PlasmaCore.DataSource timeSource
// notification created/updated time changed
onTimeChanged: updateAgoText()
Connections {
target: timeSource
// clock time changed
function onDataChanged() {
ageLabel.updateAgoText()
}
}
Component.onCompleted: updateAgoText()
function updateAgoText() {
ageLabel.agoText = Util.generateNotificationHeaderAgoText(time, jobState);
}
font.pixelSize: PlasmaCore.Theme.defaultFont.pixelSize * 0.8
// the "n minutes ago" text, for jobs we show remaining time instead
// updated periodically by a Timer hence this property with generate() function
property string agoText: ""
visible: text !== ""
opacity: 0.6
text: Util.generateNotificationHeaderRemainingText(notificationType, jobState, jobDetails) || agoText
}

View file

@ -0,0 +1,136 @@
/*
* SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.kquickcontrolsaddons 2.0 as KQCAddons
MouseArea {
id: thumbnailArea
// The protocol supports multiple URLs but so far it's only used to show
// a single preview image, so this code is simplified a lot to accommodate
// this usecase and drops everything else (fallback to app icon or ListView
// for multiple files)
property var urls
readonly property alias menuOpen: fileMenu.visible
property int _pressX: -1
property int _pressY: -1
property int leftPadding: 0
property int rightPadding: 0
property int topPadding: 0
property int bottomPadding: 0
signal openUrl(string url)
signal fileActionInvoked(QtObject action)
implicitHeight: Math.max(menuButton.height + 2 * menuButton.anchors.topMargin,
Math.round(Math.min(width / 3, width / thumbnailer.ratio)))
+ topPadding + bottomPadding
MobileShell.NotificationFileMenu {
id: fileMenu
url: thumbnailer.url
visualParent: menuButton
onActionTriggered: thumbnailArea.fileActionInvoked(action)
}
MobileShell.NotificationThumbnailer {
id: thumbnailer
readonly property real ratio: pixmapSize.height ? pixmapSize.width / pixmapSize.height : 1
url: urls[0]
// height is dynamic, so request a "square" size and then show it fitting to aspect ratio
size: Qt.size(thumbnailArea.width, thumbnailArea.width)
}
KQCAddons.QPixmapItem {
id: previewBackground
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
layer.enabled: true
opacity: 0.25
pixmap: thumbnailer.pixmap
layer.effect: FastBlur {
source: previewBackground
anchors.fill: parent
radius: 30
}
}
Item {
anchors {
fill: parent
leftMargin: thumbnailArea.leftPadding
rightMargin: thumbnailArea.rightPadding
topMargin: thumbnailArea.topPadding
bottomMargin: thumbnailArea.bottomPadding
}
KQCAddons.QPixmapItem {
id: previewPixmap
anchors.fill: parent
pixmap: thumbnailer.pixmap
smooth: true
fillMode: Image.PreserveAspectFit
}
PlasmaCore.IconItem {
anchors.centerIn: parent
width: height
height: PlasmaCore.Units.roundToIconSize(parent.height)
usesPlasmaTheme: false
source: !thumbnailer.busy && !thumbnailer.hasPreview ? thumbnailer.iconName : ""
}
PlasmaComponents3.BusyIndicator {
anchors.centerIn: parent
running: thumbnailer.busy
visible: thumbnailer.busy
}
PlasmaComponents3.Button {
id: menuButton
anchors {
top: parent.top
right: parent.right
margins: PlasmaCore.Units.smallSpacing
}
Accessible.name: tooltip.text
icon.name: "application-menu"
checkable: true
onPressedChanged: {
if (pressed) {
// fake "pressed" while menu is open
checked = Qt.binding(function() {
return fileMenu.visible;
});
fileMenu.visualParent = this;
// -1 tells it to "align bottom left of visualParent (this)"
fileMenu.open(-1, -1);
}
}
PlasmaComponents3.ToolTip {
id: tooltip
text: i18n("More Options…")
}
}
}
}

View file

@ -0,0 +1,85 @@
.import org.kde.notificationmanager 1.0 as NotificationManager
.import QtQml 2.15 as QtQml
function determineNotificationHeadingText(notificationItem) {
if (notificationItem.notificationType === NotificationManager.Notifications.JobType) {
if (notificationItem.jobState === NotificationManager.Notifications.JobStateSuspended) {
if (notificationItem.summary) {
return i18nc("Job name, e.g. Copying is paused", "%1 (Paused)", notificationItem.summary);
}
} else if (notificationItem.jobState === NotificationManager.Notifications.JobStateStopped) {
if (notificationItem.jobError) {
if (notificationItem.summary) {
return i18nc("Job name, e.g. Copying has failed", "%1 (Failed)", notificationItem.summary);
} else {
return i18n("Job Failed");
}
} else if (notificationItem.summary) {
return i18ndc("plasma_applet_org.kde.plasma.notifications", "Job name, e.g. Copying has finished", "%1 (Finished)", notificationItem.summary);
}
return i18nd("plasma_applet_org.kde.plasma.notifications", "Job Finished");
}
}
// some apps use their app name as summary, avoid showing the same text twice
// try very hard to match the two
if (notificationItem.summary && notificationItem.summary.toLocaleLowerCase().trim() !== notificationItem.applicationName.toLocaleLowerCase().trim()) {
return notificationItem.summary;
}
return "";
}
function generateNotificationHeaderAgoText(time, jobState) {
if (!time || isNaN(time.getTime()) || jobState === NotificationManager.Notifications.JobStateRunning) {
return "";
}
const deltaMinutes = Math.floor((Date.now() - time.getTime()) / 1000 / 60);
if (deltaMinutes < 1) {
return "";
}
// Received less than an hour ago, show relative minutes
if (deltaMinutes < 60) {
return i18nc("Notification was added minutes ago, keep short", "%1m ago", deltaMinutes);
}
// Received less than a day ago, show time, 22 hours so the time isn't as ambiguous between today and yesterday
if (deltaMinutes < 60 * 22) {
return Qt.formatTime(time, Qt.locale().timeFormat(QtQml.Locale.ShortFormat).replace(/.ss?/i, ""));
}
// Otherwise show relative date (Yesterday, "Last Sunday", or just date if too far in the past)
return KCoreAddons.Format.formatRelativeDate(time, QtQml.Locale.ShortFormat);
}
function generateNotificationHeaderRemainingText(notificationType, jobState, jobDetails) {
if (notificationType !== NotificationManager.Notifications.JobType || jobState !== NotificationManager.Notifications.JobStateRunning) {
return "";
}
const details = jobDetails;
if (!details || !details.speed) {
return "";
}
var remaining = details.totalBytes - details.processedBytes;
if (remaining <= 0) {
return "";
}
var eta = remaining / details.speed;
if (eta < 0.5) { // Avoid showing "0 seconds remaining"
return "";
}
if (eta < 60) { // 1 minute
return i18nc("seconds remaining, keep short", "%1 s remaining", Math.round(eta));
}
if (eta < 60 * 60) {// 1 hour
return i18nc("minutes remaining, keep short", "%1m remaining", Math.round(eta / 60));
}
if (eta < 60 * 60 * 5) { // 5 hours max, if it takes even longer there's no real point in showing that
return i18nc("hours remaining, keep short", "%1h remaining", Math.round(eta / 60 / 60));
}
return "";
}

View file

@ -1,4 +0,0 @@
module org.kde.plasma.private.mobileshell
singleton HomeScreenControls 1.0 HomeScreenControls.qml
singleton TopPanelControls 1.0 TopPanelControls.qml

View file

@ -123,6 +123,15 @@ void QuickSetting::setText(const QString &text)
Q_EMIT textChanged(text);
}
void QuickSetting::setStatus(const QString &status)
{
if (m_status == status)
return;
m_status = status;
Q_EMIT statusChanged(status);
}
QQmlListProperty<QObject> QuickSetting::children()
{
return QQmlListProperty<QObject>(this, &m_children);

View file

@ -15,6 +15,7 @@ class QuickSetting : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText REQUIRED NOTIFY textChanged)
Q_PROPERTY(QString status READ status WRITE setStatus REQUIRED NOTIFY statusChanged) // if no status is explicitly set, On/Off is used by default
Q_PROPERTY(QString icon READ iconName WRITE setIconName REQUIRED NOTIFY iconNameChanged)
Q_PROPERTY(QString settingsCommand READ settingsCommand WRITE setSettingsCommand NOTIFY settingsCommandChanged)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
@ -28,6 +29,10 @@ public:
{
return m_text;
}
QString status() const
{
return m_status;
}
QString iconName() const
{
return m_iconName;
@ -42,6 +47,7 @@ public:
}
void setText(const QString &text);
void setStatus(const QString &status);
void setIconName(const QString &iconName);
void setSettingsCommand(const QString &settingsCommand);
void setEnabled(bool enabled);
@ -50,12 +56,14 @@ public:
Q_SIGNALS:
void enabledChanged(bool enabled);
void textChanged(const QString &text);
void statusChanged(const QString &text);
void iconNameChanged(const QString &icon);
void settingsCommandChanged(const QString &settingsCommand);
private:
bool m_enabled = true;
QString m_text;
QString m_status;
QString m_iconName;
QString m_settingsCommand;
QList<QObject *> m_children;

View file

@ -0,0 +1,245 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2018 Bhushan Shah <bshah@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "shellutil.h"
#include <fcntl.h>
#include <qplatformdefs.h>
#include <unistd.h>
#include <KConfigGroup>
#include <KIO/ApplicationLauncherJob>
#include <KLocalizedString>
#include <KNotification>
#include <QDBusPendingReply>
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QGuiApplication>
#include <QProcess>
#include <QScreen>
#include <QStandardPaths>
#include <QtConcurrent/QtConcurrent>
#define FORMAT24H "HH:mm:ss"
constexpr int SCREENSHOT_DELAY = 200;
/* -- Static Helpers --------------------------------------------------------------------------- */
static QImage allocateImage(const QVariantMap &metadata)
{
bool ok;
const uint width = metadata.value(QStringLiteral("width")).toUInt(&ok);
if (!ok) {
return QImage();
}
const uint height = metadata.value(QStringLiteral("height")).toUInt(&ok);
if (!ok) {
return QImage();
}
const uint format = metadata.value(QStringLiteral("format")).toUInt(&ok);
if (!ok) {
return QImage();
}
return QImage(width, height, QImage::Format(format));
}
static QImage readImage(int fileDescriptor, const QVariantMap &metadata)
{
QFile file;
if (!file.open(fileDescriptor, QFileDevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
close(fileDescriptor);
return QImage();
}
QImage result = allocateImage(metadata);
if (result.isNull()) {
return QImage();
}
QDataStream stream(&file);
stream.readRawData(reinterpret_cast<char *>(result.bits()), result.sizeInBytes());
return result;
}
ShellUtil::ShellUtil(QObject *parent)
: QObject{parent}
{
// setHasConfigurationInterface(true);
m_kscreenInterface = new org::kde::KScreen(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/kscreen"), QDBusConnection::sessionBus(), this);
m_screenshotInterface = new OrgKdeKWinScreenShot2Interface(QStringLiteral("org.kde.KWin.ScreenShot2"),
QStringLiteral("/org/kde/KWin/ScreenShot2"),
QDBusConnection::sessionBus(),
this);
m_localeConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig);
m_localeConfigWatcher = KConfigWatcher::create(m_localeConfig);
// watch for changes to locale config, to update 12/24 hour time
connect(m_localeConfigWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void {
if (group.name() == "Locale") {
// we have to reparse for new changes (from system settings)
m_localeConfig->reparseConfiguration();
Q_EMIT isSystem24HourFormatChanged();
}
});
}
ShellUtil::~ShellUtil() = default;
ShellUtil *ShellUtil::instance()
{
static ShellUtil *inst = new ShellUtil(nullptr);
return inst;
}
void ShellUtil::executeCommand(const QString &command)
{
qWarning() << "Executing" << command;
const QStringList commandAndArguments = QProcess::splitCommand(command);
QProcess::startDetached(commandAndArguments.front(), commandAndArguments.mid(1));
}
void ShellUtil::toggleTorch()
{
// FIXME this is hardcoded to the PinePhone for now
static auto FLASH_SYSFS_PATH = "/sys/devices/platform/led-controller/leds/white:flash/brightness";
int fd = open(FLASH_SYSFS_PATH, O_WRONLY);
if (fd < 0) {
qWarning() << "Unable to open file %s" << FLASH_SYSFS_PATH;
return;
}
write(fd, m_running ? "0" : "1", 1);
close(fd);
m_running = !m_running;
Q_EMIT torchChanged(m_running);
}
bool ShellUtil::torchEnabled() const
{
return m_running;
}
bool ShellUtil::autoRotate()
{
QDBusPendingReply<bool> reply = m_kscreenInterface->getAutoRotate();
reply.waitForFinished();
if (reply.isError()) {
qWarning() << "Getting auto rotate failed:" << reply.error().name() << reply.error().message();
return false;
} else {
return reply.value();
}
}
void ShellUtil::setAutoRotate(bool value)
{
QDBusPendingReply<> reply = m_kscreenInterface->setAutoRotate(value);
reply.waitForFinished();
if (reply.isError()) {
qWarning() << "Setting auto rotate failed:" << reply.error().name() << reply.error().message();
} else {
emit autoRotateChanged(value);
}
}
void ShellUtil::handleMetaDataReceived(const QVariantMap &metadata, int fd)
{
const QString type = metadata.value(QStringLiteral("type")).toString();
if (type != QLatin1String("raw")) {
qWarning() << "Unsupported metadata type:" << type;
return;
}
auto watcher = new QFutureWatcher<QImage>(this);
connect(watcher, &QFutureWatcher<QImage>::finished, this, [watcher]() {
watcher->deleteLater();
QString filePath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
if (filePath.isEmpty()) {
qWarning() << "Couldn't find a writable location for the screenshot!";
return;
}
QDir picturesDir(filePath);
if (!picturesDir.mkpath(QStringLiteral("Screenshots"))) {
qWarning() << "Couldn't create folder at" << picturesDir.path() + QStringLiteral("/Screenshots") << "to take screenshot.";
return;
}
filePath += QStringLiteral("/Screenshots/Screenshot_%1.png").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_hhmmss")));
const auto m_result = watcher->result();
if (m_result.isNull() || !m_result.save(filePath)) {
qWarning() << "Screenshot failed";
} else {
KNotification *notif = new KNotification("captured");
notif->setComponentName(QStringLiteral("plasma_phone_components"));
notif->setTitle(i18n("New Screenshot"));
notif->setUrls({QUrl::fromLocalFile(filePath)});
notif->setText(i18n("New screenshot saved to %1", filePath));
notif->sendEvent();
}
});
watcher->setFuture(QtConcurrent::run(readImage, fd, metadata));
}
void ShellUtil::takeScreenshot()
{
// wait ~200 ms to wait for rest of animations
QTimer::singleShot(SCREENSHOT_DELAY, [=]() {
int lPipeFds[2];
if (pipe2(lPipeFds, O_CLOEXEC) != 0) {
qWarning() << "Could not take screenshot";
return;
}
// We don't have access to the ScreenPool so we'll just take the first screen
QVariantMap options;
options.insert(QStringLiteral("native-resolution"), true);
auto pendingCall = m_screenshotInterface->CaptureScreen(qGuiApp->screens().constFirst()->name(), options, QDBusUnixFileDescriptor(lPipeFds[1]));
close(lPipeFds[1]);
auto pipeFileDescriptor = lPipeFds[0];
auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, pipeFileDescriptor]() {
watcher->deleteLater();
const QDBusPendingReply<QVariantMap> reply = *watcher;
if (reply.isError()) {
qWarning() << "Screenshot request failed:" << reply.error().message();
} else {
handleMetaDataReceived(reply, pipeFileDescriptor);
}
});
});
}
bool ShellUtil::isSystem24HourFormat()
{
KConfigGroup localeSettings = KConfigGroup(m_localeConfig, "Locale");
QString timeFormat = localeSettings.readEntry("TimeFormat", QStringLiteral(FORMAT24H));
return timeFormat == QStringLiteral(FORMAT24H);
}
void ShellUtil::launchApp(const QString &app)
{
const KService::Ptr appService = KService::serviceByDesktopName(app);
if (!appService) {
qWarning() << "Could not find" << app;
return;
}
auto job = new KIO::ApplicationLauncherJob(appService, this);
job->start();
}

View file

@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QObject>
#include <KConfigWatcher>
#include <KSharedConfig>
#include "kscreeninterface.h"
#include "screenshot2interface.h"
class ShellUtil : public QObject
{
Q_OBJECT
Q_PROPERTY(bool autoRotateEnabled READ autoRotate WRITE setAutoRotate NOTIFY autoRotateChanged);
Q_PROPERTY(bool torchEnabled READ torchEnabled NOTIFY torchChanged);
Q_PROPERTY(bool isSystem24HourFormat READ isSystem24HourFormat NOTIFY isSystem24HourFormatChanged);
public:
ShellUtil(QObject *parent = nullptr);
~ShellUtil() override;
static ShellUtil *instance();
public Q_SLOTS:
void executeCommand(const QString &command);
void launchApp(const QString &app);
void toggleTorch();
void takeScreenshot();
bool autoRotate();
void setAutoRotate(bool value);
bool torchEnabled() const;
bool isSystem24HourFormat();
Q_SIGNALS:
void autoRotateChanged(bool value);
void torchChanged(bool value);
void isSystem24HourFormatChanged();
private:
void handleMetaDataReceived(const QVariantMap &metadata, int fd);
bool m_running = false;
KConfigWatcher::Ptr m_localeConfigWatcher;
KSharedConfig::Ptr m_localeConfig;
org::kde::KScreen *m_kscreenInterface;
OrgKdeKWinScreenShot2Interface *m_screenshotInterface;
};

View file

@ -1,9 +1,5 @@
qt_add_dbus_interfaces(DBUS_SRCS dbus/org.kde.KWin.ScreenShot2.xml
dbus/org.kde.KScreen.xml)
set(phonepanel_SRCS
phonepanel.cpp
${DBUS_SRCS}
)
add_library(plasma_applet_phonepanel MODULE ${phonepanel_SRCS})
@ -15,8 +11,6 @@ target_link_libraries(plasma_applet_phonepanel
Qt::DBus
KF5::Plasma
KF5::I18n
KF5::Notifications
KF5::KIOGui
KF5::Service
)

View file

@ -1,71 +0,0 @@
/*
* SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.plasmoid 2.0
PlasmaCore.ToolTipArea {
id: appletRoot
objectName: "org.kde.desktop-CompactApplet"
anchors.fill: parent
icon: plasmoid.icon
mainText: plasmoid.toolTipMainText
subText: plasmoid.toolTipSubText
location: if (plasmoid.parent && plasmoid.parent.parent.objectName === "hiddenTasksColumn" && plasmoid.location !== PlasmaCore.Types.LeftEdge) {
return PlasmaCore.Types.RightEdge;
} else {
return plasmoid.location;
}
active: !plasmoid.expanded
textFormat: plasmoid.toolTipTextFormat
mainItem: plasmoid.toolTipItem ? plasmoid.toolTipItem : null
property Item fullRepresentation
property Item compactRepresentation
Connections {
target: plasmoid
onContextualActionsAboutToShow: appletRoot.hideToolTip()
}
Layout.minimumWidth: {
switch (plasmoid.formFactor) {
case PlasmaCore.Types.Vertical:
return 0;
case PlasmaCore.Types.Horizontal:
return height;
default:
return PlasmaCore.Units.gridUnit * 3;
}
}
Layout.minimumHeight: {
switch (plasmoid.formFactor) {
case PlasmaCore.Types.Vertical:
return width;
case PlasmaCore.Types.Horizontal:
return 0;
default:
return PlasmaCore.Units.gridUnit * 3;
}
}
onCompactRepresentationChanged: {
if (compactRepresentation) {
compactRepresentation.parent = appletRoot;
compactRepresentation.anchors.fill = appletRoot;
compactRepresentation.visible = true;
}
appletRoot.visible = true;
}
}

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="General">
<entry name="AppletOrder" type="String">
<label>encoded order of items</label>
</entry>
</group>
</kcfg>

View file

@ -1,30 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.6
import QtQuick.Layouts 1.4
import QtQuick.Controls 2.4 as QQC2
import QtGraphicalEffects 1.12
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.kirigami 2.12 as Kirigami
QQC2.Control {
id: root
leftPadding: frameSvg.margins.left
topPadding: frameSvg.margins.top
rightPadding: frameSvg.margins.right
bottomPadding: frameSvg.margins.bottom
background: PlasmaCore.FrameSvgItem {
id: frameSvg
imagePath: "widgets/background"
MouseArea {
anchors.fill: parent
}
}
}

View file

@ -1,70 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick.Layouts 1.3
import QtQml.Models 2.12
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
DrawerBackground {
id: fullContainer
property Item applet
property ObjectModel fullRepresentationModel
property ListView fullRepresentationView
visible: shouldBeVisible
property bool shouldBeVisible: applet && (applet.status != PlasmaCore.Types.HiddenStatus && applet.status != PlasmaCore.Types.PassiveStatus)
height: parent.height
width: visible ? quickSettings.width : 0
Layout.minimumHeight: applet && applet.switchHeight
onShouldBeVisibleChanged: fullContainer.visible = fullContainer.shouldBeVisible
Component.onCompleted: visibleChanged();
onVisibleChanged: {
if (visible) {
for (var i = 0; i < fullRepresentationModel.count; ++i) {
if (fullRepresentationModel.get(i) === this) {
return;
}
}
if (applet && applet.pluginName == "org.kde.plasma.notifications") {
fullRepresentationModel.insert(0, this);
} else {
fullRepresentationModel.append(this);
}
fullRepresentationView.forceLayout();
fullRepresentationView.currentIndex = ObjectModel.index;
fullRepresentationView.positionViewAtIndex(ObjectModel.index, ListView.Contain)
} else if (ObjectModel.index >= 0) {
fullRepresentationModel.remove(ObjectModel.index);
fullRepresentationView.forceLayout();
}
if (!shouldBeVisible) {
visible = false;
}
}
Connections {
target: fullContainer.applet
function onActivated() {
if (!visible) {
return;
}
fullRepresentationView.currentIndex = ObjectModel.index;
}
}
Connections {
target: fullContainer.applet.fullRepresentationItem
function onParentChanged() {
fullContainer.applet.fullRepresentationItem.parent = fullContainer;
}
}
}

View file

@ -1,25 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick.Layouts 1.3
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.notificationmanager 1.0 as NotificationManager
FullContainer {
id: fullContainer
shouldBeVisible: applet && historyModel.count > 0
visible: shouldBeVisible
NotificationManager.Notifications {
id: historyModel
showExpired: true
showDismissed: true
expandUnread: true
}
}

View file

@ -1,174 +0,0 @@
/*
* SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
var layout;
var root;
var plasmoid;
var lastSpacer;
function restore() {
var configString = String(plasmoid.configuration.AppletOrder)
//array, a cell for encoded item order
var itemsArray = configString.split(";");
//map applet id->order in panel
var idsOrder = new Object();
//map order in panel -> applet pointer
var appletsOrder = new Object();
for (var i = 0; i < itemsArray.length; i++) {
//property name: applet id
//property value: order
idsOrder[itemsArray[i]] = i;
}
for (var i = 0; i < plasmoid.applets.length; ++i) {
if (idsOrder[plasmoid.applets[i].id] !== undefined) {
appletsOrder[idsOrder[plasmoid.applets[i].id]] = plasmoid.applets[i];
//ones that weren't saved in AppletOrder go to the end
} else {
appletsOrder["unordered"+i] = plasmoid.applets[i];
}
}
//finally, restore the applets in the correct order
for (var i in appletsOrder) {
root.addApplet(appletsOrder[i], -1, -1)
}
//rewrite, so if in the orders there were now invalid ids or if some were missing creates a correct list instead
save();
}
function save() {
var ids = new Array();
for (var i = 0; i < layout.children.length; ++i) {
var child = layout.children[i];
if (child.applet) {
ids.push(child.applet.id);
}
}
plasmoid.configuration.AppletOrder = ids.join(';');
}
function removeApplet (applet) {
for (var i = layout.children.length - 1; i >= 0; --i) {
var child = layout.children[i];
if (child.applet === applet) {
child.destroy();
}
}
}
//insert item2 before item1
function insertBefore(item1, item2) {
if (item1 === item2) {
return;
}
var removed = new Array();
var child;
var i;
for (i = layout.children.length - 1; i >= 0; --i) {
child = layout.children[i];
removed.push(child);
child.parent = root;
if (child === item1) {
break;
}
}
item2.parent = layout;
for (var j = removed.length - 1; j >= 0; --j) {
removed[j].parent = layout;
}
return i;
}
//insert item2 after item1
function insertAfter(item1, item2) {
if (item1 === item2) {
return;
}
var removed = new Array();
var child;
var i;
for (i = layout.children.length - 1; i >= 0; --i) {
child = layout.children[i];
//never ever insert after lastSpacer
if (child === lastSpacer && item1 === lastSpacer) {
removed.push(child);
child.parent = root;
break;
} else if (child === item1) {
break;
}
removed.push(child);
child.parent = root;
}
item2.parent = layout;
for (var j = removed.length - 1; j >= 0; --j) {
removed[j].parent = layout;
}
return i;
}
function insertAtIndex(item, position) {
if (position < 0 || position >= layout.children.length) {
return;
}
//never ever insert after lastSpacer
if (layout.children[position] === lastSpacer) {
--position;
}
var removedItems = new Array();
for (var i = position; i < layout.children.length; ++i) {
var child = layout.children[position];
child.parent = root;
removedItems.push(child);
}
item.parent = layout;
for (var i in removedItems) {
removedItems[i].parent = layout;
}
}
function insertAtCoordinates(item, x, y) {
if (root.isHorizontal) {
y = layout.height / 2;
} else {
x = layout.width / 2;
}
var child = layout.childAt(x, y);
if (!child || child === item) {
child = layout.children[0];
}
item.parent = root;
//PlasmaCore.Types.Vertical = 3
if ((plasmoid.formFactor === 3 && y < child.y + child.height/2) ||
(plasmoid.formFactor !== 3 && x < child.x + child.width/2)) {
return insertBefore(child, item);
} else {
return insertAfter(child, item);
}
}

View file

@ -1,232 +0,0 @@
/*
* SPDX-FileCopyrightText: 2014 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
NanoShell.FullScreenOverlay {
id: window
property int offset: 0 // slide progress
property int openThreshold: PlasmaCore.Units.gridUnit * 2
property bool userInteracting: false
property bool initiallyOpened: false // whether the panel is already open after a touch release (then don't restrict to collapsed height)
// height when quicksettings is fully open
required property int fullyOpenHeight
// flickable contentY
readonly property int openedContentY: wideScreen || offset > (collapsedHeight + openThreshold) ? -topEmptyAreaHeight : offsetToContentY(collapsedHeight)
readonly property int closedContentY: mainFlickable.contentHeight
readonly property bool wideScreen: width > height || width > PlasmaCore.Units.gridUnit * 45
readonly property int drawerWidth: wideScreen ? contentItem.implicitWidth : width
property int drawerX: 0
property alias fixedArea: mainScope
property alias flickable: mainFlickable
color: "transparent"
property alias contentItem: contentArea.contentItem
property int topPanelHeight
property int collapsedHeight
property real topEmptyAreaHeight
property bool appletsShown: false // whether notifications or media player applets are shown
signal closed
width: Screen.width
height: Screen.height
Component.onCompleted: plasmoid.nativeInterface.panel = window;
onVisibleChanged: if (!visible) {
closed()
}
onInitiallyOpenedChanged: {
if (initiallyOpened) mainFlickable.focus = true;
}
function offsetToContentY(num) { return -num + window.fullyOpenHeight; }
function contentYToOffset(num) { return offsetToContentY(num); }
// avoids binding loops
function updateOffset(delta) {
// only go to collapsed height for mousearea when not widescreen
let maximum = window.wideScreen ? window.fullyOpenHeight : collapsedHeight + openThreshold / 2;
offset = Math.max(0, Math.min(maximum, offset + delta));
if (!mainFlickable.moving && !mainFlickable.dragging && !mainFlickable.flicking) {
mainFlickable.contentY = offsetToContentY(window.offset);
}
}
enum MovementDirection {
None = 0,
Up,
Down
}
property int direction: SlidingContainer.MovementDirection.None
function cancelAnimations() {
closeAnim.stop();
openAnim.stop();
}
function open() {
cancelAnimations();
openAnim.restart();
initiallyOpened = true;
}
function close() {
cancelAnimations();
closeAnim.restart();
initiallyOpened = false;
}
function expand() {
cancelAnimations();
expandAnim.restart();
initiallyOpened = true;
}
function updateState() {
cancelAnimations();
if (window.offset <= 0) {
// close immediately, so that we don't have to wait PlasmaCore.Units.longDuration
window.visible = false;
close();
} else if (window.direction === SlidingContainer.MovementDirection.None) {
if (window.offset < openThreshold) {
close();
} else {
open();
}
} else if (offset > openThreshold && window.direction === SlidingContainer.MovementDirection.Down) {
open();
} else if (mainFlickable.contentY > openThreshold) {
close();
} else {
open();
}
}
Timer {
id: updateStateTimer
interval: 0
onTriggered: updateState()
}
onActiveChanged: {
if (!active) {
close();
}
}
PropertyAnimation {
id: closeAnim
target: mainFlickable
properties: "contentY"
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
to: window.closedContentY
onFinished: {
window.visible = false;
}
}
PropertyAnimation {
id: openAnim
target: mainFlickable
properties: "contentY"
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
to: window.openedContentY
}
PropertyAnimation {
id: expandAnim
target: mainFlickable
properties: "contentY"
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
to: 0
}
// fullscreen background
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.75)
opacity: (appletsShown ? 0.85 : 0.6) * Math.max(0, Math.min(1, offset / window.collapsedHeight))
Behavior on opacity { // smooth opacity changes
NumberAnimation { duration: 70 }
}
}
PlasmaCore.ColorScope {
id: mainScope
colorGroup: PlasmaCore.Theme.ViewColorGroup
anchors.fill: parent
Flickable {
id: mainFlickable
anchors.fill: parent
property real oldContentY
contentY: contentHeight
onContentYChanged: {
if (contentY === oldContentY) {
window.direction = SlidingContainer.MovementDirection.None;
} else {
window.direction = contentY > oldContentY ? SlidingContainer.MovementDirection.Up : SlidingContainer.MovementDirection.Down;
}
window.offset = contentYToOffset(contentY);
oldContentY = contentY;
// close panel immediately after panel is not shown, and the flickable is not being dragged
if (initiallyOpened && window.offset <= 0 && !mainFlickable.dragging && !closeAnim.running && !openAnim.running) {
window.updateState();
focus = false;
}
}
boundsMovement: Flickable.StopAtBounds
contentWidth: window.width
contentHeight: window.height
bottomMargin: window.height
onMovementStarted: {
window.cancelAnimations();
window.userInteracting = true;
}
onFlickStarted: window.userInteracting = true;
onMovementEnded: {
window.userInteracting = false;
window.updateState();
}
onFlickEnded: {
window.userInteracting = true;
window.updateState();
}
MouseArea {
id: dismissArea
z: 2
width: parent.width
height: mainFlickable.contentHeight
onClicked: window.close();
// actual sliding contents
PlasmaComponents.Control {
id: contentArea
z: 1
x: Math.max(0, Math.min(window.drawerX, window.width - window.drawerWidth))
width: Math.min(window.width, window.drawerWidth)
}
}
}
}
}

View file

@ -1,32 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.6
import QtQuick.Layouts 1.4
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
RowLayout {
property alias icon: icon.source
property alias text: label.text
PlasmaCore.IconItem {
id: icon
colorGroup: PlasmaCore.ColorScope.colorGroup
Layout.fillHeight: true
Layout.preferredWidth: height
}
PlasmaComponents.Label {
id: label
visible: text.length > 0
color: PlasmaCore.ColorScope.textColor
font.pixelSize: parent.height / 2
}
}

View file

@ -1,33 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import "providers"
Item {
required property QtObject provider
width: strengthIcon.height
Layout.minimumWidth: strengthIcon.height
PlasmaCore.IconItem {
id: strengthIcon
colorGroup: PlasmaCore.ColorScope.colorGroup
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: height
height: parent.height
source: provider.icon
}
}

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
@ -8,7 +8,6 @@
import QtQuick 2.12
import QtQuick.Layouts 1.3
import QtQml.Models 2.12
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.12 as Kirigami
@ -16,21 +15,19 @@ import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.workspace.components 2.0 as PlasmaWorkspace
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
import "LayoutManager.js" as LayoutManager
import "quicksettings"
import "indicators" as Indicators
import "indicators/providers" as IndicatorProviders
Item {
id: root
readonly property bool showingApp: !MobileShell.HomeScreenControls.homeScreenVisible
readonly property color backgroundColor: NanoShell.StartupFeedback.visible
? NanoShell.StartupFeedback.backgroundColor
: topPanel.colorScopeColor
Plasmoid.backgroundHints: showingApp ? PlasmaCore.Types.StandardBackground : PlasmaCore.Types.NoBackground
width: 480
height: PlasmaCore.Units.gridUnit
@ -44,330 +41,46 @@ Item {
Binding {
target: MobileShell.TopPanelControls
property: "inSwipe"
value: slidingPanel.userInteracting
value: drawer.dragging
}
Connections {
target: MobileShell.TopPanelControls
function onStartSwipe() {
swipeMouseArea.startSwipe(0);
swipeArea.startSwipe();
}
function onEndSwipe() {
swipeMouseArea.endSwipe();
swipeArea.endSwipe();
}
function onRequestRelativeScroll(offsetY) {
swipeMouseArea.updateOffset(offsetY);
swipeArea.updateOffset(offsetY);
}
}
//END API implementation
Plasmoid.backgroundHints: showingApp ? PlasmaCore.Types.StandardBackground : PlasmaCore.Types.NoBackground
property Item toolBox
property int buttonHeight: width/4
property bool reorderingApps: false
property var layoutManager: LayoutManager
readonly property color backgroundColor: NanoShell.StartupFeedback.visible ? NanoShell.StartupFeedback.backgroundColor : topPanel.colorScopeColor
readonly property bool showingApp: !MobileShell.HomeScreenControls.homeScreenVisible
readonly property bool hasTasks: tasksModel.count > 0
Containment.onAppletAdded: {
addApplet(applet, x, y);
LayoutManager.save();
}
function addApplet(applet, x, y) {
var compactContainer = compactContainerComponent.createObject(topPanel.applets)
print("Applet added: " + applet + " " + applet.title)
applet.parent = compactContainer;
compactContainer.applet = applet;
applet.anchors.fill = compactContainer;
applet.visible = true;
//FIXME: make a way to instantiate fullRepresentationItem without the open/close dance
applet.expanded = true
applet.expanded = false
var fullContainer = null;
if (applet.pluginName == "org.kde.plasma.notifications") {
fullContainer = fullNotificationsContainerComponent.createObject(fullRepresentationView.contentItem, {"fullRepresentationModel": fullRepresentationModel, "fullRepresentationView": fullRepresentationView});
} else {
fullContainer = fullContainerComponent.createObject(fullRepresentationView.contentItem, {"fullRepresentationModel": fullRepresentationModel, "fullRepresentationView": fullRepresentationView});
}
// applet.fullRepresentationItem.parent = fullContainer;
fullContainer.applet = applet;
fullContainer.contentItem = applet.fullRepresentationItem;
//applet.fullRepresentationItem.anchors.fill = fullContainer;
}
Component.onCompleted: {
LayoutManager.plasmoid = plasmoid;
LayoutManager.root = root;
LayoutManager.layout = appletsLayout;
LayoutManager.restore();
}
TaskManager.TasksModel {
id: tasksModel
sortMode: TaskManager.TasksModel.SortVirtualDesktop
groupMode: TaskManager.TasksModel.GroupDisabled
screenGeometry: plasmoid.screenGeometry
//FIXME: workaround
Component.onCompleted: tasksModel.countChanged();
}
PlasmaCore.DataSource {
id: statusNotifierSource
engine: "statusnotifieritem"
interval: 0
onSourceAdded: {
connectSource(source)
}
Component.onCompleted: {
connectedSources = sources
}
}
RowLayout {
id: appletsLayout
Layout.minimumHeight: Math.max(root.height, Math.round(Layout.preferredHeight / root.height) * root.height)
}
//todo: REMOVE?
Component {
id: compactContainerComponent
Item {
property Item applet
visible: applet && (applet.status != PlasmaCore.Types.HiddenStatus && applet.status != PlasmaCore.Types.PassiveStatus)
Layout.fillHeight: true
Layout.minimumWidth: applet && applet.compactRepresentationItem ? Math.max(applet.compactRepresentationItem.Layout.minimumWidth, topPanel.applets.height) : topPanel.applets.height
Layout.maximumWidth: Layout.minimumWidth
}
}
Component {
id: fullContainerComponent
FullContainer {}
}
Component {
id: fullNotificationsContainerComponent
FullNotificationsContainer {}
}
// indicator providers
IndicatorProviders.BatteryProvider {
id: batteryProvider
readonly property var setting: HomeScreenComponents.QuickSetting {
text: i18n("Battery")
icon: "battery-full" + (batteryProvider.pluggedIn ? "-charging" : "")
enabled: false
settingsCommand: "plasma-open-settings kcm_mobile_power"
}
Component.onCompleted: quickSettings.quickSettingsModel.include(setting)
}
IndicatorProviders.BluetoothProvider {
id: bluetoothProvider
}
property alias signalStrengthProvider: signalStrengthProviderLoader.item
Loader {
id: signalStrengthProviderLoader
source: Qt.resolvedUrl("indicators/providers/SignalStrengthProvider.qml")
}
IndicatorProviders.VolumeProvider {
id: volumeProvider
readonly property var setting: HomeScreenComponents.QuickSetting {
text: i18n("Sound")
icon: "audio-speakers-symbolic"
enabled: false
settingsCommand: "plasma-open-settings kcm_pulseaudio"
function toggle() {
volumeProvider.showVolumeOverlay()
}
}
Component.onCompleted: quickSettings.quickSettingsModel.include(setting)
}
IndicatorProviders.WifiProvider {
id: wifiProvider
// we want to bind global shortcuts here
MobileShell.VolumeProvider.bindShortcuts = true;
}
// top panel component
IndicatorsRow {
MobileShell.StatusBar {
id: topPanel
anchors.fill: parent
z: 1
colorGroup: showingApp ? PlasmaCore.Theme.HeaderColorGroup : PlasmaCore.Theme.ComplementaryColorGroup
backgroundColor: !showingApp ? "transparent" : root.backgroundColor
showDropShadow: !showingApp
showDropShadow: !root.showingApp
colorGroup: root.showingApp ? PlasmaCore.Theme.HeaderColorGroup : PlasmaCore.Theme.ComplementaryColorGroup
backgroundColor: !root.showingApp ? "transparent" : root.backgroundColor
}
// initial swipe down gesture
MouseArea {
id: swipeMouseArea
z: 99
property int oldMouseY: 0
function startSwipe(mouseX) {
slidingPanel.cancelAnimations();
slidingPanel.drawerX = Math.min(Math.max(0, mouseX - slidingPanel.drawerWidth/2), slidingPanel.width - slidingPanel.contentItem.width)
slidingPanel.userInteracting = true;
slidingPanel.flickable.contentY = slidingPanel.closedContentY;
slidingPanel.visible = true;
}
function endSwipe() {
slidingPanel.userInteracting = false;
slidingPanel.updateState();
}
function updateOffset(offsetY) {
slidingPanel.updateOffset(offsetY);
}
MobileShell.ActionDrawerOpenSurface {
id: swipeArea
actionDrawer: drawer
anchors.fill: parent
onPressed: {
oldMouseY = mouse.y;
startSwipe(mouse.x);
}
onReleased: endSwipe()
onCanceled: endSwipe()
onPositionChanged: {
updateOffset(mouse.y - oldMouseY);
oldMouseY = mouse.y;
}
}
// sliding component
SlidingContainer {
id: slidingPanel
width: plasmoid.availableScreenRect.width
height: plasmoid.availableScreenRect.height
topPanelHeight: topPanel.height
topEmptyAreaHeight: quickSettings.topEmptyAreaHeight
collapsedHeight: quickSettings.collapsedHeight
fullyOpenHeight: quickSettings.expandedHeight
appletsShown: fullRepresentationView.count > 0
offset: quickSettings.height
onClosed: quickSettings.closed()
contentItem: MouseArea {
// mousearea captures touch presses so that the flickable picks them up for swiping
implicitWidth: slidingPanel.wideScreen ? panelContents.implicitWidth : slidingPanel.width
implicitHeight: Math.min(slidingPanel.height, quickSettings.implicitHeight)
GridLayout {
id: panelContents
width: slidingPanel.wideScreen ? Math.min(parent.width, implicitWidth) : parent.width
columns: slidingPanel.wideScreen ? 2 : 1
rows: slidingPanel.wideScreen ? 1 : 2
QuickSettingsPanel {
id: quickSettings
property int trueHeight: height + Math.round(Kirigami.Units.gridUnit * 1.5) // add height of bottom bar
z: 4
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: slidingPanel.wideScreen ? Math.min(slidingPanel.width/2, PlasmaCore.Units.gridUnit * 25) : panelContents.width
parentSlidingPanel: slidingPanel
onExpandRequested: slidingPanel.expand()
onCloseRequested: slidingPanel.close()
}
// notifications and media player
ListView {
id: fullRepresentationView
implicitHeight: PlasmaCore.Units.gridUnit * 20
Layout.preferredWidth: slidingPanel.wideScreen ? Math.min(slidingPanel.width/2, quickSettings.width*fullRepresentationModel.count) : panelContents.width
Layout.preferredHeight: slidingPanel.wideScreen
? Math.min(PlasmaCore.Units.gridUnit * 20, Math.max(PlasmaCore.Units.gridUnit * 15, quickSettings.implicitHeight))
: Math.min(plasmoid.screenGeometry.height - quickSettings.implicitHeight - bottomBar.height + slidingPanel.topEmptyAreaHeight, implicitHeight)
z: 1
interactive: true//count > 0 && width < contentWidth
clip: slidingPanel.wideScreen
y: slidingPanel.wideScreen ? 0 : quickSettings.trueHeight
opacity: {
if (slidingPanel.wideScreen) {
return 1;
} else {
return fullRepresentationModel.count > 0 && slidingPanel.offset / slidingPanel.collapsedHeight;
}
}
//preferredHighlightBegin: slidingPanel.drawerX
cacheBuffer: width * 100
highlightFollowsCurrentItem: true
highlightRangeMode: ListView.ApplyRange
highlightMoveDuration: PlasmaCore.Units.longDuration
snapMode: slidingPanel.wideScreen ? ListView.NoSnap : ListView.SnapOneItem
model: ObjectModel {
id: fullRepresentationModel
}
orientation: ListView.Horizontal
MouseArea {
parent: fullRepresentationView.contentItem
anchors.fill: parent
z: -1
onClicked: slidingPanel.close()
}
}
}
}
DrawerBackground {
id: bottomBar
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
parent: slidingPanel.fixedArea
opacity: fullRepresentationView.opacity
visible: !slidingPanel.wideScreen && fullRepresentationModel.count > 1
z: 100
contentItem: RowLayout {
PlasmaComponents.TabBar {
Layout.fillWidth: true
Layout.fillHeight: true
position: PlasmaComponents.TabBar.Footer
Text {
text:fullRepresentationModel.count
}
Repeater {
model: fullRepresentationView.count
delegate: PlasmaComponents.TabButton {
implicitHeight: parent.height
text: fullRepresentationModel.get(index).applet.title
checked: fullRepresentationView.currentIndex === index
onClicked: fullRepresentationView.currentIndex = index
}
}
}
PlasmaComponents.ToolButton {
Layout.fillHeight: true
Layout.preferredWidth: height
icon.name: "paint-none"
onClicked: slidingPanel.close();
}
}
}
MobileShell.ActionDrawer {
id: drawer
}
}

View file

@ -1,146 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
ColumnLayout {
id: delegateRoot
spacing: PlasmaCore.Units.smallSpacing
signal closeRequested
signal panelClosed
// Model interface
required property string text
required property string icon
required property bool enabled
required property string settingsCommand
required property var toggleFunction
property alias labelOpacity: label.opacity
required property real boundingWidth
property color disabledButtonColor: PlasmaCore.Theme.backgroundColor
property color disabledPressedButtonColor: Qt.darker(disabledButtonColor, 1.1)
property color enabledButtonColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.ColorScope.highlightColor, {"alpha": 0.4*255})
property color enabledPressedButtonColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.ColorScope.highlightColor, {"alpha": 0.6*255});
Rectangle {
id: iconButton
Layout.preferredWidth: PlasmaCore.Units.iconSizes.large + PlasmaCore.Units.smallSpacing
Layout.minimumHeight: width
Layout.alignment: Qt.AlignHCenter
radius: PlasmaCore.Units.smallSpacing
border.color: delegateRoot.enabled ?
Qt.darker(Kirigami.ColorUtils.adjustColor(PlasmaCore.ColorScope.highlightColor, {}), 1.25) :
Kirigami.ColorUtils.adjustColor(PlasmaCore.ColorScope.textColor, {"alpha": 0.2*255})
color: {
if (delegateRoot.enabled) {
return iconMouseArea.pressed ? enabledPressedButtonColor : enabledButtonColor
} else {
return iconMouseArea.pressed ? disabledPressedButtonColor : disabledButtonColor
}
}
PlasmaCore.IconItem {
id: icon
anchors.centerIn: parent
implicitWidth: Math.round(parent.width * 0.6)
implicitHeight: width
source: delegateRoot.icon
}
MouseArea {
id: iconMouseArea
anchors.fill: parent
onClicked: {
if (delegateRoot.toggle) {
delegateRoot.toggle();
} else if (delegateRoot.toggleFunction) {
delegateRoot.toggleFunction();
} else if (delegateRoot.settingsCommand) {
NanoShell.StartupFeedback.open(
delegateRoot.icon,
delegateRoot.text,
icon.Kirigami.ScenePosition.x + icon.width/2,
icon.Kirigami.ScenePosition.y + icon.height/2,
Math.min(icon.width, icon.height))
plasmoid.nativeInterface.executeCommand(delegateRoot.settingsCommand);
root.closeRequested();
}
}
onPressAndHold: {
if (delegateRoot.settingsCommand) {
NanoShell.StartupFeedback.open(
delegateRoot.icon,
delegateRoot.text,
icon.Kirigami.ScenePosition.x + icon.width/2,
icon.Kirigami.ScenePosition.y + icon.height/2,
Math.min(icon.width, icon.height))
closeRequested();
plasmoid.nativeInterface.executeCommand(delegateRoot.settingsCommand);
} else if (delegateRoot.toggleFunction) {
delegateRoot.toggleFunction();
}
}
}
}
PlasmaComponents.Label {
id: label
Layout.maximumWidth: delegateRoot.boundingWidth - arrow.width - PlasmaCore.Units.smallSpacing * 2
Layout.alignment: Qt.AlignHCenter
text: delegateRoot.text
bottomPadding: PlasmaCore.Units.smallSpacing * 2
horizontalAlignment: Text.AlignHCenter
font.pixelSize: PlasmaCore.Theme.defaultFont.pixelSize * 0.8
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
PlasmaCore.SvgItem {
id: arrow
anchors {
left: parent.right
verticalCenter: parent.verticalCenter
verticalCenterOffset: -PlasmaCore.Units.smallSpacing
}
visible: delegateRoot.settingsCommand
width: PlasmaCore.Units.iconSizes.small/2
height: width
elementId: "down-arrow"
svg: PlasmaCore.Svg {
imagePath: "widgets/arrows"
}
}
MouseArea {
id: labelMouseArea
anchors.fill: parent
onClicked: {
if (delegateRoot.settingsCommand) {
NanoShell.StartupFeedback.open(
delegateRoot.icon,
delegateRoot.text,
icon.Kirigami.ScenePosition.x + icon.width/2,
icon.Kirigami.ScenePosition.y + icon.height/2,
Math.min(icon.width, icon.height))
plasmoid.nativeInterface.executeCommand(delegateRoot.settingsCommand);
closeRequested();
} else if (delegateRoot.toggleFunction) {
delegateRoot.toggleFunction();
}
}
}
}
}

View file

@ -1,224 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.14
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
import org.kde.bluezqt 1.0 as BluezQt
import org.kde.colorcorrect 0.1 as CC
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.components 3.0 as PC3
import "../"
Item {
id: root
implicitWidth: column.implicitWidth + PlasmaCore.Units.smallSpacing * 6
implicitHeight: expandedHeight
signal expandRequested
signal closeRequested
signal closed
property bool expandedMode: parentSlidingPanel.wideScreen
readonly property real expandedRatio: expandedMode
? 1
: Math.max(0, Math.min(1, (parentSlidingPanel.offset - collapsedHeight) /(expandedHeight-collapsedHeight)))
readonly property real topEmptyAreaHeight: parentSlidingPanel.userInteracting
? (root.height - collapsedHeight) * (1 - expandedRatio)
: (expandedMode ? 0 : root.height - collapsedHeight)
readonly property real collapsedHeight: column.Layout.minimumHeight + background.margins.top + background.fixedMargins.bottom
readonly property real expandedHeight: column.Layout.maximumHeight + background.margins.top + background.fixedMargins.bottom
Connections {
target: root.parentSlidingPanel
function onUserInteractingChanged() {
if (!parentSlidingPanel.userInteracting) {
if (root.expandedRatio > 0.7) {
root.expandedMode = true;
}
}
}
}
property NanoShell.FullScreenOverlay parentSlidingPanel
Connections {
target: root.Window.window
function onVisibilityChanged() {
root.expandedMode = parentSlidingPanel.wideScreen;
}
}
signal plasmoidTriggered(var applet, var id)
Layout.minimumHeight: flow.implicitHeight + PlasmaCore.Units.largeSpacing*2
onClosed: quickSettingsModel.panelClosed()
readonly property SettingsModel quickSettingsModel: SettingsModel {}
PlasmaCore.FrameSvgItem {
id: background
implicitHeight: root.expandedHeight
enabledBorders: parentSlidingPanel.wideScreen ? PlasmaCore.FrameSvg.AllBorders : PlasmaCore.FrameSvg.BottomBorder
anchors.fill: parent
imagePath: "widgets/background"
ColumnLayout {
id: column
anchors {
leftMargin: parent.fixedMargins.left
rightMargin: parent.fixedMargins.right
bottomMargin: parent.fixedMargins.bottom * (parentSlidingPanel.wideScreen ? 1 : 0.5) // HACK: fix the bottom arrow not being centered, bottom margins aren't properly calculated it seems
left: parent.left
right: parent.right
bottom: parent.bottom
}
spacing: 0
height: Layout.minimumHeight * (1 - root.expandedRatio) + (Layout.maximumHeight * root.expandedRatio)
readonly property real cellSizeHint: PlasmaCore.Units.iconSizes.large + PlasmaCore.Units.smallSpacing * 6
readonly property real columnWidth: Math.floor(width / Math.floor(width / cellSizeHint))
// top indicators (clock, widgets, etc.)
IndicatorsRow {
id: indicatorsRow
z: 1
Layout.leftMargin: -Kirigami.Units.largeSpacing
Layout.rightMargin: -Kirigami.Units.largeSpacing
Layout.fillWidth: true
Layout.preferredHeight: parentSlidingPanel.topPanelHeight + PlasmaCore.Units.smallSpacing * 2
showSecondRow: true
colorGroup: PlasmaCore.Theme.NormalColorGroup
backgroundColor: "transparent"
showDropShadow: false
}
// quicksettings list
ColumnLayout {
clip: expandedRatio > 0 && expandedRatio < 1 // only clip when necessary to improve performance
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: flow.Layout.minimumHeight
spacing: 0
Layout.topMargin: PlasmaCore.Units.largeSpacing
Flow {
id: flow
Layout.fillWidth: true
Layout.minimumHeight: cellSizeHint
Layout.preferredHeight: implicitHeight
Layout.maximumHeight: (flow.cellSizeHint * Math.ceil((flow.children.length - 1) / flow.columns))
readonly property real cellSizeHint: PlasmaCore.Units.iconSizes.large + PlasmaCore.Units.smallSpacing * 6
readonly property real columns: Math.floor(width / cellSizeHint)
readonly property real columnWidth: Math.floor(width / columns)
spacing: 0
Repeater {
model: quickSettingsModel
delegate: Delegate {
id: delegateItem
required property var modelData
width: Math.max(implicitWidth + PlasmaCore.Units.smallSpacing * 2, boundingWidth)
boundingWidth: root.expandedRatio < 0.4
? flow.width / (flow.columns + 1)
: (flow.width / (flow.columns + 1)) * (1 - root.expandedRatio) + (flow.width / flow.columns) * root.expandedRatio
labelOpacity: y > 0 ? 1 : root.expandedRatio
opacity: y <= 0 ? 1 : root.expandedRatio
text: modelData.text
icon: modelData.icon
enabled: modelData.enabled
settingsCommand: modelData.settingsCommand
toggleFunction: modelData.toggle
Connections {
target: delegateItem
onCloseRequested: root.closeRequested();
}
Connections {
target: root
onClosed: delegateItem.panelClosed();
}
}
}
move: Transition {
NumberAnimation {
duration: PlasmaCore.Units.shortDuration
easing.type: Easing.Linear
properties: "x,y"
}
}
}
BrightnessItem {
id: brightnessSlider
Layout.topMargin: PlasmaCore.Units.largeSpacing
Layout.bottomMargin: PlasmaCore.Units.smallSpacing
Layout.leftMargin: PlasmaCore.Units.largeSpacing
Layout.rightMargin: PlasmaCore.Units.largeSpacing
Layout.fillWidth: true
opacity: root.expandedRatio
}
}
// bottom "handle bar"
ColumnLayout {
id: bottomBar
spacing: 0
visible: !parentSlidingPanel.wideScreen
Layout.fillWidth: true
implicitHeight: visible ? Math.round(PlasmaCore.Units.gridUnit * 1.3) : 0
Kirigami.Separator {
Layout.fillWidth: true
color: PlasmaCore.Theme.disabledTextColor
opacity: 0.3
}
Kirigami.Icon {
color: PlasmaCore.Theme.disabledTextColor
source: expandedRatio >= 0.5 ? "go-up-symbolic" : "go-down-symbolic"
implicitWidth: PlasmaCore.Units.gridUnit
implicitHeight: width
Layout.alignment: Qt.AlignCenter
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
}
TapHandler {
onTapped: {
if (root.expandedMode) {
root.closeRequested();
} else {
root.expandRequested();
root.expandedMode = true;
}
}
}
}
}
}
}

View file

@ -7,238 +7,9 @@
#include "phonepanel.h"
#include <fcntl.h>
#include <qplatformdefs.h>
#include <unistd.h>
#include <KConfigGroup>
#include <KIO/ApplicationLauncherJob>
#include <KLocalizedString>
#include <KNotification>
#include <QDBusPendingReply>
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QGuiApplication>
#include <QProcess>
#include <QScreen>
#include <QStandardPaths>
#include <QtConcurrent/QtConcurrent>
#define FORMAT24H "HH:mm:ss"
constexpr int SCREENSHOT_DELAY = 200;
/* -- Static Helpers --------------------------------------------------------------------------- */
static QImage allocateImage(const QVariantMap &metadata)
{
bool ok;
const uint width = metadata.value(QStringLiteral("width")).toUInt(&ok);
if (!ok) {
return QImage();
}
const uint height = metadata.value(QStringLiteral("height")).toUInt(&ok);
if (!ok) {
return QImage();
}
const uint format = metadata.value(QStringLiteral("format")).toUInt(&ok);
if (!ok) {
return QImage();
}
return QImage(width, height, QImage::Format(format));
}
static QImage readImage(int fileDescriptor, const QVariantMap &metadata)
{
QFile file;
if (!file.open(fileDescriptor, QFileDevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
close(fileDescriptor);
return QImage();
}
QImage result = allocateImage(metadata);
if (result.isNull()) {
return QImage();
}
QDataStream stream(&file);
stream.readRawData(reinterpret_cast<char *>(result.bits()), result.sizeInBytes());
return result;
}
PhonePanel::PhonePanel(QObject *parent, const QVariantList &args)
: Plasma::Containment(parent, args)
{
// setHasConfigurationInterface(true);
m_kscreenInterface = new org::kde::KScreen(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/kscreen"), QDBusConnection::sessionBus(), this);
m_screenshotInterface = new OrgKdeKWinScreenShot2Interface(QStringLiteral("org.kde.KWin.ScreenShot2"),
QStringLiteral("/org/kde/KWin/ScreenShot2"),
QDBusConnection::sessionBus(),
this);
m_localeConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig);
m_localeConfigWatcher = KConfigWatcher::create(m_localeConfig);
// watch for changes to locale config, to update 12/24 hour time
connect(m_localeConfigWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void {
if (group.name() == "Locale") {
// we have to reparse for new changes (from system settings)
m_localeConfig->reparseConfiguration();
Q_EMIT isSystem24HourFormatChanged();
}
});
}
PhonePanel::~PhonePanel() = default;
void PhonePanel::executeCommand(const QString &command)
{
qWarning() << "Executing" << command;
const QStringList commandAndArguments = QProcess::splitCommand(command);
QProcess::startDetached(commandAndArguments.front(), commandAndArguments.mid(1));
}
void PhonePanel::toggleTorch()
{
// FIXME this is hardcoded to the PinePhone for now
static auto FLASH_SYSFS_PATH = "/sys/devices/platform/led-controller/leds/white:flash/brightness";
int fd = open(FLASH_SYSFS_PATH, O_WRONLY);
if (fd < 0) {
qWarning() << "Unable to open file %s" << FLASH_SYSFS_PATH;
return;
}
write(fd, m_running ? "0" : "1", 1);
close(fd);
m_running = !m_running;
Q_EMIT torchChanged(m_running);
}
bool PhonePanel::torchEnabled() const
{
return m_running;
}
bool PhonePanel::autoRotate()
{
QDBusPendingReply<bool> reply = m_kscreenInterface->getAutoRotate();
reply.waitForFinished();
if (reply.isError()) {
qWarning() << "Getting auto rotate failed:" << reply.error().name() << reply.error().message();
return false;
} else {
return reply.value();
}
}
void PhonePanel::setAutoRotate(bool value)
{
QDBusPendingReply<> reply = m_kscreenInterface->setAutoRotate(value);
reply.waitForFinished();
if (reply.isError()) {
qWarning() << "Setting auto rotate failed:" << reply.error().name() << reply.error().message();
} else {
emit autoRotateChanged(value);
}
}
void PhonePanel::handleMetaDataReceived(const QVariantMap &metadata, int fd)
{
const QString type = metadata.value(QStringLiteral("type")).toString();
if (type != QLatin1String("raw")) {
qWarning() << "Unsupported metadata type:" << type;
return;
}
auto watcher = new QFutureWatcher<QImage>(this);
connect(watcher, &QFutureWatcher<QImage>::finished, this, [watcher]() {
watcher->deleteLater();
QString filePath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
if (filePath.isEmpty()) {
qWarning() << "Couldn't find a writable location for the screenshot!";
return;
}
QDir picturesDir(filePath);
if (!picturesDir.mkpath(QStringLiteral("Screenshots"))) {
qWarning() << "Couldn't create folder at" << picturesDir.path() + QStringLiteral("/Screenshots") << "to take screenshot.";
return;
}
filePath += QStringLiteral("/Screenshots/Screenshot_%1.png").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_hhmmss")));
const auto m_result = watcher->result();
if (m_result.isNull() || !m_result.save(filePath)) {
qWarning() << "Screenshot failed";
} else {
KNotification *notif = new KNotification("captured");
notif->setComponentName(QStringLiteral("plasma_phone_components"));
notif->setTitle(i18n("New Screenshot"));
notif->setUrls({QUrl::fromLocalFile(filePath)});
notif->setText(i18n("New screenshot saved to %1", filePath));
notif->sendEvent();
}
});
watcher->setFuture(QtConcurrent::run(readImage, fd, metadata));
}
void PhonePanel::takeScreenshot()
{
// wait ~200 ms to wait for rest of animations
QTimer::singleShot(SCREENSHOT_DELAY, [=]() {
int lPipeFds[2];
if (pipe2(lPipeFds, O_CLOEXEC) != 0) {
qWarning() << "Could not take screenshot";
return;
}
// We don't have access to the ScreenPool so we'll just take the first screen
QVariantMap options;
options.insert(QStringLiteral("native-resolution"), true);
auto pendingCall = m_screenshotInterface->CaptureScreen(
qGuiApp->screens().constFirst()->name(), options,
QDBusUnixFileDescriptor(lPipeFds[1]));
close(lPipeFds[1]);
auto pipeFileDescriptor = lPipeFds[0];
auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, pipeFileDescriptor]() {
watcher->deleteLater();
const QDBusPendingReply<QVariantMap> reply = *watcher;
if (reply.isError()) {
qWarning() << "Screenshot request failed:" << reply.error().message();
} else {
handleMetaDataReceived(reply, pipeFileDescriptor);
}
});
});
}
bool PhonePanel::isSystem24HourFormat()
{
KConfigGroup localeSettings = KConfigGroup(m_localeConfig, "Locale");
QString timeFormat = localeSettings.readEntry("TimeFormat", QStringLiteral(FORMAT24H));
return timeFormat == QStringLiteral(FORMAT24H);
}
void PhonePanel::launchApp(const QString &app)
{
const KService::Ptr appService = KService::serviceByDesktopName(app);
if (!appService) {
qWarning() << "Could not find" << app;
return;
}
auto job = new KIO::ApplicationLauncherJob(appService, this);
job->start();
}
K_PLUGIN_CLASS_WITH_JSON(PhonePanel, "metadata.json")
#include "phonepanel.moc"

View file

@ -9,50 +9,13 @@
#include <Plasma/Containment>
#include <KConfigWatcher>
#include <KSharedConfig>
#include "kscreeninterface.h"
#include "screenshot2interface.h"
class PhonePanel : public Plasma::Containment
{
Q_OBJECT
Q_PROPERTY(bool autoRotateEnabled READ autoRotate WRITE setAutoRotate NOTIFY autoRotateChanged);
Q_PROPERTY(bool torchEnabled READ torchEnabled NOTIFY torchChanged);
Q_PROPERTY(bool isSystem24HourFormat READ isSystem24HourFormat NOTIFY isSystem24HourFormatChanged);
public:
PhonePanel(QObject *parent, const QVariantList &args);
~PhonePanel() override;
public Q_SLOTS:
void executeCommand(const QString &command);
void launchApp(const QString &app);
void toggleTorch();
void takeScreenshot();
bool autoRotate();
void setAutoRotate(bool value);
bool torchEnabled() const;
bool isSystem24HourFormat();
signals:
void autoRotateChanged(bool value);
void torchChanged(bool value);
void isSystem24HourFormatChanged();
private:
void handleMetaDataReceived(const QVariantMap &metadata, int fd);
bool m_running = false;
KConfigWatcher::Ptr m_localeConfigWatcher;
KSharedConfig::Ptr m_localeConfig;
org::kde::KScreen *m_kscreenInterface;
OrgKdeKWinScreenShot2Interface *m_screenshotInterface;
};
#endif

View file

@ -54,6 +54,7 @@ ColumnLayout {
id: timeSource
engine: "time"
connectedSources: ["Local"]
interval: 1000
interval: 60000
intervalAlignment: PlasmaCore.Types.AlignToMinute
}
}

View file

@ -9,9 +9,12 @@ import QtQuick 2.12
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.12
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.workspace.keyboardlayout 1.0
import org.kde.notificationmanager 1.1 as Notifications
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import "../components"
PlasmaCore.ColorScope {
@ -78,9 +81,18 @@ PlasmaCore.ColorScope {
left: parent.left
right: parent.right
}
height: PlasmaCore.Units.gridUnit
height: PlasmaCore.Units.gridUnit * 1.25
opacity: 1 - (passwordFlickable.contentY / passwordFlickable.columnHeight)
sourceComponent: SimpleHeaderBar {}
sourceComponent: MobileShell.StatusBar {
id: statusBar
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
backgroundColor: "transparent"
showSecondRow: false
showDropShadow: true
showTime: false
disableSystemTray: true // HACK: prevent SIGABRT
}
}
// phone lockscreen component
@ -125,11 +137,10 @@ PlasmaCore.ColorScope {
alignment: Qt.AlignHCenter
Layout.bottomMargin: PlasmaCore.Units.gridUnit * 2 // keep spacing even if media controls are gone
}
MediaControls {
MobileShell.MediaControlsWidget {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
Layout.minimumWidth: PlasmaCore.Units.gridUnit * 15
Layout.leftMargin: PlasmaCore.Units.gridUnit
Layout.rightMargin: PlasmaCore.Units.gridUnit
}
@ -184,11 +195,10 @@ PlasmaCore.ColorScope {
Layout.fillWidth: true
Layout.minimumWidth: PlasmaCore.Units.gridUnit * 20
}
MediaControls {
MobileShell.MediaControlsWidget {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
Layout.minimumWidth: PlasmaCore.Units.gridUnit * 20
}
}
}

View file

@ -1,160 +0,0 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.5
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.plasma.extras 2.0 as PlasmaExtras
Item {
visible: mpris2Source.hasPlayer
implicitHeight: controlsRow.height + controlsRow.y
RowLayout {
id: controlsRow
anchors.bottom: parent.bottom
y: PlasmaCore.Units.smallSpacing // some distance to the password field
width: parent.width
height: PlasmaCore.Units.gridUnit * 3
spacing: 0
enabled: mpris2Source.canControl
PlasmaCore.DataSource {
id: mpris2Source
readonly property string source: "@multiplex"
readonly property var playerData: data[source]
readonly property bool hasPlayer: sources.length > 1 && !!playerData
readonly property string identity: hasPlayer ? playerData.Identity : ""
readonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing"
readonly property bool canControl: hasPlayer && playerData.CanControl
readonly property bool canGoBack: hasPlayer && playerData.CanGoPrevious
readonly property bool canGoNext: hasPlayer && playerData.CanGoNext
readonly property var currentMetadata: hasPlayer ? playerData.Metadata : ({})
readonly property string track: {
var xesamTitle = currentMetadata["xesam:title"]
if (xesamTitle) {
return xesamTitle
}
// if no track title is given, print out the file name
var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : ""
if (!xesamUrl) {
return ""
}
var lastSlashPos = xesamUrl.lastIndexOf('/')
if (lastSlashPos < 0) {
return ""
}
var lastUrlPart = xesamUrl.substring(lastSlashPos + 1)
return decodeURIComponent(lastUrlPart)
}
readonly property string artist: currentMetadata["xesam:artist"] || ""
readonly property string albumArt: currentMetadata["mpris:artUrl"] || ""
engine: "mpris2"
connectedSources: [source]
function startOperation(op) {
var service = serviceForSource(source)
var operation = service.operationDescription(op)
return service.startOperationCall(operation)
}
function goPrevious() {
startOperation("Previous");
}
function goNext() {
startOperation("Next");
}
function playPause(source) {
startOperation("PlayPause");
}
}
Image {
id: albumArt
Layout.preferredWidth: height
Layout.fillHeight: true
asynchronous: true
fillMode: Image.PreserveAspectFit
source: mpris2Source.albumArt
sourceSize.height: height
visible: status === Image.Loading || status === Image.Ready
}
Item { // spacer
width: PlasmaCore.Units.smallSpacing
height: 1
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
PlasmaComponents3.Label {
Layout.fillWidth: true
wrapMode: Text.NoWrap
elide: Text.ElideRight
text: mpris2Source.track || i18nd("plasma_lookandfeel_org.kde.lookandfeel", "No media playing")
textFormat: Text.PlainText
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize + 1
maximumLineCount: 1
}
PlasmaExtras.DescriptiveLabel {
Layout.fillWidth: true
wrapMode: Text.NoWrap
elide: Text.ElideRight
// if no artist is given, show player name instead
text: mpris2Source.artist || mpris2Source.identity || ""
textFormat: Text.PlainText
font.pointSize: PlasmaCore.Theme.smallestFont.pointSize + 1
maximumLineCount: 1
}
}
PlasmaComponents3.ToolButton {
enabled: mpris2Source.canGoBack
icon.name: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward"
onClicked: {
// fadeoutTimer.running = false
mpris2Source.goPrevious()
}
visible: mpris2Source.canGoBack || mpris2Source.canGoNext
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Previous track")
}
PlasmaComponents3.ToolButton {
Layout.fillHeight: true
Layout.preferredWidth: height // make this button bigger
icon.name: mpris2Source.playing ? "media-playback-pause" : "media-playback-start"
onClicked: {
// fadeoutTimer.running = false
mpris2Source.playPause()
}
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Play or Pause media")
}
PlasmaComponents3.ToolButton {
enabled: mpris2Source.canGoNext
icon.name: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward"
onClicked: {
// fadeoutTimer.running = false
mpris2Source.goNext()
}
visible: mpris2Source.canGoBack || mpris2Source.canGoNext
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Next track")
}
}
}

View file

@ -1,87 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.12
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.workspace.components 2.0 as PW
import "indicators" as Indicators
import "indicators/providers" as Providers
// a simple version of the task panel
// in the future, it should share components with the existing task panel
PlasmaCore.ColorScope {
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
property real textPixelSize: PlasmaCore.Units.gridUnit * 0.6
layer.enabled: true
layer.effect: DropShadow {
visible: true
cached: true
horizontalOffset: 0
verticalOffset: 1
radius: 6.0
samples: 17
color: Qt.rgba(0,0,0,0.6)
}
Providers.SignalStrengthProvider {
id: signalStrengthProviderLoader
}
Controls.Control {
topPadding: PlasmaCore.Units.smallSpacing
bottomPadding: PlasmaCore.Units.smallSpacing
rightPadding: PlasmaCore.Units.smallSpacing * 3
leftPadding: PlasmaCore.Units.smallSpacing * 3
anchors.fill: parent
contentItem: RowLayout {
id: row
spacing: 0
Indicators.SignalStrength {
provider: signalStrengthProviderLoader
labelPixelSize: textPixelSize
Layout.fillHeight: true
}
// spacing in the middle
Item {
Layout.fillWidth: true
}
RowLayout {
id: indicators
spacing: PlasmaCore.Units.smallSpacing * 1.5
Layout.fillHeight: true
Indicators.Bluetooth {
Layout.fillHeight: true
}
Indicators.Wifi {
Layout.fillHeight: true
}
Indicators.Volume {
Layout.fillHeight: true
}
Indicators.Battery {
spacing: PlasmaCore.Units.smallSpacing * 1.5
labelPixelSize: textPixelSize
Layout.fillHeight: true
}
}
}
}
}

View file

@ -1,46 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.6
import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.workspace.components 2.0 as PW
RowLayout {
visible: pmSource.data["Battery"]["Has Cumulative"]
property real labelPixelSize
PW.BatteryIcon {
id: battery
Layout.preferredWidth: height
Layout.fillHeight: true
hasBattery: true
percent: pmSource.data["Battery"]["Percent"]
pluggedIn: pmSource.data["AC Adapter"] ? pmSource.data["AC Adapter"]["Plugged in"] : false
height: batteryLabel.height
width: batteryLabel.height
PlasmaCore.DataSource {
id: pmSource
engine: "powermanagement"
connectedSources: ["Battery", "AC Adapter"]
}
}
PlasmaComponents.Label {
id: batteryLabel
text: i18n("%1%", battery.percent)
Layout.alignment: Qt.AlignVCenter
color: PlasmaCore.ColorScope.textColor
font.pixelSize: labelPixelSize
}
}

View file

@ -1,47 +0,0 @@
/*
SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2013-2017 Jan Grulich <jgrulich@redhat.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
import QtQuick 2.2
import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.bluezqt 1.0 as BluezQt
PlasmaCore.IconItem {
id: connectionIcon
property bool deviceConnected : false
source: deviceConnected ? "preferences-system-bluetooth-activated" : "preferences-system-bluetooth";
colorGroup: PlasmaCore.ColorScope.colorGroup
visible: BluezQt.Manager.bluetoothOperational
Layout.fillHeight: true
Layout.preferredWidth: height
function updateStatus()
{
var connectedDevices = [];
for (var i = 0; i < BluezQt.Manager.devices.length; ++i) {
var device = BluezQt.Manager.devices[i];
if (device.connected) {
connectedDevices.push(device);
}
}
deviceConnected = connectedDevices.length > 0;
}
Component.onCompleted: {
BluezQt.Manager.deviceAdded.connect(updateStatus);
BluezQt.Manager.deviceRemoved.connect(updateStatus);
BluezQt.Manager.deviceChanged.connect(updateStatus);
BluezQt.Manager.bluetoothBlockedChanged.connect(updateStatus);
BluezQt.Manager.bluetoothOperationalChanged.connect(updateStatus);
updateStatus();
}
}

View file

@ -1,32 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.6
import QtQuick.Layouts 1.4
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
RowLayout {
property alias icon: icon.source
property alias text: label.text
PlasmaCore.IconItem {
id: icon
colorGroup: PlasmaCore.ColorScope.colorGroup
Layout.fillHeight: true
Layout.preferredWidth: height
}
PlasmaComponents.Label {
id: label
visible: text.length > 0
color: PlasmaCore.ColorScope.textColor
font.pixelSize: parent.height / 2
}
}

View file

@ -1,63 +0,0 @@
/*
SPDX-FileCopyrightText: 2019 Aditya Mehra <Aix.m@outlook.com>
SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
import QtQuick 2.2
import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.private.volume 0.1
PlasmaCore.IconItem {
id: paIcon
Layout.fillHeight: true
Layout.preferredWidth: height
property bool volumeFeedback: true
property int maxVolumeValue: Math.round(100 * PulseAudio.NormalVolume / 100.0)
property int volumeStep: Math.round(5 * PulseAudio.NormalVolume / 100.0)
readonly property string dummyOutputName: "auto_null"
source: paSinkModel.preferredSink && !isDummyOutput(paSinkModel.preferredSink)
? iconName(paSinkModel.preferredSink.volume, paSinkModel.preferredSink.muted)
: iconName(0, true)
colorGroup: PlasmaCore.ColorScope.colorGroup
visible: paSinkModel.preferredSink && paSinkModel.preferredSink.muted
function iconName(volume, muted, prefix) {
if (!prefix) {
prefix = "audio-volume";
}
var icon = null;
var percent = volume / maxVolumeValue;
if (percent <= 0.0 || muted) {
icon = prefix + "-muted";
} else if (percent <= 0.25) {
icon = prefix + "-low";
} else if (percent <= 0.75) {
icon = prefix + "-medium";
} else {
icon = prefix + "-high";
}
return icon;
}
function isDummyOutput(output) {
return output && output.name === dummyOutputName;
}
SinkModel {
id: paSinkModel
}
VolumeOSD {
id: osd
}
VolumeFeedback {
id: feedback
}
}

View file

@ -1,46 +0,0 @@
/*
SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2013-2017 Jan Grulich <jgrulich@redhat.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
import QtQuick 2.2
import QtQuick.Layouts 1.4
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
PlasmaCore.IconItem {
id: connectionIcon
source: connectionIconProvider.connectionIcon
colorGroup: PlasmaCore.ColorScope.colorGroup
Layout.fillHeight: true
Layout.preferredWidth: height
PlasmaComponents.BusyIndicator {
id: connectingIndicator
anchors.fill: parent
running: connectionIconProvider.connecting
visible: running
}
PlasmaNM.NetworkStatus {
id: networkStatus
}
PlasmaNM.NetworkModel {
id: connectionModel
}
PlasmaNM.Handler {
id: handler
}
PlasmaNM.ConnectionIcon {
id: connectionIconProvider
}
}

View file

@ -1,20 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.1
import org.kde.plasma.mm 1.0
QtObject {
property string icon: "network-mobile-" + Math.floor(SignalIndicator.strength / 20) * 20
property string label: !SignalIndicator.available ? ""
: SignalIndicator.simLocked ? i18n("SIM Locked") : SignalIndicator.name
}

View file

@ -5,12 +5,13 @@
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
text: i18n("Airplane Mode")
icon: "network-flightmode-on"
status: ""
enabled: PlasmaNM.Configuration.airplaneModeEnabled
PlasmaNM.Handler {

View file

@ -7,12 +7,13 @@
*/
import org.kde.colorcorrect 0.1 as CC
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
HomeScreenComponents.QuickSetting {
MobileShell.QuickSetting {
text: i18n("Night Color")
icon: "redshift-status-on"
enabled: compositorAdaptor.active
status: ""
settingsCommand: "plasma-open-settings kcm_nightcolor"
CC.CompositorAdaptor {

View file

@ -7,7 +7,6 @@ for (var j = 0; j < desktopsArray.length; j++) {
var panel = new Panel("org.kde.phone.panel");
panel.location = "top";
panel.addWidget("org.kde.plasma.notifications");
panel.addWidget("org.kde.plasma.mediacontroller");
panel.height = 1.25 * gridUnit; // HACK: supposed to be gridUnit + smallSpacing, but it doesn't seem to give the correct number
var bottomPanel = new Panel("org.kde.phone.taskpanel")

Some files were not shown because too many files have changed in this diff Show more