mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
panel: Refactor panel and implement blue ocean mockups
This commit is contained in:
parent
7f9a9ca60c
commit
6653ad6e54
101 changed files with 4004 additions and 2339 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ set(mobilehomescreencomponentsplugin_SRCS
|
|||
applicationlistmodel.cpp
|
||||
favoritesmodel.cpp
|
||||
homescreenutils.cpp
|
||||
quicksettingsmodel.cpp
|
||||
)
|
||||
|
||||
add_library(mobilehomescreencomponentsplugin ${mobilehomescreencomponentsplugin_SRCS})
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
|
|
|||
40
components/mobileshell/CMakeLists.txt
Normal file
40
components/mobileshell/CMakeLists.txt
Normal 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)
|
||||
30
components/mobileshell/mobileshellplugin.cpp
Normal file
30
components/mobileshell/mobileshellplugin.cpp
Normal 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");
|
||||
}
|
||||
21
components/mobileshell/mobileshellplugin.h
Normal file
21
components/mobileshell/mobileshellplugin.h
Normal 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;
|
||||
};
|
||||
231
components/mobileshell/notifications/notificationfilemenu.cpp
Normal file
231
components/mobileshell/notifications/notificationfilemenu.cpp
Normal 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();
|
||||
}
|
||||
49
components/mobileshell/notifications/notificationfilemenu.h
Normal file
49
components/mobileshell/notifications/notificationfilemenu.h
Normal 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;
|
||||
};
|
||||
157
components/mobileshell/notifications/notificationthumbnailer.cpp
Normal file
157
components/mobileshell/notifications/notificationthumbnailer.cpp
Normal 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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
256
components/mobileshell/qml/actiondrawer/ActionDrawer.qml
Normal file
256
components/mobileshell/qml/actiondrawer/ActionDrawer.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
66
components/mobileshell/qml/components/BaseItem.qml
Normal file
66
components/mobileshell/qml/components/BaseItem.qml
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
18
components/mobileshell/qml/components/Direction.qml
Normal file
18
components/mobileshell/qml/components/Direction.qml
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
9
components/mobileshell/qml/components/util.js
Normal file
9
components/mobileshell/qml/components/util.js
Normal 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));
|
||||
}
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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"
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
34
components/mobileshell/qml/qmldir
Normal file
34
components/mobileshell/qml/qmldir
Normal 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
|
||||
33
components/mobileshell/qml/statusbar/ClockText.qml
Normal file
33
components/mobileshell/qml/statusbar/ClockText.qml
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
127
components/mobileshell/qml/widgets/MediaControlsWidget.qml
Normal file
127
components/mobileshell/qml/widgets/MediaControlsWidget.qml
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
178
components/mobileshell/qml/widgets/NotificationsWidget.qml
Normal file
178
components/mobileshell/qml/widgets/NotificationsWidget.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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…")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
components/mobileshell/qml/widgets/notifications/util.js
Normal file
85
components/mobileshell/qml/widgets/notifications/util.js
Normal 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 "";
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
module org.kde.plasma.private.mobileshell
|
||||
|
||||
singleton HomeScreenControls 1.0 HomeScreenControls.qml
|
||||
singleton TopPanelControls 1.0 TopPanelControls.qml
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
245
components/mobileshell/shellutil.cpp
Normal file
245
components/mobileshell/shellutil.cpp
Normal 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();
|
||||
}
|
||||
57
components/mobileshell/shellutil.h
Normal file
57
components/mobileshell/shellutil.h
Normal 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;
|
||||
};
|
||||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ ColumnLayout {
|
|||
id: timeSource
|
||||
engine: "time"
|
||||
connectedSources: ["Local"]
|
||||
interval: 1000
|
||||
interval: 60000
|
||||
intervalAlignment: PlasmaCore.Types.AlignToMinute
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue