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

Conversation

ubunatic
Copy link
Contributor

@ubunatic ubunatic commented Feb 17, 2023

  • adding a live search box and clear button to quickly filter mappings.
  • added a place holder in the rename box.
  • added -R CLI flag to skip the pkexec dialog without the app failing
  • moved GtkKeyEvent to components tests and extened it to emit key events in tests
  • autoformatted the code and added some type annotations

default view:
image

view when filtering:
image

new usage info:

usage: input-remapper-gtk [-h] [-d] [-R]

options:
  -h, --help     show this help message and exit
  -d, --debug    displays additional debug information
  -R, --no-root  allow rejecting root access (by cancelling the pkexec dialog)

@jonasBoss
Copy link
Collaborator

Can you encapsulate this functionality better?
Given that this is purely view related and does nothing to the underlying data this should not interact with the DataManager.

I would encapsulate the clear-button and the search-input into a new component i.e. gui.components.editor.MappingFilterControls. This component can directly publish the FilterChanged on the MessageBroker and manage the user input internally.

This also makes testing of that component easy, see test_components.py for examples on that.

@ubunatic
Copy link
Contributor Author

Can you encapsulate this functionality better? Given that this is purely view related and does nothing to the underlying data this should not interact with the DataManager.

I would encapsulate the clear-button and the search-input into a new component i.e. gui.components.editor.MappingFilterControls. This component can directly publish the FilterChanged on the MessageBroker and manage the user input internally.

This also makes testing of that component easy, see test_components.py for examples on that.

Understood, I will give it a try in the next days.
Do you have a hint for me, how I can send keyboard events in tests, to see if the controls are behaving as expected when receiving input.

@sezanzeb
Copy link
Owner

sezanzeb commented Feb 18, 2023

how I can send keyboard events in tests

As far as I remember set_text on labels, entries and buffers also emits events that trigger our listeners https://lazka.github.io/pgi-docs/#Gtk-3.0/classes/Entry.html#Gtk.Entry.set_text

For buttons use https://lazka.github.io/pgi-docs/#Gtk-3.0/classes/Button.html#Gtk.Button.clicked for example.

@sezanzeb
Copy link
Owner

@jonasBoss
Copy link
Collaborator

I think you can just call the set_... methods on the gtk objects, and the object will send the appropriate events see TestAnalogInputSwitch.test_updates_event_as_analog as an example.

For keyboard events, I did not find a good way to do that. we have TestMappingSelectionLabel.test_leaves_edit_mode_on_esc which directly calls the _on_gtk... method but it would be much cleaner if you find a way to send the event through gtk.

For Gtk.Entry objects they have a activate() method, which we use in TestMappingSelectionLabel.test_enter_edit_mode_updates_visibility to hit the return key.

@ubunatic ubunatic force-pushed the feature/mapping-filter branch from 6e4b4fd to 3684604 Compare February 18, 2023 23:49
@ubunatic
Copy link
Contributor Author

ubunatic commented Feb 19, 2023

When I run the TestGui suite I get one error now. Not sure if this is caused by my changes.

python -m tests.integration.test_gui TestGui.test_combination -q -b
...
  File "/home/uwe/git/input-remapper/tests/integration/test_gui.py", line 1042, in test_combination
    ).output_symbol,
AttributeError: 'NoneType' object has no attribute 'output_symbol'

Other than that, I think the component is now ready and should work with other ListBoxes too.

@ubunatic ubunatic force-pushed the feature/mapping-filter branch from aede01f to 6be2700 Compare February 19, 2023 00:44
@@ -32,7 +32,7 @@
from tests.lib.logger import logger
from tests.lib.fixtures import fixtures
from tests.lib.pipes import push_event, push_events, uinput_write_history_pipe
from tests.integration.test_components import FlowBoxTestUtils
from tests.integration.test_components import FlowBoxTestUtils, GtkKeyEvent
Copy link
Owner

@sezanzeb sezanzeb Feb 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing the GtkKeyEvent class from this file with the import causes the test to fail somehow.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, funny, now it doesn't fix it anymore.

Copy link
Owner

@sezanzeb sezanzeb Feb 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it is a race condition. it fails most of the time, but works sometimes. This could be timing related. Looking at the test, there are some calls to self.throttle. Something is possibly happening slower than before or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw. I seem to be the only user of the class. Did it serve a purpose before?

Copy link
Contributor Author

@ubunatic ubunatic Feb 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I found this:

    def throttle(self, time_=10):
        """Give GTK some time in ms to process everything."""
        # tests suddenly started to freeze my computer up completely and tests started
        # to fail. By using this (and by optimizing some redundant calls in the gui) it
        # worked again. EDIT: Might have been caused by my broken/bloated ssd. I'll
        # keep it in some places, since it did make the tests more reliable after all.
        for _ in range(time_ // 2):
            gtk_iteration()
            time.sleep(0.002)

I wonder if we can use sth. like GLib.timeout_add() instead. From what I know time.sleep is discouraged for managing any kind of timing issues. Always try to wait for the desired state to appear, rather than waiting fixed amounts of time. It is just a guess, but time.sleep may actually prevent other critical code from being executed during the sleep (on the main thread I guess, but I do not know how Python + Gtk works with regards to UI thread vs. background threads) 🤷🏼‍♂️

I will try to add sth. like this:

        self.awaitEqual(
            lambda t: self.data_manager.active_preset.get_mapping(
                get_combination(combination_1)
            ).output_symbol,
            "a",
            timeout=1000,  # optional, default maybe at 100
        )
        self.awaitTrue(
            # ...
        )

Copy link
Owner

@sezanzeb sezanzeb Feb 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our tests are horribly complicated. I can also see that throttle might be a bad practice in some places. In others, if I remember correctly, it is just there to give gtk some time to process internal events and stuff and to give it some time to render the gui properly.

test_combination works when my current PR #578 is merged with yours, so don't worry about it too much

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#578 is merged, once you pull the newest changes from beta it should work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I resolved the conflicts. Yes the TestGui suite now runs fine 👍🏼.

@jonasBoss
Copy link
Collaborator

Thank you for contributing, It helps us a lot to have more people working on this 👍.
From my side this looks good to merge apart from one thing:
image
The clear button is way to big, and kind of ugly. I am using the kde-breeze theme. There is a "edit-clear-symbolic" icon which might be better.
image


Although I am fine with your current implementation, there are still a few things I want to note, but that is probably just my preference 🤷.

It seems counter-intuitive that there are now two classes controlling the MappingListBox.
I would have probably gone with some hybrid from your first, and the current solution:

class MappingListBox:
    """The listbox showing all available mapping in the active_preset."""

    def _on_filter_changed(self, data: FilterData):
        # filter the mappings

class FilterControls:
    """A text entry and a clear button controlling a filter"""
    def __init__(
        self,
        message_broker,
        message_type: MessageType,
        input: Gtk.Entry,
        clear_btn: Gtk.Button,
    ):
        ...

    def _on_gtk_input_changed(self, *_):
        # publish message with FilterData

    def _on_gtk_clear_btn_clicked(self, *_):
        # reset the input and publish message
    
    def _on_gtk_esc_pressed(self, _, key_event: Gdk.EventKey):
        if key_event.keyval == Gdk.KEY_Escape:
            ...

This lets us test the two components completely independent, and if there is ever a need to filter the MappingListBox for some other reason than the user input we can do that with the message.

@ubunatic
Copy link
Contributor Author

I separated the ListBox Gtk-specific code from the mapping filter entry in another branch

The branch re-adds massaging and includes all changes from this PR. Also, since the Gtk code is very generic, I moved that to another module. @jonasBoss is this more what you had in mind? If yes, I would create another PR for further discussion and close this one.

@jonasBoss
Copy link
Collaborator

I love it 👍. It keeps each class simple and focused on only one task.

@ubunatic
Copy link
Contributor Author

This PR is replaced by a new version of the feature at #628

@ubunatic ubunatic closed this Feb 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants