mirror of https://github.com/cutefishos/settings
feat(settings): add input method selection page
parent
88abc2fec4
commit
30738dd13a
@ -0,0 +1,12 @@
|
||||
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#fff">
|
||||
<path d="m2.5 3.5c-1.1046 0-2 0.89543-2 2v5c0 1.1046 0.89543 2 2 2h11c1.1046 0 2-0.89543 2-2v-5c0-1.1046-0.89543-2-2-2zm0 1.25h11c0.41421 0 0.75 0.33579 0.75 0.75v5c0 0.41421-0.33579 0.75-0.75 0.75h-11c-0.41421 0-0.75-0.33579-0.75-0.75v-5c0-0.41421 0.33579-0.75 0.75-0.75z"/>
|
||||
<rect x="3" y="6" width="1.5" height="1.25" rx=".3"/>
|
||||
<rect x="5.5" y="6" width="1.5" height="1.25" rx=".3"/>
|
||||
<rect x="8" y="6" width="1.5" height="1.25" rx=".3"/>
|
||||
<rect x="10.5" y="6" width="1.5" height="1.25" rx=".3"/>
|
||||
<rect x="12.25" y="8.25" width="1" height="1.25" rx=".3"/>
|
||||
<rect x="3" y="8.25" width="1.5" height="1.25" rx=".3"/>
|
||||
<rect x="5.5" y="8.5" width="5" height="1" rx=".3"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 810 B |
@ -0,0 +1,101 @@
|
||||
#include "inputmethod.h"
|
||||
|
||||
#include "desktopentry.h"
|
||||
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusPendingReply>
|
||||
#include <QDBusReply>
|
||||
#include <QDBusServiceWatcher>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace {
|
||||
constexpr auto service = "com.cutefish.InputMethod1";
|
||||
constexpr auto path = "/com/cutefish/InputMethod1";
|
||||
|
||||
QVariantMap describe(const QString &id)
|
||||
{
|
||||
QVariantMap method {{QStringLiteral("id"), id}, {QStringLiteral("name"), id}};
|
||||
const QString file = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, id);
|
||||
QFile desktop(file);
|
||||
DesktopEntryData data;
|
||||
if (!file.isEmpty() && desktop.open(QIODevice::ReadOnly)
|
||||
&& DesktopEntry::parse(id, file, desktop.readAll(), &data)) {
|
||||
if (!data.name.isEmpty())
|
||||
method[QStringLiteral("name")] = data.name;
|
||||
method[QStringLiteral("icon")] = data.icon;
|
||||
}
|
||||
return method;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
InputMethod::InputMethod(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
auto *watcher = new QDBusServiceWatcher(QLatin1String(service), QDBusConnection::sessionBus(),
|
||||
QDBusServiceWatcher::WatchForOwnerChange, this);
|
||||
connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this,
|
||||
&InputMethod::refreshInterface);
|
||||
refreshInterface();
|
||||
}
|
||||
|
||||
void InputMethod::refreshInterface()
|
||||
{
|
||||
delete m_interface;
|
||||
m_interface = new QDBusInterface(QLatin1String(service), QLatin1String(path),
|
||||
QLatin1String(service), QDBusConnection::sessionBus(), this);
|
||||
if (m_interface->isValid()) {
|
||||
connect(m_interface, SIGNAL(inputMethodChanged()), this, SIGNAL(currentChanged()));
|
||||
connect(m_interface, SIGNAL(runningChanged()), this, SIGNAL(runningChanged()));
|
||||
}
|
||||
emit availableChanged();
|
||||
emit currentChanged();
|
||||
emit runningChanged();
|
||||
refreshMethods();
|
||||
}
|
||||
|
||||
void InputMethod::refreshMethods()
|
||||
{
|
||||
m_methods.clear();
|
||||
if (m_interface->isValid()) {
|
||||
const QDBusReply<QStringList> reply =
|
||||
m_interface->call(QStringLiteral("availableInputMethods"));
|
||||
for (const auto &id : reply.value())
|
||||
m_methods << describe(id);
|
||||
}
|
||||
emit methodsChanged();
|
||||
}
|
||||
|
||||
bool InputMethod::available() const
|
||||
{
|
||||
return m_interface->isValid();
|
||||
}
|
||||
|
||||
QString InputMethod::current() const
|
||||
{
|
||||
return m_interface->isValid() ? m_interface->property("inputMethod").toString() : QString();
|
||||
}
|
||||
|
||||
bool InputMethod::running() const
|
||||
{
|
||||
return m_interface->isValid() && m_interface->property("running").toBool();
|
||||
}
|
||||
|
||||
void InputMethod::select(const QString &id)
|
||||
{
|
||||
if (!m_interface->isValid())
|
||||
return;
|
||||
auto *watcher = new QDBusPendingCallWatcher(
|
||||
m_interface->asyncCall(QStringLiteral("setInputMethod"), id), this);
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
||||
[this, id](QDBusPendingCallWatcher *call) {
|
||||
const QDBusPendingReply<bool> reply = *call;
|
||||
if (reply.isError() || !reply.value()) {
|
||||
qWarning() << "Cannot select input method" << id << reply.error().message();
|
||||
refreshMethods();
|
||||
}
|
||||
emit currentChanged();
|
||||
call->deleteLater();
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDBusInterface>
|
||||
#include <QObject>
|
||||
#include <QVariantList>
|
||||
|
||||
// Client of the compositor's com.cutefish.InputMethod1 service.
|
||||
class InputMethod : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool available READ available NOTIFY availableChanged)
|
||||
Q_PROPERTY(QVariantList methods READ methods NOTIFY methodsChanged)
|
||||
Q_PROPERTY(QString current READ current NOTIFY currentChanged)
|
||||
Q_PROPERTY(bool running READ running NOTIFY runningChanged)
|
||||
|
||||
public:
|
||||
explicit InputMethod(QObject *parent = nullptr);
|
||||
|
||||
bool available() const;
|
||||
|
||||
QVariantList methods() const { return m_methods; }
|
||||
|
||||
QString current() const;
|
||||
bool running() const;
|
||||
|
||||
// An empty id turns the input method off.
|
||||
Q_INVOKABLE void select(const QString &id);
|
||||
|
||||
signals:
|
||||
void availableChanged();
|
||||
void methodsChanged();
|
||||
void currentChanged();
|
||||
void runningChanged();
|
||||
|
||||
private:
|
||||
void refreshInterface();
|
||||
void refreshMethods();
|
||||
QDBusInterface *m_interface = nullptr;
|
||||
QVariantList m_methods;
|
||||
};
|
||||
@ -0,0 +1,120 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import FishUI 1.0 as FishUI
|
||||
import Cutefish.Settings 1.0
|
||||
import "../"
|
||||
|
||||
ItemPage {
|
||||
headerTitle: qsTr("Input Method")
|
||||
|
||||
InputMethod {
|
||||
id: inputMethod
|
||||
}
|
||||
|
||||
Scrollable {
|
||||
anchors.fill: parent
|
||||
contentHeight: layout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
anchors.fill: parent
|
||||
spacing: FishUI.Units.largeSpacing * 2
|
||||
|
||||
RoundedItem {
|
||||
Label {
|
||||
text: qsTr("Input method")
|
||||
color: FishUI.Theme.disabledTextColor
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
visible: !inputMethod.available
|
||||
text: qsTr("Input methods can only be selected in a Cutefish Wayland session.")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: inputMethod.available
|
||||
? [{ "id": "", "name": qsTr("None"), "icon": "" }]
|
||||
.concat(inputMethod.methods)
|
||||
: []
|
||||
|
||||
delegate: MouseArea {
|
||||
id: item
|
||||
|
||||
property bool isSelected: modelData.id === inputMethod.current
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.max(36, row.implicitHeight + FishUI.Units.largeSpacing)
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: inputMethod.select(modelData.id)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: FishUI.Theme.smallRadius
|
||||
color: item.isSelected ? FishUI.Theme.highlightColor
|
||||
: item.containsMouse ? FishUI.Theme.disabledTextColor
|
||||
: "transparent"
|
||||
opacity: item.isSelected ? 1 : 0.1
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: FishUI.Units.smallSpacing
|
||||
anchors.rightMargin: FishUI.Units.largeSpacing
|
||||
spacing: FishUI.Units.largeSpacing
|
||||
|
||||
FishUI.IconItem {
|
||||
width: 24
|
||||
height: 24
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
source: modelData.icon ? modelData.icon : "input-keyboard"
|
||||
smooth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.name
|
||||
elide: Text.ElideRight
|
||||
color: item.isSelected ? FishUI.Theme.highlightedTextColor
|
||||
: FishUI.Theme.textColor
|
||||
}
|
||||
|
||||
Image {
|
||||
width: 16
|
||||
height: 16
|
||||
sourceSize: Qt.size(16, 16)
|
||||
source: "qrc:/images/dark/checked.svg"
|
||||
visible: item.isSelected
|
||||
smooth: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
visible: inputMethod.available && inputMethod.current !== "" && !inputMethod.running
|
||||
text: qsTr("The selected input method is not running.")
|
||||
color: FishUI.Theme.disabledTextColor
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
visible: inputMethod.available
|
||||
text: qsTr("Applications started before the input method may need to be restarted.")
|
||||
color: FishUI.Theme.disabledTextColor
|
||||
wrapMode: Text.WordWrap
|
||||
leftPadding: FishUI.Units.largeSpacing * 1.5
|
||||
rightPadding: FishUI.Units.largeSpacing * 1.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue