Tuesday, March 10, 2015

Creative Problem Solving

It's about time for another of my yearly personal blog posts. This time I'll be covering a creative approach that I took to implementing a feature.

The feature: EMPC has a filesystem view which displays a gengrid of all available music. When the user types while this view is active, an entry appears to search for entities in the current directory. While the entry is active, however, the user should also be able to continue using the arrow keys to navigate. Also, there is no cursor in this entry so allowing left/right arrows to move the cursor position is something that must be avoided.

The problem: This seemingly requires splitting focus between the gengrid widget and the entry widget and redirecting certain key presses to each widget. EFL does not permit this type of focus splitting, and attempting to redirect key presses to various widgets is not smart.

First attempt: Initially I figured I'd just let Elementary figure out focus and things would "magically work". I added object key callbacks for both the gengrid and the entry; the gengrid would append to the entry for keys with compose strings, and the entry would operate as usual. Both callbacks had handling code for special keys Escape/Return/Enter which would hide the entry.

Minor success: The arrow keys still functioned as expected, typing continued incrementing the search as expected, and hiding the entry worked as expected.

Major failure: Pressing backspace, which has a valid compose string ("\b"), would cause invalid characters to be added to the entry, and the user would have to start over. Also, other entry keybinds (eg. ctrl+a) would never work as expected. The entry was never receiving focus (thanks elm!), and it would have blocked arrow navigation if it had worked as I expected.

Second attempt: After tossing out a few more failure ideas, I came to the conclusion that there was no way to do this which did not involve the entry itself having focus at all times. Compose, backspace, and shortcut handling is all done in edje internals, and I had no desire to face the efl repo commit message ghestapo unnecessarily by breaking out such features as utility functions. Nor did I want to do any shenanigans with changing the focus object during key press and faking the input object path. No, my path was simpler. I would create an ecore key event handler before the elementary event handler.
To do this requires some knowledge of efl init internals and ordering, but the basic idea is that you manually call ecore_event_init() before elm_init(), set up whatever input event handlers you want (eg. ECORE_EVENT_KEY_DOWN), and then call elm_init(). In the corresponding handler, you can then return 0/false/EINA_FALSE/ECORE_CALLBACK_DONE/ECORE_CALLBACK_CANCEL to prevent the event from being propagated any farther down the canvas. So now, when the entry is active, focus is disallowed on the gengrid to force it onto the entry, and then the arrow keys are filtered in my pre-event handler.
The downside here is that gengrid is another failure widget which lacks a lot of functionality/testing, and so there are no externally accessible functions for navigating directionally. Since there is the constant talk of scrapping gengrid, I opted to write a simple algorithm in my app to calculate and find the target item xy coordinates using elm_gengrid_item_pos_get() with some iteration.

Success: Everything is working as expected, and the related event filtering code can be found here, while the gengrid item navigation code is here.

Overall this was a clever solution to the issue, but not necessarily a great one. I've used this technique for a number of input-related issues in EMPC, and it feels hacky in every case. I think that ideally there should probably be a more intuitive way of doing input blocking and filtering which doesn't require the awful key grab api in evas.