Entry / TextBuffer that does not listen to accelerators?

classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

Entry / TextBuffer that does not listen to accelerators?

Stephan Brunner
Hi all,

I'd thought this is a common problem, but didn't find a solution in the docs
or via google:

In  my application, I would like to have some "fast and simple" accelerators
like <u> or <delete>. On the other hand, I have some Gtk2::Entry and
Gtk2::TextBuffer for text editing. Of course, I still do want to be able to
insert 'u' in those...
Is there any way to give the Entry / TextBuffer a higher "priority" than the
accels, so that (while editing text) <u> inserts a 'u' and doesn't fire up
the associated action? Something like $widget->set_listen_to_accels(FALSE)?

Maybe it would work (didn't try yet) to have a key_press_event-callback for
the text widgets that manually inserts a 'u' (and all the other accelerated
chars), but that doesn't look like a elegant solution...

Thanks for your help,

Stephan
_______________________________________________
gtk-perl-list mailing list
[hidden email]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list
Reply | Threaded
Open this post in threaded view
|

Re: Entry / TextBuffer that does not listen to accelerators?

Jan Hudec
On Fri, Sep 30, 2005 at 14:51:40 +0200, Stephan Brunner wrote:
> Hi all,
>
> I'd thought this is a common problem, but didn't find a solution in the docs
> or via google:
>
> In  my application, I would like to have some "fast and simple" accelerators
> like <u> or <delete>. On the other hand, I have some Gtk2::Entry and
> Gtk2::TextBuffer for text editing. Of course, I still do want to be able to
> insert 'u' in those...

It should work. Out of the box. Have you _tried_ it?

The reason this should work is, that the active widget gets the input.
If the widget does not handle it, it passes it on to it's parent. And so
on up to the window, which handles the accels.

> Is there any way to give the Entry / TextBuffer a higher "priority" than the
> accels, so that (while editing text) <u> inserts a 'u' and doesn't fire up
> the associated action? Something like $widget->set_listen_to_accels(FALSE)?
>
> Maybe it would work (didn't try yet) to have a key_press_event-callback for
> the text widgets that manually inserts a 'u' (and all the other accelerated
> chars), but that doesn't look like a elegant solution...

It would work iff it is not necessary. The widget itself does the
insertion in a key-press-event callback.

--
                                                 Jan 'Bulb' Hudec <[hidden email]>

_______________________________________________
gtk-perl-list mailing list
[hidden email]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list

signature.asc (196 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Entry / TextBuffer that does not listen to accelerators?

Stephan Brunner
Am Freitag 30 September 2005 15:14 schrieb Jan Hudec:

> On Fri, Sep 30, 2005 at 14:51:40 +0200, Stephan Brunner wrote:
> > Hi all,
> >
> > I'd thought this is a common problem, but didn't find a solution in the
> > docs or via google:
> >
> > In  my application, I would like to have some "fast and simple"
> > accelerators like <u> or <delete>. On the other hand, I have some
> > Gtk2::Entry and Gtk2::TextBuffer for text editing. Of course, I still do
> > want to be able to insert 'u' in those...
>
> It should work. Out of the box. Have you _tried_ it?

I tried it - see the following short example:

*** snip ***

#!/usr/bin/perl
use warnings;
use strict;
use Gtk2 '-init';

my %Top;
$Top{window} = Gtk2::Window->new;
$Top{window}->signal_connect (destroy => sub { Gtk2->main_quit; });
$Top{window}->set_default_size(300, 100);

my %Menu;
$Menu{string} = <<"EOS";
<ui>
  <menubar name='Menu'>
    <menu action='File'>
    <menuitem action='u_action'/>
    </menu>
  </menubar>
</ui>
EOS

# Actions
$Menu{actions} = [
     [ 'File', undef, 'File' ],
     [ "u_action",  undef,    "Do nothing", "u", undef, \&u_action ]
   ];
$Menu{action_group} = Gtk2::ActionGroup->new('Menu');
$Menu{action_group}->add_actions($Menu{actions});

# UIManager
$Menu{manager} = Gtk2::UIManager->new();

# Actions -> UIManager
$Menu{manager}->insert_action_group($Menu{action_group}, 0);

# Menu{string} -> UIManager
$Menu{manager}->add_ui_from_string($Menu{string});

# Accelerators -> Top{window}
$Top{window}->add_accel_group($Menu{manager}->get_accel_group());

# VBox -> Top{window}
$Top{vbox} = Gtk2::VBox->new();
$Top{window}->add($Top{vbox});

# Menu -> VBox
$Top{vbox}->pack_start($Menu{manager}->get_widget('/Menu'), 0, 0, 0);

# Entry -> VBox
my $Entry = Gtk2::Entry->new();
$Entry->set_text("Enter some text (try 'u'!)");
$Top{vbox}->pack_start($Entry, 0, 0, 0);

# Run
$Top{window}->show_all;
Gtk2->main;

# the action
{
        my $count;
        sub u_action {
    $count++;
    print "$count: This is sub u_action...\n";
        }
}

*** snap ***

I am not able to enter any 'u' into the Entry.

> The reason this should work is, that the active widget gets the input.
> If the widget does not handle it, it passes it on to it's parent. And so
> on up to the window, which handles the accels.

Reading this, I wonder even more why I can't get it to work the way I want to.
Is there some subtle mistake or usage-error in my code?

Stephan
_______________________________________________
gtk-perl-list mailing list
[hidden email]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list
Reply | Threaded
Open this post in threaded view
|

Re: Entry / TextBuffer that does not listen to accelerators?

muppet-6

Stephan Brunner said:
> I am not able to enter any 'u' into the Entry.
>
>> The reason this should work is, that the active widget gets the input.
>> If the widget does not handle it, it passes it on to it's parent. And so
>> on up to the window, which handles the accels.

Not quite; key events are delivered first to the toplevel window (an X quirk),
and propagated from there to child widgets.  gtk_window_key_press_event()
looks like this:

  static gint
  gtk_window_key_press_event (GtkWidget   *widget,
     GdkEventKey *event)
  {
    GtkWindow *window = GTK_WINDOW (widget);
    gboolean handled = FALSE;

    /* handle mnemonics and accelerators */
    if (!handled)
      handled = gtk_window_activate_key (window, event);

    /* handle focus widget key events */
    if (!handled)
      handled = gtk_window_propagate_key_event (window, event);

    /* Chain up, invokes binding set */
    if (!handled)
      handled = GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);

    return handled;
  }

Which means that window-wide mnemonics and accelerators take precedence over
per-widget keystrokes.  And, if you think about it, it *has* to work that way,
or something like a TextView would eat all of the keys that are supposed to be
menu accelerators.



> Reading this, I wonder even more why I can't get it to work the way I want to.
> Is there some subtle mistake or usage-error in my code?

No.  Your key is handled by the UI manager, which means, by the logic above,
it takes precedence over the widget's handling of the event.  In fact, your
Entry or TextView never even gets the key-press-event.


This sort of clash is the reason for using modifiers on accelerators in the
first place.  The problem would be easily solved by making your accel be
"<Ctrl>U" instead of just "U"... but since that's not what you asked...

In this instance you need to have the widget get the first crack at the
unadorned key.  The guys on gtk-app-devel-list or in #gtk+ will probably know
better than i, but here's what i would try:

a) Don't put the accelerator in the UIManager descriptions, but handle it by
hand in key_press_event on the toplevel window, connected by
signal_connect_after().  By removing it from the UI manager, it doesn't get
handled in the accelerator table, and by having it connected _after, your
handler runs *after* the key has been propagated to the other widgets, which
gives the TextView and Entry first crack at it.  The drawback is that you lose
the helpful accelerator hint in the menu.

To try this out, change

     [ "u_action",  undef,    "Do nothing", "u", undef, \&u_action ]

to

     [ "u_action",  undef,    "Do nothing", undef, undef, undef ]

and add

  my $button = Gtk2::Button->new('focus me and press u');
  $Top{vbox}->pack_start($button, 0, 0, 0);
  $Top{window}->signal_connect (key_press_event => sub {
      my ($widget, $event) = @_;
      use Gtk2::Gdk::Keysyms;
      if ($event->keyval == $Gtk2::Gdk::Keysyms{u}) {
          u_action ();
          return 1;
      }
      return 0;
  });

just before your $Top{window}->show_all call.


b) Leave the accelerator in the UIManager, and add special code in your action
handler to route the event as necessary.

  sub u_action {
      # if the key focus is currently on the toplevel window, we may
      # need to do further key routing:
      my $widget = Gtk2->get_event_widget (Gtk2->get_current_event);
      if ($widget->isa ('Gtk2::Window')) {
          # check the type of the widget that currently has focus:
          my $focus_widget = $widget->get_focus;
          if ($focus_widget &&
              ($focus_widget->isa ('Gtk2::Editable') ||
               $focus_widget->isa ('Gtk2::TextView'))) {
              # he needs this event more than we do.
              $focus_widget->event (Gtk2->get_current_event);
              return;
          }
      }
      $count++;
      print "$count: This is sub u_action...\n";
  }

I don't like this approach very much because it feels fragile, and requires
placing this behavior in all of the handlers for unadorned accels, rather than
just connecting them in a different place.  On the other hand, you get your
handlers through UIManager, which is nice.


--
muppet <scott at asofyet dot org>

_______________________________________________
gtk-perl-list mailing list
[hidden email]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list
Reply | Threaded
Open this post in threaded view
|

Re: Entry / TextBuffer that does not listen to accelerators?

muppet-6

muppet said:
> The guys on gtk-app-devel-list or in #gtk+ will probably know
> better than i, but here's what i would try:
>
> a) [...]
>
> b) [...]

In case that wasn't really clear, that was option a) *or* option b).  Don't do
both.


>   sub u_action {
>       # if the key focus is currently on the toplevel window, we may
>       # need to do further key routing:
>       my $widget = Gtk2->get_event_widget (Gtk2->get_current_event);

I forgot that Gtk2::Actions can be triggered from anywhere; if the event has
fired from a toolbar button, the current event won't be a key event.  Of
course, it's unlikely that the event widget would be the toplevel window in
that case, so this will probably work, but it's worth noting that this code
still may have plenty  of holes in it.

>       if ($widget->isa ('Gtk2::Window')) {


--
muppet <scott at asofyet dot org>

_______________________________________________
gtk-perl-list mailing list
[hidden email]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list
Reply | Threaded
Open this post in threaded view
|

Re: Entry / TextBuffer that does not listen to accelerators?

Jan Hudec
In reply to this post by muppet-6
On Fri, Sep 30, 2005 at 11:04:54 -0400, muppet wrote:

>
> Stephan Brunner said:
> > I am not able to enter any 'u' into the Entry.
> >
> >> The reason this should work is, that the active widget gets the input.
> >> If the widget does not handle it, it passes it on to it's parent. And so
> >> on up to the window, which handles the accels.
>
> Not quite; key events are delivered first to the toplevel window (an X quirk),
> and propagated from there to child widgets.  gtk_window_key_press_event()
> looks like this:
Hm. I thought that it was saner than this. But yes, actually the toplevel
window is above it's children, not below in X.

>   static gint
>   gtk_window_key_press_event (GtkWidget   *widget,
>      GdkEventKey *event)
>   {
>     GtkWindow *window = GTK_WINDOW (widget);
>     gboolean handled = FALSE;
>
>     /* handle mnemonics and accelerators */
>     if (!handled)
>       handled = gtk_window_activate_key (window, event);
>
>     /* handle focus widget key events */
>     if (!handled)
>       handled = gtk_window_propagate_key_event (window, event);
>
>     /* Chain up, invokes binding set */
>     if (!handled)
>       handled = GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
>
>     return handled;
>   }
>
> Which means that window-wide mnemonics and accelerators take precedence over
> per-widget keystrokes.  And, if you think about it, it *has* to work that way,
> or something like a TextView would eat all of the keys that are supposed to be
> menu accelerators.
No, it does not. I would even call it a bug. The event signal returns
a boolean, whether it handled the keypress or not. The logic should be
reversed. It should first propagate the event to the children and only handle
it if they did not handle it already. It would require each widget to
truthfuly return whether it handled the keypress, but it would behave saner.

By the way, how does it handle the tab switching? If you have an entry in the
tab order, you can tab into it, but you can't tab out, as the tab gets
inserted. That means that the container handles the tab only if it's child
did not. Or does the child handle it? That would sound mad.

--
                                                 Jan 'Bulb' Hudec <[hidden email]>

_______________________________________________
gtk-perl-list mailing list
[hidden email]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list

signature.asc (196 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Entry / TextBuffer that does not listen to accelerators?

Stephan Brunner
In reply to this post by muppet-6
Am Freitag 30 September 2005 17:04 schrieb muppet:

> Stephan Brunner said:
> > I am not able to enter any 'u' into the Entry.
> >
> >> The reason this should work is, that the active widget gets the input.
> >> If the widget does not handle it, it passes it on to it's parent. And so
> >> on up to the window, which handles the accels.
>
> Not quite; key events are delivered first to the toplevel window (an X
> quirk), and propagated from there to child widgets.
> gtk_window_key_press_event() looks like this:

[code]

> Which means that window-wide mnemonics and accelerators take precedence
> over per-widget keystrokes.  And, if you think about it, it *has* to work
> that way, or something like a TextView would eat all of the keys that are
> supposed to be menu accelerators.
>
> > Reading this, I wonder even more why I can't get it to work the way I
> > want to. Is there some subtle mistake or usage-error in my code?
>
> No.  Your key is handled by the UI manager, which means, by the logic
> above, it takes precedence over the widget's handling of the event.  In
> fact, your Entry or TextView never even gets the key-press-event.
>
> This sort of clash is the reason for using modifiers on accelerators in the
> first place.  The problem would be easily solved by making your accel be
> "<Ctrl>U" instead of just "U"... but since that's not what you asked...
>
> In this instance you need to have the widget get the first crack at the
> unadorned key.  The guys on gtk-app-devel-list or in #gtk+ will probably
> know better than i, but here's what i would try:
>
> a) Don't put the accelerator in the UIManager descriptions, but handle it
> by hand in key_press_event on the toplevel window, connected by
> signal_connect_after().  By removing it from the UI manager, it doesn't get
> handled in the accelerator table, and by having it connected _after, your
> handler runs *after* the key has been propagated to the other widgets,
> which gives the TextView and Entry first crack at it.  The drawback is that
> you lose the helpful accelerator hint in the menu.

Hmm, since I want to provide some sort of "plug-in" mechanism for the
application, I'm quite happy about the UI manager and its convenient way of
extending the user interface. Not only would I lose the accelerator hint, but
also the possibility to automatically "register" a plug-in action with its
accelerator.

[code]

> b) Leave the accelerator in the UIManager, and add special code in your
> action handler to route the event as necessary.
>
>   sub u_action {
>       # if the key focus is currently on the toplevel window, we may
>       # need to do further key routing:
>       my $widget = Gtk2->get_event_widget (Gtk2->get_current_event);
>       if ($widget->isa ('Gtk2::Window')) {
>           # check the type of the widget that currently has focus:
>           my $focus_widget = $widget->get_focus;
>           if ($focus_widget &&
>               ($focus_widget->isa ('Gtk2::Editable') ||
>                $focus_widget->isa ('Gtk2::TextView'))) {
>               # he needs this event more than we do.
>               $focus_widget->event (Gtk2->get_current_event);
>               return;
>           }
>       }
>       $count++;
>       print "$count: This is sub u_action...\n";
>   }
>
> I don't like this approach very much because it feels fragile, and requires
> placing this behavior in all of the handlers for unadorned accels, rather
> than just connecting them in a different place.  On the other hand, you get
> your handlers through UIManager, which is nice.

Yeah, even though it's clear what happens here, it somehow doesn't feel
"complete" enough.

Well, there doesn't seem to be a "straight forward" solution like
$widget->set_listen_to_accels(FALSE), so I will have to think twice about
going for <Ctrl>U... I just like the VIM-approach more than the Emacs one;-)

Thank you very much for the deep insight - at least I now understand why it's
not working "out of the box"!

Stephan
 
_______________________________________________
gtk-perl-list mailing list
[hidden email]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list
Reply | Threaded
Open this post in threaded view
|

Re: Entry / TextBuffer that does not listen to accelerators?

muppet-6
In reply to this post by Jan Hudec

Jan Hudec said:
> On Fri, Sep 30, 2005 at 11:04:54 -0400, muppet wrote:
>> Which means that window-wide mnemonics and accelerators take precedence
>> over per-widget keystrokes.  And, if you think about it, it *has* to
>> work that way, or something like a TextView would eat all of the keys
>> that are supposed to be menu accelerators.
>
> No, it does not. I would even call it a bug. The event signal returns
> a boolean, whether it handled the keypress or not.

Well, it made sense at the time i'd figured out what it was doing.  :-/

At this point, it's way too late to be fixed in gtk+ 2.x.  Perhaps propose a
better solution to gtk-devel list for 3.0?


> By the way, how does it handle the tab switching? If you have an entry in the
> tab order, you can tab into it, but you can't tab out, as the tab gets
> inserted. That means that the container handles the tab only if it's child
> did not. Or does the child handle it? That would sound mad.

It's complicated.  I've poked through it in the past and decided not to go back.

--
muppet <scott at asofyet dot org>

_______________________________________________
gtk-perl-list mailing list
[hidden email]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list
Reply | Threaded
Open this post in threaded view
|

Re: Entry / TextBuffer that does not listen to accelerators?

justin+gnome
muppet et al,

Is this the same situation in which we discussed the use of the
can_activate_accel signal?

http://mail.gnome.org/archives/gtk-perl-list/2005-July/msg00223.html


Justin
_______________________________________________
gtk-perl-list mailing list
[hidden email]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list