Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mapping filter in Input section #621

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions bin/input-remapper-gtk
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ from inputremapper.daemon import DaemonProxy
from inputremapper.logger import logger, update_verbosity, log_info


def start_processes() -> DaemonProxy:
def start_processes(ignore_pkexec_errors=False) -> DaemonProxy:
"""Start reader-service and daemon via pkexec to run in the background."""
# this function is overwritten in tests
try:
ReaderService.pkexec_reader_service()
ReaderService.pkexec_reader_service(ingore_errors=ignore_pkexec_errors)
except Exception as e:
logger.error(e)
sys.exit(11)
Expand All @@ -57,7 +57,12 @@ if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument(
'-d', '--debug', action='store_true', dest='debug',
help=_('Displays additional debug information'),
help=_('displays additional debug information'),
default=False
)
parser.add_argument(
'-R', '--no-root', action='store_true', dest='no_root',
help=_('allow rejecting root access (by cancelling the pkexec dialog)'),
default=False
)

Expand Down Expand Up @@ -85,8 +90,8 @@ if __name__ == '__main__':

# create the reader before we start the reader-service (start_processes) otherwise
# it can come to race conditions with the creation of pipes
reader_client = ReaderClient(message_broker, _Groups())
daemon = start_processes()
reader_client = ReaderClient(message_broker, _Groups(), ignore_pkexec_errors=options.no_root)
daemon = start_processes(ignore_pkexec_errors=options.no_root)

data_manager = DataManager(
message_broker, GlobalConfig(), reader_client, daemon, GlobalUInputs(), system_mapping
Expand Down
115 changes: 87 additions & 28 deletions data/input-remapper.glade
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
<property name="margin-end">2</property>
<property name="icon-name">media-playback-start</property>
</object>
<object class="GtkImage" id="clear-icon">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">sweeper</property>
</object>
<object class="GtkImage" id="copy-icon1">
<property name="visible">True</property>
<property name="can-focus">False</property>
Expand Down Expand Up @@ -436,6 +441,7 @@ Shortcut: ctrl + del</property>
<object class="GtkEntry" id="preset_name_input">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="placeholder-text" translatable="yes">enter preset name...</property>
</object>
<packing>
<property name="expand">True</property>
Expand Down Expand Up @@ -501,6 +507,7 @@ Shortcut: ctrl + del</property>
<property name="can-focus">False</property>
<property name="margin-top">18</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
Expand All @@ -521,36 +528,51 @@ Shortcut: ctrl + del</property>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="margin-bottom">18</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="spacing">6</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="combination-label">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">no input configured</property>
<property name="wrap">True</property>
<property name="halign">center</property>
<property name="margin-bottom">18</property>
<child>
<object class="GtkLabel" id="combination-label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">no input configured</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="recording_status">
<property name="can-focus">False</property>
<property name="opacity">0.5</property>
<property name="label" translatable="yes"> (recording ...)</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="recording_status">
<property name="can-focus">False</property>
<property name="opacity">0.5</property>
<property name="label" translatable="yes"> (recording ...)</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
Expand All @@ -564,11 +586,9 @@ Shortcut: ctrl + del</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="margin-start">18</property>
<property name="margin-end">18</property>
<property name="margin-bottom">18</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="spacing">6</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="create_mapping_button">
<property name="label" translatable="yes">Add</property>
Expand All @@ -580,8 +600,8 @@ Shortcut: ctrl + del</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
Expand All @@ -596,7 +616,7 @@ Shortcut: ctrl + del</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
Expand All @@ -610,7 +630,7 @@ Shortcut: ctrl + del</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
Expand All @@ -629,6 +649,45 @@ Shortcut: ctrl + del</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="mapping-filter-box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkEntry" id="mapping-filter-input">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="placeholder-text" translatable="yes">filter mappings...</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="mapping-filter-clear-button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Clear search filter</property>
<property name="halign">start</property>
<property name="image">clear-icon</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
Expand Down
1 change: 1 addition & 0 deletions inputremapper/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def may_autoload(self, group_key: str, preset: str):

def remove_timeout(func):
"""Remove timeout to ensure the call works if the daemon is not a proxy."""

# the timeout kwarg is a feature of pydbus. This is needed to make tests work
# that create a Daemon by calling its constructor instead of using pydbus.
def wrapped(*args, **kwargs):
Expand Down
120 changes: 119 additions & 1 deletion inputremapper/gui/components/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@

from gi.repository import Gtk

from typing import Optional
from typing import (
Optional,
Iterator,
)

from inputremapper.configs.mapping import MappingData

Expand Down Expand Up @@ -173,3 +176,118 @@ def _render(self):
label.append(self._mapping_name or "?")

self._gui.set_label(" / ".join(label))


class ListFilterControl:
"""Implements UI-side filtering of list widgets.

The following example creates a new ``ListFilterControl`` for a given
``Gtk.ListBox`` and a given ``Gtk.Entry`` for text input. It also sets all
optional arguments to override some default behavior.

>>> ListFilterControl(
>>> my_gtk_listbox,
>>> my_gtk_entry,
>>> clear_button=my_gtk_button, # use an optional clear button
>>> case_sensitive=True, # change default behavior
>>> get_row_name=MyRow.get_name # custom row name getter
>>> )

"""

MAX_WIDGET_TREE_TEXT_SEARCH_DEPTH = 10

def __init__(
self,
# message_broker: MessageBroker,
controlled_listbox: Gtk.ListBox,
filter_entry: Gtk.GtkEntry,
clear_button: Gtk.Button = None,
case_sensitive=False,
get_row_name=None,
):
self._controlled_listbox: Gtk.ListBox = controlled_listbox
self._filter_entry: Gtk.Entry = filter_entry
self._clear_button: Gtk.Button = clear_button

self._filter_value: str = ""
self._case_sensitive: bool = bool(case_sensitive)
self._get_row_name = get_row_name or self.get_row_name

self._connect_gtk_signals()

@classmethod
def get_row_name(T, row: Gtk.ListBoxRow) -> str:
"""
Returns the visible text of a Gtk.ListBoxRow from both the row's `name`
attribute or the row's text in the UI.
"""
text = getattr(row, "name", "")

# find and join all text in the ListBoxRow
text += " ".join(v for v in T.get_widget_tree_text(row) if v != "")

return text.strip()

@classmethod
def get_widget_tree_text(T, widget: Gtk.Widget, level=0) -> Iterator[str]:
"""
Recursively traverses the tree of child widgets starting from the given
widget, and yields the text of all text-containing widgets.
"""
if level > T.MAX_WIDGET_TREE_TEXT_SEARCH_DEPTH:
return

if hasattr(widget, "get_label"):
yield (widget.get_label() or "").strip()
if hasattr(widget, "get_text"):
yield (widget.get_text() or "").strip()
if isinstance(widget, Gtk.Container):
for t in widget.get_children():
yield from T.get_widget_tree_text(t, level=level + 1)

def _connect_gtk_signals(self):
if self._clear_button:
self._clear_button.connect("clicked", self.on_gtk_clear_button_clicked)
self._filter_entry.connect("key-release-event", self.on_gtk_filter_entry_input)

# apply defined filter by sending out the corresponding events
def apply_filter(self):
self._apply_filter_to_listbox_children()

# matches the current filter_value and filter_options with the given value
def match_filter(self, value: str):
value = (value or "").strip()

# if filter is not set, all rows need to match
if self._filter_value == "":
return True

if self._case_sensitive:
return self._filter_value in value
else:
return self._filter_value.lower() in value.lower()

def _apply_filter_to_listbox_children(self):
value = self._filter_value.lower()
selected: Gtk.ListBoxRow = None
row: Gtk.ListBoxRow = None
for row in self._controlled_listbox.get_children():
if self.match_filter(self._get_row_name(row)):
# show matching rows, then select the first row
row.show()
if selected is None:
selected = row
self._controlled_listbox.select_row(selected)
else:
# hide non-matching rows
row.hide()

def on_gtk_filter_entry_input(self, _, event: Gdk.EventKey):
self._filter_value = (self._filter_entry.get_text() or "").strip()
self.apply_filter()

def on_gtk_clear_button_clicked(self, *_):
self._filter_entry.set_text("")
self._filter_value = ""
self.apply_filter()
1 change: 0 additions & 1 deletion inputremapper/gui/components/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,6 @@ def _set_model(self, target: str):
self.model.clear()
self.model.append(["None, None", _("No Axis")])
for type_, code in types_codes:

key_name = bytype[type_][code]
if isinstance(key_name, list):
key_name = key_name[0]
Expand Down
Loading