PHP-GTK2 Newsletter

PHP-GTK2 Tips & Techniques
FREE Newsletter
by kksou



Sample Code 277: How to set up an application to run in the system tray using GtkStatusIcon - Part 4 - display popup menu on right click?
Written by kksou   
Tuesday, 26 June 2007
Problem

In this Part 4, I will show you how to popup a menu when the user clicks on the system tray icon with the right mouse button as shown below:

How to set up an application to run in the system tray using GtkStatusIcon - Part 4 - display popup menu on right click?


Solution

First make sure you have read the article How to set up an application to run in the system tray using GtkStatusIcon - Part 2 - display GTK window on left click?

Once you understand that a GtkStatusIcon is just a unique GTK "widget" that you can place outside your GtkWindow in the system tray icon, you will find that everything becomes simple and straightforward.

  1. Register the signal 'popup-menu' to track right mouse click on the system tray icon.
  2. Use exactly the same technique as outlined in How to display context sensitive popup menu with right mouse click in GtkTreeView? to popup the menu.

Important Note: This only works for PHP-GTK2 compliled with gtk+ v2.10 and above. If you are using an older version, for linux, you may follow the step-by-step instructions to recompile php-gtk2 with gtk+ v2.10. For windows, please refer to How to install php gtk2 on windows? You may also want to take a look here to see some of the new exciting PHP-GTK2 Functionalities.


Sample Code
1   
2   
3   
4   
5   
6   
7   
8   
9   
10   
11   
12   
13   
14   
15   
16   
17   
18   
19   
20   
21   
22   
23   
24   
25   
26   
27   
28   
29   
30   
31   
32   
33   
34   
35   
36   
37   
38   
39   
40   
41   
42   
43   
44   
45   
46   
47   
48   
49   
50   
51   
52   
53   
54   
55   
56   
57   
58   
59   
60   
61   
62   
63   
64   
65   
66   
67   
68   
69   
70   
71   
72   
73   
74   
75   
76   
77   
78   
79   
80   
81   
82   
83   
84   
85   
86   
87   
88   
<?php
// setup GTK main application
$window = new GtkWindow();
$window->set_title($argv[0]);
$window->set_size_request(400, 300);
$window->connect_simple('destroy', array('Gtk','main_quit'));
$window->add($vbox = new GtkVBox());

$vbox->pack_start($hbox = new GtkHBox(), 0, 0);
$hbox->set_size_request(-1, 2);

// Create the top textview to display the conversation
$buffer1 = new GtkTextBuffer();
$view1 = new GtkTextView();
$view1->set_buffer($buffer1);
$view1->set_editable(false);
$view1->modify_font(new PangoFontDescription("Arial 9"));
$view1->set_wrap_mode(Gtk::WRAP_WORD);

$scrolled_win1 = new GtkScrolledWindow();
$scrolled_win1->set_policy(Gtk::POLICY_AUTOMATIC,Gtk::POLICY_AUTOMATIC);
$frame1 = new GtkFrame();
$frame1->add($scrolled_win1);
$vbox->pack_start($frame1, 0, 0);
$scrolled_win1->add($view1);
$scrolled_win1->set_size_request(400,200);

$vbox->pack_start($hbox = new GtkHBox(), 0, 0);
$hbox->set_size_request(-1, 1);

// Create the bottom textview to type your message
$buffer2 = new GtkTextBuffer();
$buffer2->set_text('type your message here');
$view2 = new GtkTextView();
$view2->set_buffer($buffer2);
$view2->modify_font(new PangoFontDescription("Arial 9"));
$view2->set_wrap_mode(Gtk::WRAP_WORD);

$scrolled_win2 = new GtkScrolledWindow();
$scrolled_win2->set_policy(Gtk::POLICY_AUTOMATIC,Gtk::POLICY_AUTOMATIC);
$frame2 = new GtkFrame();
$frame2->add($scrolled_win2);
$vbox->pack_start($frame2, 0, 0);
$scrolled_win2->add($view2);
$view2->connect('key-press-event', 'on_key_press',
    $view1, $buffer1, $view2, $buffer2);
$scrolled_win2->set_size_request(400,80);
$view2->grab_focus();

// setup system tray icon
$statusicon = new GtkStatusIcon();
$statusicon->set_from_stock(Gtk::STOCK_NETWORK);
$statusicon->set_tooltip('Left click to launch PHP-GTK Messenger');

$app_status = 0;
$statusicon->connect('activate', 'on_activate');
$statusicon->connect('popup-menu', 'on_popup_menu'); // note 1

$window->hide_all();
Gtk::main();

function on_key_press($widget, $event, $view1, $buffer1, $view2, $buffer2) {
    if ($event->keyval==Gdk::KEY_Return) {
        if ($event->state & Gdk::SHIFT_MASK) return false;
        $input = $buffer2->get_text($buffer2->get_start_iter(),
            $buffer2->get_end_iter());
        $iter = $buffer1->get_end_iter();
        $buffer1->insert($iter, "You say: $input\n\n" );
        $view1->scroll_to_mark($buffer1->get_insert(), 0);
        $buffer2->set_text('');
        return true;
    } else {
        return false;
    }
}

function on_activate($statusicon) {
    global $window, $app_status, $view2;
    if ($app_status) {
        $statusicon->set_tooltip('Left click to launch PHP-GTK Messenger');
        $window->hide_all();
        $app_status = 0;
    } else {
        $statusicon->set_tooltip('Left click to hide PHP-GTK Messenger');
        $window->show_all();
        $view2->grab_focus();
        $app_status = 1;
    }
  • Note that this is only 70% of the sample code. You have to be a registered member to see the entire sample code. Please login or register.
  • Registration is free and immediate.
  • Have some doubt about the registration? Please read this forum article.
Explanation

The sample code above is an extension of How to set up an application to run in the system tray using GtkStatusIcon - Part 3 - display GTK app on left click?

We also make use of the technique as outlined in How to display context sensitive popup menu with right mouse click in GtkTreeView? to popup the menu on right mouse click.

What's new here:

  1. Register the signal 'popup-menu' to track right mouse click on the system tray icon.
  2. The right-menu definition.
  3. Popup the menu.
  4. Process the menu items.

Note
  1. I've only tested GtkStatusIcon on winxp. If you're on linux or mac, please let me know if the code runs ok on your machine.
  2. On my machine running winxp, I got the following warning message:
    (php2.exe:xxxx): GLib-WARNING **: g_main_context_prepare() called recursively fr om within a source's check() or prepare() member.
    I've tried many different applications using GtkStatusIcon. All seems to run ok. So I'm not sure if this is a bug or not. Does it occur on your machine too?

Related Links

User reviews   Average user ratings:    4.5   (from 8 users)
  1. Waschman from Sweden
    June 29, 2007 8:47pm

    It works and it's easy to understand the code. But I also get the same errormessages you mentioned in the article. Don't know why!

  2. shin
    February 20, 2008 1:24pm
    =D not to meeeeee

    Ok. im using winxp
    and i try your source ... ^^' i got no problem

    /* Quijote Shin ^^'
    GOD BLESS YOU ALL
    */

  3. Kevin
    March 07, 2008 10:09am
    Menu does not go away on 'click-out'

    I have everything working (show-hide application on tray-icon-click, and show context-menu on right-tray-icon-click) but when I click away the context-menu does not go away (unless I click on the actual application window)...it makes sense to click on the application window for a regular context-menu, but not for the status icon context-menu. My work around is to hide the context-menu after some seconds when the cursor leaves the icon or menu, but I would rather have it work correctly (like other applications running in the status tray), any help would be greatly appreciated.

    This is what I have so far...

  4. Kevin
    March 07, 2008 10:21am
    Current working code...referenced above

    class TrayIcon extends GtkStatusIcon {

    public function __construct ( $window ) { // pass application window on instantiate
    parent::__construct();
    $this->window = $window;
    $this->tray_state;
    $this->connect( 'activate', array( $this, 'toggle_tray' ) );
    $this->window->connect( 'delete_event', array( $this, 'window_state' ) );
    $this->window->connect( 'window_state_event', array( $this, 'window_state' ) );
    }

    public function set_menu ( $menu ) { // pass context-menu to tray object instance
    $this->menu = $menu;
    $this->menu->connect( 'enter-notify-event', array( $this, 'reset_menu' ) );
    $this->menu->connect( 'leave-notify-event', array( $this, 'reset_menu' ) );
    $this->connect_simple( 'popup-menu', array( $this, 'open_menu' ) );
    $this->menu->show_all();
    }

    public function reset_menu ( $menu, $event ) {
    $menu_timeout = 0.75; // seconds float, to hide menu
    switch ( $event->type ) {
    case Gdk::LEAVE_NOTIFY: // event: 11
    $reset = $menu_timeout * 1000;
    $this->timeout = Gtk::timeout_add( $reset, array( $this, 'close_menu' ) );
    break;
    case Gdk::ENTER_NOTIFY: // event: 10
    if ( $this->timeout ) {
    Gtk::timeout_remove( $this->timeout );
    }
    break;
    default:
    }
    }

    public function open_menu () {
    $this->menu->popup();
    $this->menu_state = true;
    $this->menu->window->set_cursor( new GdkCursor( Gdk::LEFT_PTR ) ); // otherwise I get the 'working-hourglass' cursor
    }

    public function close_menu () {
    $this->menu->popdown();
    $this->menu_state = false;
    return false; // required for timeout
    }

    public function toggle_tray () {
    if ( $this->menu_state ) {
    return false; // close menu instead
    }
    switch ( $this->tray_state ) {
    case true:
    $this->release_from_tray();
    break;
    case false:
    $this->call_to_tray();
    break;
    default:
    }
    }

    public function release_from_tray () {
    $this->window->show_all();
    $this->tray_state = false;
    }

    public function call_to_tray () {
    $this->window->hide_all();
    $this->tray_state = true;
    }

    public function window_state ( $window, $event ) {
    $window_state = $event->new_window_state;
    if ( $event->type == Gdk::DELETE ) { // event: 0
    // option to call app to tray when you close the app //
    // $this->call_to_tray();
    // return true; // block further signals
    }
    if ( $window_state == 2 || $window_state == 6 ) {
    $this->call_to_tray();
    $this->window->deiconify();
    }
    // window event states...
    // http://gtk.php.net/manual/en/gdk.enum.windowstate.php
    // 0: normal, shown, restored
    // 1: system tray
    // 2: minimized-iconified to status bar
    // 3: hidden
    // 4: maximized
    // 6: minimized from maximized state,
    // minimized-iconified to floating
    // $w->set_skip_taskbar_hint(true);
    }

    public function __destruct () {
    //parent::__destruct();
    //$this->window->__destruct();
    //unset( $this );
    //exit(0);
    }
    }

  5. Rouven
    April 02, 2008 7:29am

  6. Rouven
    April 02, 2008 8:48am
    ty Kevin

    i am using this code atm

    ------------------------------------------------
    function on_popup_menu () {
    if ( !$this->menu ) {
    $menu_definition = array( 'show' => '_Show', 'hide' => '_Hide', 'hr1' => '',
    'connect' => '_Connect','disconnect' => '_Disconnect',
    'hr2' => '', 'exit' => '_Exit');
    $this->menu = new GtkMenu();
    $this->menu->connect( 'enter-notify-event', array($this,'reset_menu'));
    $this->menu->connect( 'leave-notify-event', array($this,'reset_menu'));

    foreach($menu_definition as $key => $menuitem_definition) {
    if ($menuitem_definition == '') {
    $this->menu->append(new GtkSeparatorMenuItem());
    } else {
    $menu_item = new GtkMenuItem($menuitem_definition);
    $this->menu->append($menu_item);
    $menu_item->connect('activate', array($this,'on_popup_menu_click'), $key );
    }
    }
    }

    // show the menu
    $this->menu->show_all();
    $this->menu->popup();
    $this->menu->grab_focus();
    }

    public function close_menu () {
    if ( $this->timeout ) {
    Gtk::timeout_remove( $this->timeout );
    }
    if ( $this->menu ) {
    $this->menu->popdown();
    }
    }

    public function reset_menu ( $menu="", $event="" ) {
    $menu_timeout = 0.75; // seconds float, to hide menu
    switch ( $event->type ) {
    case Gdk::LEAVE_NOTIFY: // event: 11
    $reset = $menu_timeout * 1000; // calc milliseconds
    $this->timeout = Gtk::timeout_add( $reset, array( $this, 'close_menu' ) );
    break;
    case Gdk::ENTER_NOTIFY: // event: 11
    if ( $this->timeout ) {
    Gtk::timeout_remove( $this->timeout );
    }
    break;
    default:
    }
    }
    ----------------------------------------------

    it really works kickass like :) and the 0.75 secs is a decent delay for a menu timeout. i hope the timer recreation will not start to reserve a lot of memory (but i guess it wont). killing a menu by destroy() and recreating takes up like 100k for ~10 times which i think is too much.

    I'm having a really little problem now. a horizontal row in the menu gets a "leave" event which might close the menu even if the user is in it.

    mfg RR

  7. Tarvin Colmar
    April 08, 2008 9:05am

  8. Yonny
    May 28, 2008 1:43am

Note: You have to be a registered member to leave a comment. Free registration here.

 
< Prev   Next >

Copyright © 2006-2008. kksou.com. All Rights Reserved