Handling popup menus

With over a hundred different widgets supported, GTK+ makes for a very rich programming environment. However, there is a generally accepted set of "standard" widgets that are most commonly used in applications, one of which is the popup menu.

If you are a long-time shell person who really does not know much about GUIs, then you should understand that popup menus are also known as context-sensitive menus and generally appear close to the mouse pointer when the right mouse button is pressed.

Anyway, here's the next script:

<?php
    function doshutdown() {
        gtk::main_quit();
    }

    function show_popup($event, $menu) {
        if ($event->button == 3)
        $menu->popup(null, null, null, $event->button, $event->time);
    }

    function mnunew_click($new) {
        print "New clicked!\n";
    }

    function mnuopen_click($new) {
        print "Open clicked!\n";
    }

    function mnuexit_click($new) {
        print "Exit clicked!\n\n";
        doshutdown();
    }

    $menu =& new GtkMenu();
    $new =& new GtkMenuItem("New");
    $open =& new GtkMenuItem("Open");
    $sep =& new GtkMenuItem("");
    $sep->set_sensitive(false);
    $exit =& new GtkMenuItem("Exit");

    $menu->append($new);
    $menu->append($open);
    $menu->append($sep);
    $menu->append($exit);
    $menu->show_all();

    $window =& new GtkWindow();
    $window->set_title("Using menus");
    $window->set_default_size(300, 100);
    $window->connect("destroy", "doshutdown");
    $window->add_events(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
    $window->connect_object('button-press-event', 'show_popup', $menu);

    $new->connect("activate", 'mnunew_click');
    $open->connect("activate", 'mnuopen_click');
    $exit->connect("activate", 'mnuexit_click');
    $window->show_all();

    gtk::main();
?>

If you are wondering at the length of that script, do not worry - it is the longest in this article! You already know quite a bit of what goes on in there also, so it is probably not all that fearsome.

Starting at the line $menu =& new GtkMenu(), a new class is introduced: GtkMenu. This widget is solely designed to host a popup menu, but it intertwines cleverly with GtkMenuBar, the widget designed to host the horizontal-style menu bar, and I will discuss that later.

Each GtkMenu contains several GtkMenuItems, and these are created similarly to GtkButtons and GtkLabels in that you passed the string you wish them to display.

Just before creating the last GtkMenuItem, I slipped a blank GtkMenuItem in there that you may not have noticed. This item, upon which I call the set_sensitive() method that is universal to all GTK widgets, is there to act as a separator between New/Open and Exit. Creating a GtkMenuItem with no text results in a blank menu item that may still be selected by users. Calling set_sensitive() on that widget and passing in false disables the widget.

Through the use of the append() method of GtkMenu we add our GtkMenuItems to our popup menu then create and set-up the GtkWindow itself. Two new methods are called here: add_events() and connect_object().

Add_events() is a peculiar but very helpful function that allows you to modify which events a given object captures. In essence, you can make a widget listen to an event it ignores by default. The method takes one parameter (we OR two parameters into one in the example), which is a bit mask of constants from the GdkEventMask list. In the script above, GDK_BUTTON_PRESS_MASK and GDK_BUTTON_RELEASE_MASK are combined into one bit mask before being passed in, which makes the widget calling add_events, our GtkWindow, respond to mouse buttons being pressed and released. In turn, our GtkWindow will emit the signal "button-press-event", which we bind a function to in the next line.

Similar to the connect() method we've been using so far, the connect_object() method also connects signals to functions, with the key difference that the object passed into the handler function is not the object you used to call the method on. Instead, the object passed in is the one you set as parameter three to connect_object().

You might not think this is particularly helpful, but in the example we set $menu as the object to be passed to our show_popup() function. If connect() had been used as opposed to connect_object() we would have had to try to get a handle to $menu, because a pretty useless reference to the GtkWindow would have been passed in. Whilst this can be overcome with custom parameters (see the box out Using Custom Parameters for more information), it is more logical to use connect_object().

So, at the end of the day, we tie our GtkWindow's button-press-event signal to our show_popup function. Note that the first parameter in show_popup() is of the type GdkEvent, which at the time of writing sadly seems entirely undocumented in the PHP-GTK documentation (please correct me if I am wrong). However, it is documented in the GDK developer documentation, albeit in C++: http://developer.gnome.org/doc/API/gdk/gdk-event-structures.html#GDKEVENT.

The particular GdkEvent type we're interested in in this situation is GdkEventButton, documented at http://developer.gnome.org/doc/API/gdk/gdk-event-structures.html#GDKEVENTBUTTON. This event is sent when buttons are clicked and released, which is what we're looking to handle. If your C is weak, never fear - here's a quick breakdown of some of the data included in this event:

  • button; the mouse button press (left = 1, middle = 2, right = 3)

  • time; the time, in milliseconds, that the event occurred

  • x, y; the x and y coordinate of the mouse

  • state; a bit mask of GdkModifierTypes (see the main PHP-GTK documentation) that describes whether Control was held down, etc

  • pressure; generally only used for graphics tablets, this is a floating point value from 0 to 1 describing how "hard" the button was clicked. This defaults to 0.5 for mouse clicks

So, the first line of the function checks which button was pressed to generate the event, and, if it was button 3 (the right mouse button), we call the popup() method of our GtkMenu.

popup() takes a total of five parameters: the first two are generally null as they are only used when tying menus to GtkMenuItems. Parameter three is null in the example, but can be the name of a function to call to return the x and y coordinates at which you wish your menu to appear as an array. So, for example:

function mnupos() {
    return array(50, 200);
}

$menu->popup(null, null, 'mnupos', $event->button, $event->time);

When the third parameter is null, the current mouse coordinates are used, which is usually the desired result. Parameter four is the button that was pressed to generate the event, and finally parameter five is the time the event took place, in milliseconds. As seen above, even though there are five parameters for the method, it is actually quite straightforward to use.

The mnuexit_click() function could have been removed entirely because, as well the fact that multiple functions can be connected to a single signal, multiple signals can be connected to a single function. If there were no special processing to be run when Exit was clicked (in our example we print to the console), then the activate GtkMenuItem signal could have been connected to doshutdown() as well as the GtkWindow destroy signal.

Save this script as gtk3.php and run it as before.

As promised, I want to briefly mention how GtkMenu bar works. A menu created with GtkMenu can be used as seen above, where it is activated in a floating space. It can also be used to provide the contents of a horizontal-style menu bar item, for example "New, Open, Save, Exit" would be the GtkMenu that was attached to the "File" GtkMenuItem of a GtkMenuBar widget. Make sense? If not, here's a quick piece of code to demonstrate:

$mainmenu =& new GtkMenuBar();
$filemenu =& new GtkMenuItem("File");
$mainmenu->append($filemenu);
$filemenuoptions = &new GtkMenu();
$open =& new GtkMenuItem("Open");
$filemenuoptions->append($open);
$save =& new GtkMenuItem("Save");
$filemenuoptions->append($save);
$filemenu->set_submenu($filemenuoptions);

So, a GtkMenuBar is the menu strip along the top of your window. The top level items (e.g.: File, Edit, Document, Bookmarks, etc, in KDE's Kate) are GtkMenuItems, which each contain a GtkMenu of their contents. The "Document" GtkMenuItem in Kate would contain a GtkMenu which itself contained GtkMenuItems for Back, Forward, and any open files.

 

Want to learn PHP 7?

Hacking with PHP has been fully updated for PHP 7, and is now available as a downloadable PDF. Get over 1200 pages of hands-on PHP learning today!

If this was helpful, please take a moment to tell others about Hacking with PHP by tweeting about it!

Next chapter: Advanced GUIs >>

Previous chapter: Multiple Windows

Jump to:

 

Home: Table of Contents

Copyright ©2015 Paul Hudson. Follow me: @twostraws.