Skip to content

Commit

Permalink
Joystick Gremlin now using ProfileCollections, plugins changed to wor…
Browse files Browse the repository at this point in the history
…k with front-end
  • Loading branch information
Rexeh committed Jan 21, 2024
1 parent 90d4e85 commit 1ec56fa
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 181 deletions.
282 changes: 112 additions & 170 deletions joystick_diagrams/plugins/joystick_gremlin_plugin/joystick_gremlin.py
Original file line number Diff line number Diff line change
@@ -1,183 +1,125 @@
"""Joystick Gremlin (Version ~13) XML Parser for use with Joystick Diagrams"""
"""Joystick Gremlin (Version ~13) XML Parser for use with Joystick Diagrams.
Author: Robert Cox
"""
import logging
from typing import Union
from xml.dom import minidom

from joystick_diagrams.input.profile_collection import ProfileCollection

_logger = logging.getLogger(__name__)

HAT_POSITIONS = {
1: "U",
2: "UR",
3: "R",
4: "DR",
5: "D",
6: "DL",
7: "L",
8: "UL",
}

class JoystickGremlin:
def __init__(self, filepath):
## TRY FIND PATH

class JoystickGremlinParser:
def __init__(self, filepath):
self.file = self.parse_xml_file(filepath)

# New Attributes
self.device_names = self.get_device_names()
self.profiles = []
self.modes = None
self.mode = None
self.devices = None
self.device = None
self.current_device = None
self.current_mode = None
self.current_inherit = None
self.inherit = None
self.buttons = None
self.button_array = None
self.inherit_modes = {}
self.using_inheritance = False
self.position_map = {
1: "U",
2: "UR",
3: "R",
4: "DR",
5: "D",
6: "DL",
7: "L",
8: "UL",
}
self.hats = None

def get_device_names(self) -> list:
self.devices = self.get_devices()
device_items = []

for item in self.devices:
device_items.append(item.getAttribute("name"))
return device_items

def get_modes(self) -> list:
self.devices = self.get_devices()
profile_modes = []

item = self.devices[0] # All Modes common across JG
modes = item.getElementsByTagName("mode")
for mode in modes:
mode_name = mode.getAttribute("name")
profile_modes.append(mode_name)
return profile_modes

def parse_xml_file(self, xml_file) -> minidom.Document:
## Improve loading of file, checks for validity etc
return minidom.parse(xml_file)

def create_dictionary(self, profiles=None) -> dict:
self.profiles = profiles
self.devices = self.get_devices()
_logger.debug(f"Number of Devices: {self.devices.length}")

for self.device in self.devices:
self.current_device = self.get_single_device()
self.modes = self.get_device_modes()
_logger.debug(f"All Modes: {self.modes}")
for self.mode in self.modes:
self.current_inherit = self.has_inheritance()
self.button_array = {}
self.current_mode = self.get_single_mode()
_logger.debug(f"Selected Mode: {self.current_mode}")
self.buttons = self.get_mode_buttons()
self.hats = self.get_mode_hats()
self.extract_buttons()
self.extract_hats()
self.update_joystick_dictionary(
self.current_device,
self.current_mode,
self.current_inherit,
self.button_array,
)
if self.using_inheritance:
self.inherit_joystick_dictionary()

self.filter_dictionary()
return self.joystick_dictionary

def filter_dictionary(self) -> dict:
if isinstance(self.profiles, list) and len(self.profiles) > 0:
for key, value in self.joystick_dictionary.items():
for item in value.copy():
if item not in self.profiles:
self.joystick_dictionary[key].pop(item, None)
return self.joystick_dictionary

def get_devices(self):
return self.file.getElementsByTagName("device")

def get_mode_buttons(self):
return self.mode.getElementsByTagName("button")

def get_mode_hats(self):
return self.mode.getElementsByTagName("hat")

def get_device_modes(self):
return self.device.getElementsByTagName("mode")

def get_single_device(self):
return self.device.getAttribute("name")

def get_single_mode(self):
return self.mode.getAttribute("name")

def has_inheritance(self):
inherit = self.mode.getAttribute("inherit")
if inherit != "":
if self.using_inheritance is not True:
self.using_inheritance = True
return inherit
else:
return False

def inherited_modes(self):
return self.mode.getAttribute("name")

def extract_buttons(self):
for i in self.buttons:
if i.getAttribute("description") != "":
self.button_array.update({"BUTTON_" + str(i.getAttribute("id")): str(i.getAttribute("description"))})
else:
self.button_array.update({"BUTTON_" + str(i.getAttribute("id")): self.no_bind_text})
return self.button_array

def extract_hats(self) -> None:
for i in self.hats:
hat_id = i.getAttribute("id")
_logger.debug("Hat ID: {hat_id}")

if i.getAttribute("description"):
hat_description = i.getAttribute("description")
_logger.debug("Hat has description: {hat_description}")
else:
hat_description = ""

hat_containers = i.getElementsByTagName("container")

if hat_containers:
_logger.debug("Has containers: {hat_containers.length}")

for container in hat_containers:
hat_positions = container.getElementsByTagName("action-set")
hat_count = hat_positions.length
increment = 8 / hat_count
pos = 1
_logger.debug(f"We have {hat_count} hat positions")

for position in hat_positions:
if position.getElementsByTagName("description"):
# Ignore more than 1 description. always use first
hat_direction_description = position.getElementsByTagName("description")[0].getAttribute(
"description"
)
else:
hat_direction_description = hat_description

_logger.debug(f"POV Position: {self.position_map[pos]}")

self.button_array.update(
{f"POV_{i.getAttribute('id')}_{self.position_map[pos]}": str(hat_direction_description)}
)

pos = pos + increment
else:
_logger.error(f"No container found for hat: {hat_id}")

def get_device_count(self):
return self.file.getElementsByTagName("device").length
def create_dictionary(self) -> ProfileCollection:
profile_collection = ProfileCollection()

# Get all the modes
modes = self.file.getElementsByTagName("mode")

for mode in modes:
# Create a profile for the mode using NAME
_active_profile = profile_collection.create_profile(mode.getAttribute("name"))

# Create DEVICE from PARENT node
_device_guid = mode.parentNode.getAttribute("device-guid")
_device_name = mode.parentNode.getAttribute("name")
_device_obj = _active_profile.add_device(_device_guid, _device_name)

# Iterate each AXIS / Button
bindings = mode.childNodes

for bind in bindings:
if bind.nodeType == bind.ELEMENT_NODE:
bind_type = bind.tagName
bind_description = bind.getAttribute("description")
bind_identifier = bind.getAttribute("id")

match bind_type:
case "axis":
if bind_description:
_device_obj.create_input(bind_identifier, bind_description)
case "button":
if bind_description:
_device_obj.create_input(bind_identifier, bind_description)
case "hat":
hats = self.extract_hats(bind)

if hats:
for hat_id, hat_action in hats:
_device_obj.create_input(hat_id, hat_action)
case _:
_logger.warning("Unknown bind type ({bind_type}) detected while processing {_device_guid}")

return profile_collection

def extract_hats(self, hat_node) -> list[Union[str, str] | None]:
"""Extract the hat positions for a given HAT node.
Each HAT node may contain a CONTAINER, which may contain N number of action-set nodes
Returns array of arrays containing the formatted POV CONTROL and ACTION
"""
hat_id: str = hat_node.getAttribute("id")
hat_description: str = hat_node.getAttribute("description") or ""
hat_mappings: list = []

_logger.debug("Hat ID: {hat_id}")
_logger.debug("Hat has description: {hat_description}")

# Get the containers
hat_container = hat_node.getElementsByTagName("container")

if not hat_container:
return hat_mappings

_logger.debug("Has containers: {hat_containers.length}")

hat_positions = hat_container[0].getElementsByTagName("action-set")

hat_mappings = []

for position in hat_positions:
# Get REMAP of node (assumes 1)
hat_position_id = position.getElementsByTagName("remap")[0].getAttribute("button")

# Get the description node if exists
hat_description_check = position.getElementsByTagName("description")

# If no node then continue
if not hat_description_check:
continue

hat_description = hat_description_check[0].getAttribute("description")

if not hat_description:
# If we don't have a description then no point using the item
continue

hat_mappings.append([f"POV_{hat_id}_{HAT_POSITIONS[int(hat_position_id)]}", hat_description])

return hat_mappings


if __name__ == "__main__":
pass
17 changes: 13 additions & 4 deletions joystick_diagrams/plugins/joystick_gremlin_plugin/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import logging
from pathlib import Path

from joystick_diagrams.plugins.joystick_gremlin_plugin.joystick_gremlin import (
JoystickGremlinParser,
)
from joystick_diagrams.plugins.plugin_interface import PluginInterface

from .config import settings
Expand All @@ -15,15 +18,21 @@ def __init__(self):
self.settings.validators.register()

def process(self):
return None
return self.instance.create_dictionary()

def set_path(self, path: Path) -> bool:
self.path = path
return True
inst = JoystickGremlinParser(path)

if inst:
self.instance = inst
self.path = path
return True

return False

@property
def path_type(self):
return self.FilePath("Select your Joystick Diagrams .XML file", "/%USERPROFILE%/Saved Games", [".xml"])
return self.FilePath("Select your Joystick Gremlin .XML file", "/%USERPROFILE%/Saved Games", [".xml"])

@property
def name(self) -> str:
Expand Down
21 changes: 15 additions & 6 deletions joystick_diagrams/plugins/star_citizen_plugin/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
from pathlib import Path

from joystick_diagrams.plugins.plugin_interface import PluginInterface

from .config import settings
from joystick_diagrams.plugins.star_citizen_plugin.config import (
settings, # TODO Move out plugins to separate package
)
from joystick_diagrams.plugins.star_citizen_plugin.star_citizen import (
StarCitizen, # TODO Move out plugins to separate package
)

_logger = logging.getLogger("__name__")

Expand All @@ -15,11 +19,17 @@ def __init__(self):
self.settings.validators.register()

def process(self):
return None
return self.instance.parse()

def set_path(self, path: Path) -> bool:
self.path = path
return True
inst = StarCitizen(path)

if inst:
self.path = path
self.instance = inst
return True

return False

@property
def path_type(self):
Expand All @@ -44,4 +54,3 @@ def get_path(self) -> bool:

if __name__ == "__main__":
plugin = ParserPlugin()
print(plugin)
2 changes: 1 addition & 1 deletion joystick_diagrams/ui/mock_main/setting_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def set_plugin_path(self, data):
print(f"Data is {data}")
try:
currentPlugin = self.get_selected_plugin_object()
success = currentPlugin.set_path(data)
success = currentPlugin.set_path(data[0])
# TODO Plugin module needs improving to give better information?
if success:
print("worked")
Expand Down

0 comments on commit 1ec56fa

Please sign in to comment.