Commit 5312497e authored by Aleix Pol Gonzalez's avatar Aleix Pol Gonzalez 🐧 Committed by Aleix Pol Gonzalez
Browse files

tablet: Make it possible to specify how a tablet maps to an output

CCBUG: 433045
parent 83f468bc
Pipeline #209075 passed with stage
in 5 minutes and 8 seconds
......@@ -59,6 +59,7 @@ InputDevice::InputDevice(QString dbusName)
connect(this, &InputDevice::leftHandedChanged, this, &InputDevice::needsSaveChanged);
connect(this, &InputDevice::orientationChanged, this, &InputDevice::needsSaveChanged);
connect(this, &InputDevice::outputNameChanged, this, &InputDevice::needsSaveChanged);
connect(this, &InputDevice::outputAreaChanged, this, &InputDevice::needsSaveChanged);
}
InputDevice::~InputDevice() = default;
......@@ -68,11 +69,12 @@ void InputDevice::save()
m_orientation.save();
m_outputName.save();
m_leftHanded.save();
m_outputArea.save();
}
bool InputDevice::isSaveNeeded() const
{
return m_leftHanded.changed() || m_orientation.changed() || m_outputName.changed();
return m_leftHanded.changed() || m_orientation.changed() || m_outputName.changed() || m_outputArea.changed();
}
void InputDevice::defaults()
......@@ -80,11 +82,12 @@ void InputDevice::defaults()
m_leftHanded.resetFromDefaults();
m_orientation.resetFromDefaults();
m_outputName.resetFromDefaults();
m_outputArea.resetFromDefaults();
}
bool InputDevice::isDefaults() const
{
return m_leftHanded.isDefaults() && m_orientation.isDefaults() && m_outputName.isDefaults();
return m_leftHanded.isDefaults() && m_orientation.isDefaults() && m_outputName.isDefaults() && m_outputArea.isDefaults();
}
void InputDevice::load()
......@@ -92,6 +95,7 @@ void InputDevice::load()
m_orientation.resetFromSaved();
m_leftHanded.resetFromSaved();
m_outputName.resetFromSaved();
m_outputArea.resetFromSaved();
}
void InputDevice::setOrientation(int ori)
......@@ -108,3 +112,8 @@ void InputDevice::setLeftHanded(bool set)
{
m_leftHanded.set(set);
}
void InputDevice::setOutputArea(const QRectF &outputArea)
{
m_outputArea.set(outputArea);
}
......@@ -17,12 +17,14 @@ class InputDevice : public QObject
{
Q_OBJECT
Q_PROPERTY(QSizeF size READ size CONSTANT)
Q_PROPERTY(bool supportsLeftHanded READ supportsLeftHanded CONSTANT)
Q_PROPERTY(bool leftHanded READ isLeftHanded WRITE setLeftHanded NOTIFY leftHandedChanged)
Q_PROPERTY(bool supportsOrientation READ supportsOrientation CONSTANT)
Q_PROPERTY(int orientation READ orientation WRITE setOrientation NOTIFY orientationChanged)
Q_PROPERTY(QString outputName READ outputName WRITE setOutputName NOTIFY outputNameChanged)
Q_PROPERTY(QRectF outputArea READ outputArea WRITE setOutputArea NOTIFY outputAreaChanged)
public:
InputDevice(QString dbusName);
......@@ -67,6 +69,16 @@ public:
return m_outputName.value();
}
void setOutputName(const QString &outputName);
QSizeF size() const
{
return m_size.value();
}
QRectF outputArea() const
{
return m_outputArea.value();
}
void setOutputArea(const QRectF &outputArea);
Q_SIGNALS:
void needsSaveChanged();
......@@ -74,6 +86,7 @@ Q_SIGNALS:
void leftHandedChanged();
void orientationChanged();
void outputNameChanged();
void outputAreaChanged();
private:
template<typename T>
......@@ -166,6 +179,7 @@ private:
//
// general
Prop<QString> m_name = Prop<QString>(this, "name");
Prop<QSizeF> m_size = Prop<QSizeF>(this, "size");
Prop<QString> m_sysName = Prop<QString>(this, "sysName");
Prop<bool> m_leftHanded = Prop<bool>(this,
......@@ -176,6 +190,11 @@ private:
Prop<int> m_orientation =
Prop<int>(this, "orientationDBus", nullptr, &OrgKdeKWinInputDeviceInterface::supportsCalibrationMatrix, &InputDevice::orientationChanged);
Prop<QString> m_outputName = Prop<QString>(this, "outputName", nullptr, nullptr, &InputDevice::outputNameChanged);
Prop<QRectF> m_outputArea = Prop<QRectF>(this,
"outputArea",
&OrgKdeKWinInputDeviceInterface::defaultOutputArea,
&OrgKdeKWinInputDeviceInterface::supportsOutputArea,
&InputDevice::outputAreaChanged);
std::unique_ptr<OrgKdeKWinInputDeviceInterface> m_iface;
};
......
......@@ -64,20 +64,27 @@ class OutputsModel : public QStandardItemModel
public:
OutputsModel()
{
appendRow(new QStandardItem(i18n("Follow the active screen")));
auto screens = qGuiApp->screens();
auto it = new QStandardItem(i18n("Follow the active screen"));
it->setData(screens[0]->physicalSize(), Qt::UserRole + 1); // we use the first display to give an idea
it->setData(screens[0]->size(), Qt::UserRole + 2);
appendRow(it);
for (auto screen : screens) {
auto geo = screen->geometry();
auto it =
new QStandardItem(i18nc("model - (x,y widthxheight)", "%1 - (%2,%3 %4×%5)", screen->model(), geo.x(), geo.y(), geo.width(), geo.height()));
it->setData(screen->name(), Qt::UserRole);
it->setData(screen->physicalSize(), Qt::UserRole + 1);
it->setData(screen->size(), Qt::UserRole + 2);
appendRow(it);
}
setItemRoleNames({
{Qt::DisplayRole, "display"},
{Qt::UserRole, "name"},
{Qt::UserRole + 1, "physicalSize"},
{Qt::UserRole + 2, "size"},
});
}
......@@ -98,12 +105,27 @@ public:
}
};
/// This model lists the different ways the tablet will fit onto its output
class OutputsFittingModel : public QStandardItemModel
{
public:
OutputsFittingModel()
{
appendRow(new QStandardItem(i18n("Fit to Output")));
appendRow(new QStandardItem(i18n("Fit Output in tablet")));
appendRow(new QStandardItem(i18n("Custom size")));
setItemRoleNames({{Qt::DisplayRole, "display"}});
}
};
Tablet::Tablet(QObject *parent, const QVariantList &list)
: ManagedConfigModule(parent, list)
, m_devicesModel(new DevicesModel(this))
{
qmlRegisterType<OutputsModel>("org.kde.plasma.tablet.kcm", 1, 0, "OutputsModel");
qmlRegisterType<OrientationsModel>("org.kde.plasma.tablet.kcm", 1, 0, "OrientationsModel");
qmlRegisterType<OutputsFittingModel>("org.kde.plasma.tablet.kcm", 1, 1, "OutputsFittingModel");
qmlRegisterAnonymousType<InputDevice>("org.kde.plasma.tablet.kcm", 1);
setAboutData(new KAboutData(QStringLiteral("kcm_tablet"), i18n("Tablet"), QStringLiteral("1.0"), i18n("Configure drawing tablets"), KAboutLicense::LGPL));
......
/*
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2012 Dan Vratil <dvratil@redhat.com>
SPDX-License-Identifier: GPL-2.0-or-later
Adapted from KScreen
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 as Controls
import QtGraphicalEffects 1.15
import org.kde.kirigami 2.20 as Kirigami
Item {
id: output
property bool isSelected: true
onIsSelectedChanged: {
if (isSelected) {
z = 89;
} else {
z = 0;
}
}
Rectangle {
id: outline
radius: Kirigami.Units.smallSpacing
color: Kirigami.Theme.backgroundColor
anchors.fill: parent
border {
color: isSelected ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor
width: 1
ColorAnimation on color {
duration: Kirigami.Units.longDuration
}
}
}
Item {
id: orientationPanelContainer
anchors.fill: output
visible: false
Rectangle {
id: orientationPanel
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: Kirigami.Units.largeSpacing
color: isSelected ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor
smooth: true
ColorAnimation on color {
duration: Kirigami.Units.longDuration
}
}
}
OpacityMask {
anchors.fill: orientationPanelContainer
source: orientationPanelContainer
maskSource: outline
}
}
......@@ -4,11 +4,11 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.1
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as QQC2
import org.kde.kirigami 2.6 as Kirigami
import org.kde.plasma.tablet.kcm 1.0
import org.kde.plasma.tablet.kcm 1.1
import org.kde.kcm 1.3
SimpleKCM {
......@@ -24,41 +24,58 @@ SimpleKCM {
type: Kirigami.MessageType.Information
visible: combo.count === 0
text: i18n("No drawing tablets found.")
text: i18nd("kcmtablet", "No drawing tablets found.")
}
Kirigami.FormLayout {
Kirigami.FormLayout {
id: form
enabled: combo.count > 0
QQC2.ComboBox {
id: combo
Kirigami.FormData.label: i18nd("kcmtablet", "Device:")
Kirigami.FormData.label: i18ndc("kcmtablet", "@label:listbox The device we are configuring", "Device:")
model: kcm.devicesModel
onCurrentIndexChanged: {
parent.device = kcm.devicesModel.deviceAt(combo.currentIndex)
const initialOutputArea = form.device.outputArea;
if (initialOutputArea === Qt.rect(0, 0, 1, 1)) {
outputAreaCombo.currentIndex = 0;
} else if (initialOutputArea.x === 0 && initialOutputArea.y === 0 && initialOutputArea.width === 1) {
outputAreaCombo.currentIndex = 1;
} else {
outputAreaCombo.currentIndex = 2;
}
keepAspectRatio.checked = tabletItem.aspectRatio === (form.device.outputArea.width / form.device.outputArea.height)
outputAreaView.resetOutputArea(outputAreaCombo.currentIndex, initialOutputArea)
}
}
property QtObject device: null
QQC2.ComboBox {
id: outputsCombo
Kirigami.FormData.label: i18nd("kcmtablet", "Target display:")
model: OutputsModel {}
model: OutputsModel {
id: outputsModel
}
enabled: count > 2 //It's only interesting when there's more than 1 screen
currentIndex: model.rowForOutputName(parent.device.outputName)
currentIndex: outputsModel.rowForOutputName(parent.device.outputName)
textRole: "display"
onActivated: {
parent.device.outputName = model.outputNameAt(currentIndex)
parent.device.outputName = outputsModel.outputNameAt(currentIndex)
}
}
QQC2.ComboBox {
Kirigami.FormData.label: i18nd("kcmtablet", "Orientation:")
model: OrientationsModel {}
model: OrientationsModel {
id: orientationsModel
}
enabled: parent.device && parent.device.supportsOrientation
currentIndex: model.rowForOrientation(parent.device.orientation)
currentIndex: orientationsModel.rowForOrientation(parent.device.orientation)
textRole: "display"
onActivated: {
parent.device.orientation = model.orientationAt(currentIndex)
parent.device.orientation = orientationsModel.orientationAt(currentIndex)
}
}
QQC2.CheckBox {
......@@ -69,6 +86,121 @@ SimpleKCM {
parent.device.leftHanded = checked
}
}
QQC2.ComboBox {
id: outputAreaCombo
Layout.fillWidth: true
Kirigami.FormData.label: i18nd("kcmtablet", "Area:")
model: OutputsFittingModel {}
onActivated: {
outputAreaView.changed = true
keepAspectRatio.checked = true
outputAreaView.resetOutputArea(index, index === 0 ? Qt.rect(0,0, 1,1) : Qt.rect(0, 0, 1, outputItem.aspectRatio/tabletItem.aspectRatio))
}
}
// Display fit demo
Item {
id: outputAreaView
function resetOutputArea(mode, outputArea) {
if (mode === 0) {
tabletItem.x = 0
tabletItem.y = 0
tabletItem.width = Qt.binding(() => outputItem.width);
tabletItem.height = Qt.binding(() => outputItem.height);
} else {
tabletItem.x = Qt.binding(() => outputArea.x * outputItem.width)
tabletItem.y = Qt.binding(() => outputArea.y * outputItem.height)
tabletItem.width = Qt.binding(() => tabletSizeHandle.x);
tabletItem.height = Qt.binding(() => tabletSizeHandle.y);
tabletSizeHandle.x = Qt.binding(() => outputArea.width * outputItem.width)
tabletSizeHandle.y = Qt.binding(() => outputArea.height * outputItem.height)
}
}
readonly property rect outputAreaSetting: Qt.rect(tabletItem.x/outputItem.width, tabletItem.y/outputItem.height,
tabletItem.width/outputItem.width, tabletItem.height/outputItem.height)
property bool changed: false
onOutputAreaSettingChanged: {
if (form.device && changed) {
form.device.outputArea = outputAreaSetting
}
}
Layout.fillWidth: true
Layout.preferredHeight: Math.max(outputItem.height, tabletItem.height)
enabled: parent.device
Output {
id: outputItem
readonly property size outputPhysicalSize: outputsModel.data(outputsModel.index(outputsCombo.currentIndex, 0), Qt.UserRole + 1)
readonly property size outputSize: outputsModel.data(outputsModel.index(outputsCombo.currentIndex, 0), Qt.UserRole + 2)
readonly property real aspectRatio: outputPhysicalSize.width / outputPhysicalSize.height
width: parent.width * 0.7
height: width / aspectRatio
}
Rectangle {
id: tabletItem
color: Kirigami.Theme.activeBackgroundColor
opacity: 0.8
readonly property real aspectRatio: outputAreaCombo.currentIndex === 0 ? outputItem.aspectRatio : form.device.size.width / form.device.size.height
width: tabletSizeHandle.x
height: tabletSizeHandle.y
DragHandler {
cursorShape: Qt.ClosedHandCursor
target: parent
enabled: outputAreaCombo.currentIndex >= 2
onActiveChanged: { outputAreaView.changed = true }
xAxis.minimum: 0
xAxis.maximum: outputItem.width - tabletItem.width
yAxis.minimum: 0
yAxis.maximum: outputItem.height - tabletItem.height
}
QQC2.Button {
id: tabletSizeHandle
x: outputItem.width
y: outputItem.width / parent.aspectRatio
visible: outputAreaCombo.currentIndex >= 2
icon.name: "transform-move"
display: QQC2.AbstractButton.IconOnly
text: i18nd("kcmtablet", "Resize the tablet area")
QQC2.ToolTip {
text: tabletSizeHandle.text
visible: parent.hovered
delay: Kirigami.Units.toolTipDelay
}
DragHandler {
cursorShape: Qt.SizeFDiagCursor
target: parent
onActiveChanged: { outputAreaView.changed = true }
xAxis.minimum: 10
xAxis.maximum: outputItem.width - tabletItem.x
yAxis.minimum: keepAspectRatio.checked ? (tabletItem.width / tabletItem.aspectRatio) : 10
yAxis.maximum: keepAspectRatio.checked ? (tabletItem.width / tabletItem.aspectRatio) : outputItem.height - tabletItem.y
}
}
}
}
QQC2.CheckBox {
id: keepAspectRatio
text: i18ndc("kcmtablet", "@option:check", "Lock aspect ratio")
visible: outputAreaCombo.currentIndex >= 2
checked: true
}
QQC2.Label {
text: i18ndc("kcmtablet", "tablet area position - size", "%1,%2 - %3×%4", Math.floor(outputAreaView.outputAreaSetting.x * outputItem.outputSize.width)
, Math.floor(outputAreaView.outputAreaSetting.y * outputItem.outputSize.height)
, Math.floor(outputAreaView.outputAreaSetting.width * outputItem.outputSize.width)
, Math.floor(outputAreaView.outputAreaSetting.height * outputItem.outputSize.height))
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment