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

keypress event #256

Open
cbrown1 opened this issue Mar 5, 2018 · 12 comments
Open

keypress event #256

cbrown1 opened this issue Mar 5, 2018 · 12 comments

Comments

@cbrown1
Copy link

cbrown1 commented Mar 5, 2018

I just discovered enaml and am really enjoying it. But one thing I really need is to be able to accept keypresses in my form. I have seen a stackexchange post on this but I can't get that to work. Is there any way to do this?

Thanks!

@MatthieuDartiailh
Copy link
Member

I believe this is not possible at the moment. You could define your own widget taking care of it, but that means writing some Qt specific code. However this is an interesting request and I will try to come up with at least a plan for implementing this. But I can make no promise at to when this feature may land.

@frmdstryr
Copy link
Contributor

There's a KeyEvent you can try by installing enamlx. It was updated recently to work with enaml 0.10.2.

See https://github.com/frmdstryr/enamlx/blob/master/enamlx/widgets/key_event.py

@MatthieuDartiailh
Copy link
Member

Thinking a bit about it and discussing with @sccolbert, we came to wonder what is your use case for capturing key press and if it cannot be solved in a different way.

@cbrown1
Copy link
Author

cbrown1 commented Mar 5, 2018

I generate forms that act as sort of dashboards, meaning that they mostly display information to the user. But the user must be able to quickly enter bits of information without much hassle, and without having to pay too much attention since they have other tasks to do at the same time. Over the years, I have found that accepting a keypress by the form is by far the best way to do this because the user doesn't also have to hit enter, or make sure that the correct text box is selected, or that the mouse cursor is exactly where it needs to be. They have a number keypad and just hit the right number at the right time, and it works great.

@cbrown1
Copy link
Author

cbrown1 commented Mar 5, 2018

@frmdstryr, thanks. Is there an example of how to use keyevent? I see that the most recent commits to occ_viewer.py and plot_area.py include comments about adding key events, but I don't see where in the code.

@frmdstryr
Copy link
Contributor

frmdstryr commented Mar 5, 2018

I tried this with a form and I'm not sure it's going to do what you intend as the currently focused field captures the key press so it must be added to each form element and special keys filtered out.

But heres the modified person tutorial example.

Import enamlx and call install before importing enaml.

#------------------------------------------------------------------------------
# Copyright (c) 2013, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
from __future__ import unicode_literals, print_function

from atom.api import Atom, Unicode, Range, Bool, observe

import enamlx
enamlx.install()

import enaml
from enaml.qt.qt_application import QtApplication


class Person(Atom):
    """ A simple class representing a person object.

    """
    last_name = Unicode()

    first_name = Unicode()

    age = Range(low=0)

    debug = Bool(False)

    @observe('age')
    def debug_print(self, change):
        """ Prints out a debug message whenever the person's age changes.

        """
        if self.debug:
            templ = "{first} {last} is {age} years old."
            s = templ.format(
                first=self.first_name, last=self.last_name, age=self.age,
            )
            print(s)


if __name__ == '__main__':
    with enaml.imports():
        from person_view import PersonView

    john = Person(first_name='John', last_name='Doe', age=42)
    john.debug = True

    app = QtApplication()
    view = PersonView(person=john)
    view.show()

    app.start()

Then add KeyEvents to the Window and to each Field you want to listen to (as the Field captures the key presses before it gets to the parent).

#------------------------------------------------------------------------------
# Copyright (c) 2013, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
from enaml.widgets.api import Window, Label, Field, Form
from enaml.stdlib.fields import IntField
from enamlx.widgets.api import KeyEvent

enamldef PersonForm(Form):
    attr person
    attr key_manager
    Label:
        text = 'First Name'
    Field:
        text := person.first_name
        KeyEvent:
            # Captures all presses from the field and send to the parent
            pressed :: key_manager.pressed(change)
    Label:
        text = 'Last Name'
    Field:
        text := person.last_name
    Label:
        text = 'Age'
    IntField:
        minimum = 0
        value := person.age


enamldef PersonView(Window):
    attr person
    KeyEvent: keys:
        # Captures all key presses not handled by the focused widget
        pressed :: print(change)
        released :: print(change)
    PersonForm:
        person := parent.person
        key_manager = keys

Also note, nesting a KeyEvent as a direct child in the form will mess up the automatic layout as the KeyEvent uses the parent widget.

I'm sure there's a better way to implement this but it worked for what I needed at the time (capture arrow presses to move a CNC machine head).

@tstordyallison
Copy link

The approach we’ve taken in the past was a container subclass that captures key events that bubble up and sends them to enaml.

The main use case was for shortcuts that were applied to a particular area of the screen (hence the container). We limited the implementation to only send them to enaml when there was modifier present also (plus a few exceptions).

e.g. Ctrl-S to save a view, Esc to close it.

@cbrown1
Copy link
Author

cbrown1 commented Mar 8, 2018

Thanks for the comments. @frmdstryr, that seems like it will work. But for my particular use case, it would require edits to each form item, which are not always known until runtime, and by a non-programmer. For now I will have to wait and hope something shows up in enaml.

@MatthieuDartiailh
Copy link
Member

@cbrown1 I am kind of confused because in usual GUI system, if the widget that has focus handle the key event it will never bubble up to its parent (http://doc.qt.io/qt-5/qkeyevent.html). So in your use case, what would you expect to see if one field has focus before the user start typing ? and how is the form routing the keypress if the focused widget does not handle it ?

@cbrown1
Copy link
Author

cbrown1 commented Mar 9, 2018

There aren't any text input fields in my forms, only labels, images, etc and maybe a button or two. My understanding is that when any of these widgets have focus, they pass the key events on to the form. Of course, if a textbox had focus it would process the key event normally.

@MatthieuDartiailh
Copy link
Member

In this case I believe that @frmdstryr solution would work with a single KeyEvent on the container since the children do not capture the key press (he needs multiple because the field captures it).
After discussing it with @sccolbert, it appears that the clean way to implement this would be through features (like focus event and drag and drop support). This solution is preferred first because it encourages the user to not abuse the feature and second because it avoids possible issues with re-parenting. I woon't have time to work on this any time soon, but volunteers are welcome.

@Julian-O
Copy link
Contributor

This doesn't solve the general case, but might solve the specific case the @cbrown1 is having.

I am writing a similar app (mainly to learn Enaml). It shows an image and the user must classify it by pressing one character - e.g. 'd' for 'dog', 'c' for 'cat', 'n' for 'neither' or '0' for 'unclear. There are only a small and finite number of keys I need to capture.

I have two similar approaches that are working for me: Menu bars with keyboard shortcuts and toolbars with keyboard shortcuts. I haven't decided which looks better yet. One possibility is to create a toolbar and style it as invisible, so I can be even more flexible.

Shortcuts are added to the menuitem/action title - "Cat\tc" means "c" is the keyboard shortcut. (Does "&Cat" work too? I haven't tested.)

It doesn't work if the cursor is on a field element, but do you need those?

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

No branches or pull requests

5 participants