Skip to content

Commit 467377a

Browse files
committed
Add dialog for the auto mapper
1 parent c18c63d commit 467377a

5 files changed

Lines changed: 302 additions & 0 deletions

File tree

gremlin/ui/auto_mapper.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# -*- coding: utf-8; -*-
2+
3+
# Copyright (C) 2015 - 2025 Lionel Ott
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
import logging
19+
20+
from PySide6 import QtCore
21+
from PySide6 import QtQml
22+
23+
import dill
24+
from gremlin import auto_mapper
25+
from gremlin.ui import backend
26+
27+
QML_IMPORT_NAME = "Gremlin.UI"
28+
QML_IMPORT_MAJOR_VERSION = 1
29+
30+
31+
@QtQml.QmlElement
32+
class AutoMapper(QtCore.QObject):
33+
def __init__(self, parent=None):
34+
super().__init__(parent)
35+
36+
@QtCore.Slot(dict, dict, bool, bool, result=str)
37+
def create_mappings(self, physical_devices, vjoy_devices, overwrite, repeat) -> str:
38+
"""
39+
Create mappings between physical and vJoy devices.
40+
41+
Args:
42+
physical_devices: Dictionary of {device_guid: is_selected} for physical devices
43+
vjoy_devices: Dictionary of {vjoy_id: is_selected} for vJoy devices
44+
overwrite: Whether to overwrite existing mappings
45+
repeat: Whether to repeat vJoy mappings
46+
47+
Returns:
48+
A string report for the user summarizing new mappings.
49+
"""
50+
logging.getLogger("system").info(
51+
"Creating mappings from physical devices %s to vJoy devices %s, "
52+
"options overwrite: %s, repeat: %s",
53+
physical_devices,
54+
vjoy_devices,
55+
overwrite,
56+
repeat,
57+
)
58+
mapper = auto_mapper.AutoMapper(backend.Backend().profile)
59+
return mapper.generate_mappings(
60+
[
61+
dill.GUID.from_str(guid)
62+
for (guid, chosen) in physical_devices.items()
63+
if chosen
64+
],
65+
[int(vjoy_id) for (vjoy_id, chosen) in vjoy_devices.items() if chosen],
66+
auto_mapper.AutoMapperOptions(
67+
repeat_vjoy_inputs=repeat,
68+
overwrite_used_inputs=overwrite,
69+
),
70+
)

gremlin/ui/backend.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from gremlin.logical_device import LogicalDevice
3333
from gremlin.signal import signal
3434

35+
from gremlin.ui.auto_mapper import AutoMapper
3536
from gremlin.ui.device import InputIdentifier, LogicalDeviceManagementModel
3637
from gremlin.ui.profile import InputItemModel, ModeHierarchyModel
3738
from gremlin.ui.script import ScriptListModel
@@ -312,6 +313,10 @@ def getInputItem(
312313
@Slot(result=LogicalDeviceManagementModel)
313314
def getLogicalDeviceManagementModel(self) -> LogicalDeviceManagementModel:
314315
return LogicalDeviceManagementModel(self)
316+
317+
@Slot(result=AutoMapper)
318+
def getAutoMapper(self) -> AutoMapper:
319+
return AutoMapper(self)
315320

316321
@Slot(str, int, result=bool)
317322
def isActionExpanded(self, uuid_str: str, index: int) -> bool:

gremlin/ui/device.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ class DeviceListModel(QtCore.QAbstractListModel):
248248
QtCore.Qt.UserRole + 6: QtCore.QByteArray("vid".encode()),
249249
QtCore.Qt.UserRole + 7: QtCore.QByteArray("guid".encode()),
250250
QtCore.Qt.UserRole + 8: QtCore.QByteArray("joy_id".encode()),
251+
QtCore.Qt.UserRole + 9: QtCore.QByteArray("vjoy_id".encode()),
251252
}
252253

253254
role_query = {
@@ -259,6 +260,7 @@ class DeviceListModel(QtCore.QAbstractListModel):
259260
"vid": lambda dev: "{:04X}".format(dev.vendor_id),
260261
"guid": lambda dev: str(dev.device_guid),
261262
"joy_id": lambda dev: dev.joystick_id,
263+
"vjoy_id": lambda dev: dev.vjoy_id,
262264
}
263265

264266
def __init__(self, parent=None):

qml/DialogAutoMapper.qml

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// -*- coding: utf-8; -*-
2+
//
3+
// Copyright (C) 2015 - 2025 Lionel Ott
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
19+
import QtQuick
20+
import QtQuick.Controls
21+
import QtQuick.Layouts
22+
import QtQuick.Window
23+
24+
import QtQuick.Controls.Universal
25+
26+
import Gremlin.Device
27+
import Gremlin.Profile
28+
import Gremlin.UI
29+
30+
31+
Window {
32+
id: _root
33+
34+
minimumWidth: 900
35+
minimumHeight: 300
36+
37+
title: "Auto Mapper"
38+
39+
DeviceListModel {
40+
id: physicalDevices
41+
deviceType: "physical"
42+
}
43+
44+
DeviceListModel {
45+
id: virtualDevices
46+
deviceType: "virtual"
47+
}
48+
49+
property AutoMapper autoMapper: backend.getAutoMapper()
50+
51+
// Properties to track the selected devices
52+
property var selectedPhysicalDevices: ({})
53+
property var selectedVJoyDevices: ({})
54+
property bool overwriteNonEmpty: false
55+
property bool repeatVJoy: false
56+
57+
Rectangle {
58+
id: mainWindow
59+
anchors.fill: parent
60+
anchors.margins: 10
61+
62+
Column {
63+
id: mainColumn
64+
anchors.fill: parent
65+
spacing: 10
66+
67+
Row {
68+
id: devicesRow
69+
width: parent.width
70+
height: parent.height - 60 // Leave space for the button
71+
spacing: 20
72+
73+
Column {
74+
id: physicalDevicesColumn
75+
width: parent.width * 0.45
76+
height: parent.height
77+
spacing: 5
78+
79+
CheckBox {
80+
id: overwriteNonEmptyPhysicalInputsCheckbox
81+
width: parent.width
82+
text: qsTr("Overwrite non-empty physical inputs")
83+
checked: overwriteNonEmpty
84+
onCheckedChanged: overwriteNonEmpty = checked
85+
}
86+
87+
GroupBox {
88+
id: physicalDevicesGroupBox
89+
width: parent.width
90+
height: parent.height - overwriteNonEmptyPhysicalInputsCheckbox.height - 5
91+
title: qsTr("Physical Devices")
92+
93+
ScrollView {
94+
id: physicalDevicesScroll
95+
anchors.fill: parent
96+
clip: true
97+
98+
Column {
99+
width: physicalDevicesScroll.width - 20
100+
spacing: 5
101+
padding: 5
102+
103+
Repeater {
104+
model: physicalDevices
105+
delegate: CheckBox {
106+
width: parent.width - 10
107+
text: model.name
108+
checked: false
109+
onCheckedChanged: {
110+
selectedPhysicalDevices[model.guid] = checked;
111+
}
112+
}
113+
}
114+
}
115+
}
116+
}
117+
}
118+
119+
Column {
120+
id: vjoyDevicesColumn
121+
width: parent.width * 0.45
122+
height: parent.height
123+
spacing: 5
124+
125+
CheckBox {
126+
id: repeatVJoyCheckbox
127+
width: parent.width
128+
text: qsTr("Repeat vJoy devices")
129+
checked: repeatVJoy
130+
onCheckedChanged: repeatVJoy = checked
131+
}
132+
133+
GroupBox {
134+
id: vjoyDevicesGroupBox
135+
width: parent.width
136+
height: parent.height - repeatVJoyCheckbox.height - 5
137+
title: qsTr("vJoy Devices")
138+
139+
ScrollView {
140+
id: vjoyDevicesScroll
141+
anchors.fill: parent
142+
clip: true
143+
144+
Column {
145+
width: vjoyDevicesScroll.width - 20
146+
spacing: 5
147+
padding: 5
148+
149+
Repeater {
150+
model: virtualDevices
151+
delegate: CheckBox {
152+
width: parent.width - 10
153+
text: model.name
154+
checked: false
155+
onCheckedChanged: {
156+
selectedVJoyDevices[model.vjoy_id] = checked;
157+
}
158+
}
159+
}
160+
}
161+
}
162+
}
163+
}
164+
}
165+
166+
Row {
167+
id: buttonRow
168+
width: Math.min(600, parent.width * 0.8)
169+
height: 60
170+
spacing: 10
171+
anchors.horizontalCenter: parent.horizontalCenter
172+
173+
Button {
174+
id: createButton
175+
text: qsTr("Create 1:1 mappings")
176+
onClicked: {
177+
var result = autoMapper.create_mappings(
178+
selectedPhysicalDevices, selectedVJoyDevices,
179+
overwriteNonEmpty, repeatVJoy);
180+
resultText.text = result ? result : "";
181+
}
182+
height: parent.height
183+
width: 200
184+
}
185+
186+
TextArea {
187+
id: resultText
188+
width: parent.width - createButton.width - parent.spacing
189+
height: parent.height
190+
readOnly: true
191+
wrapMode: Text.WordWrap
192+
horizontalAlignment: Text.AlignRight
193+
placeholderText: qsTr("Awaiting user selections and button press")
194+
background: Rectangle {
195+
color: "#f5f5f5"
196+
border.color: "#cccccc"
197+
border.width: 1
198+
radius: 2
199+
}
200+
}
201+
}
202+
}
203+
}
204+
}

qml/Main.qml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ ApplicationWindow {
150150
text: qsTr("Input Repeater")
151151
//onTriggered: Helpers.createComponent(".qml")
152152
}
153+
MenuItem {
154+
text: qsTr("Auto Mapper")
155+
onTriggered: Helpers.createComponent("DialogAutoMapper.qml")
156+
}
153157
MenuItem {
154158
text: qsTr("Device Information")
155159
onTriggered: Helpers.createComponent("DialogDeviceInformation.qml")
@@ -288,6 +292,23 @@ ApplicationWindow {
288292
}
289293
}
290294

295+
ToolButton {
296+
text: "A"
297+
font.family: "bootstrap-icons"
298+
font.pixelSize: 20
299+
font.weight: 900
300+
301+
ToolTip {
302+
visible: parent.hovered
303+
text: qsTr("Open Auto Mapper")
304+
delay: 500
305+
}
306+
307+
onClicked: {
308+
Helpers.createComponent("DialogAutoMapper.qml")
309+
}
310+
}
311+
291312
Rectangle {
292313
Layout.fillWidth: true
293314
}

0 commit comments

Comments
 (0)