Xfce Foundation Classes
 « Index | Adding a statusbar Building a GNU autotools project »

Chapter 7: Adding a client area and context menu


A main window should have a client area that is separate from its menubar, toolbar and statusbar to ensure the layout of these important widgets remains constant. In chapter 4 we added a vertical box called main_vbox to XfcApp. At the start of main_vbox we packed a menubar and then a toolbar. In chapter six we added a statusbar to main_vbox but packed it at the end. The next call we make to pack_start() will pack a widget so that it appears after the toolbar but before the statusbar. If you were only adding one widget, such as the text editing widget Gtk::TextView, you would add it directly to main_vbox. If you were going to add several widgets you would first add them to another packing box and then add this box to main_vbox.

In this chapter we will add a vertical box to main_vbox to use as the client area but first we need to think about the context menu. A context menu should pop up whenever the right mouse button is pressed inside the client area. A context menu should not pop up when the right mouse button is pressed inside the menubar, toolbar or status bar unless it belongs to that widget. This is one reason to add a separate packing box for the client area. It makes implementing our pop-up context menu that much easier.

Widgets that have no window do not receive signals. Packing boxes are 'no window' widgets so they cannot receive signals. If  we want to implement a pop-up context menu we have to use a widget that can receive mouse button events. Gtk::EventBox is just such a widget. An event box can receive any signal you want. To implement a context menu we need add a Gtk::EventBox to main_vbox, and then add our client box to this event box.

First, to the XfcApp header file we need to add an include statement for the Gtk::Menu header file:

#include <xfc/gtk/menu.hh>

and to XfcApp class declaration we need to add a signal handler that will respond to "button_press_event" signals:

bool on_button_press(const Gdk::EventButton& event, Gtk::Menu *menu);
 
Whenever the right mouse is pressed inside our client area on_button_press() gets called to display a context menu.

Next, in the XfcApp constructor we need to add the following code which adds an event box to the main_vbox widget:

#include <xfc/gtk/eventbox.hh>

Gtk::EventBox *eventbox = new Gtk::EventBox;
main_vbox->pack_start(*eventbox);
eventbox->show();

eventbox->set_events(Gdk::BUTTON_PRESS_MASK);
   
Gtk::VBox *client_vbox = new Gtk::VBox;
eventbox->add(*client_vbox);
client_vbox->show();

The first three lines of code creates an instance of Gtk::Eventbox and then adds it to main_vbox. An event box is dervied from Gtk::Bin so it can only have one child. The fourth line of code calls set_events() to tell the event box we want to receive "button_press_event" signals. The set_events() function is declared in Gtk::Widget. The last three lines of code creates an instance of Gtk::VBox and adds it to the event box.

For the purposes of this example we will use the menubar's file menu as the pop up context menu but in reality you would create a custom menu to suit your application.

Gtk::MenuItem *item = static_cast<Gtk::MenuItem*>(manager->get_widget("/MenuBar/FileMenu"));
Gtk::Menu *menu = item->get_submenu();
eventbox->signal_button_press_event().connect(sigc::bind(sigc::mem_fun(this, &XfcApp::on_button_press), menu));


The first line of code obtains a pointer to the FileMenu menu item from its Gtk::UIManager path. The second line of code gets a pointer to the File submenu. The last line of code connects the event box's "button_press_event" signal to our on_button_press() signal handler so that it gets called each time a mouse button is pressed inside the event box. As seen before, we use the sigc::bind() function to bind the file menu pointer so that it gets passed as the last argument to our on_button_press() handler when a "button_press_event" signal is emitted.

The code for on_button_press() looks like this:

bool
XfcApp::on_button_press(const Gdk::EventButton& event, Gtk::Menu *menu)
{
    if (event.button() == 3)
        menu->popup(event.button(), event.time());
    return true;
}


Gdk::EventButton is a data object that passes data specific to mouse button press events. The button() function returns 1 if the left button was pressed, 2 if the middle button was pressed and 3 if the right button was pressed. A context menu should only be displayed when the right (secondary) mouse button is pressed. on_button_press() checks to see if the button pressed was the right button, and if so displays the context menu. GDK event handlers should return true if the event was handled.

Well that's it. Now you have a fully functional main window with an action-based menubar and toolbar and a statusbar. The statusbar displays a progress bar and the currently selected menu item's tooltip during menu selection. The main window has a client area that can parent one or more widgets and will display a pop up context menu if the right mouse button is pressed.

The updated XfcApp header file <xfcapp.hh> should now look like this:

#include <xfc/main.hh>
#include <xfc/gtk/menu.hh>
#include <xfc/gtk/window.hh>
#include <xfc/gtk/uimanager.hh>
#include "statusbar.hh"

using namespace Xfc;

class XfcApp : public Gtk::Window
{
    Pointer<Gtk::ActionGroup> action_group;
    Pointer<Gtk::UIManager> manager;
    
    Statusbar *statusbar_;
    
    void add_actions();
    void install_menu_hints(Gtk::Action& action, Gtk::Widget& widget);
   
bool on_button_press(const Gdk::EventButton& event, Gtk::Menu *menu);

public:
    XfcApp();
    virtual ~XfcApp();
    
    void on_file_new();
    void on_file_open();
    void on_file_save();
    void on_file_save_as();
    void on_file_quit();
    void on_edit_cut();
    void on_edit_copy();
    void on_edit_paste();
    void on_edit_clear();
    void on_edit_preferences();
    void on_help_about();
};


and the updated source file <xfcapp.cc> looks like this:

#include "xfcapp.hh"
#include "xfcapp.ui"
#include <xfc/gtk/accelgroup.hh>
#include <xfc/gtk/box.hh>
#include <xfc/gtk/eventbox.hh>
#include <xfc/gtk/menubar.hh>
#include <xfc/gtk/menuitem.hh>
#include <xfc/gtk/stock.hh>
#include <xfc/gtk/toolbar.hh>
#include <xfc/glib/error.hh>
#include <gconf/gconf-client.h>
#include <iostream>

XfcApp::XfcApp()
{
    // Set the window title and default size    
    set_title("XfcApp");
    set_default_size(400, 300);
    
    // Create the action group and add the actions
    action_group = new Gtk::ActionGroup("XfcAppActions");
    add_actions();    
    
    // Create the user interface manager and insert the action group    
    manager = new Gtk::UIManager;
    manager->insert_action_group(*action_group);
    manager->signal_connect_proxy().connect(sigc::mem_fun(this, &XfcApp::install_menu_hints));
    
    // Associate the user interface manager's AccelGroup with the window
    add_accel_group(manager->get_accel_group());
    
    // Create main vertical box and add to window    
    Gtk::VBox *main_vbox = new Gtk::VBox;
    add(*main_vbox);
    
    // Create custom statusbar (before loading XML description) and pack it at the end of main_vbox    
    statusbar = new Statusbar(true);
    main_vbox->pack_end(*statusbar, false);
    statusbar->show();
    
    // Load XML description of the menus and toolbar from a definition string.
    G::Error error;    
    if (!manager->add_ui_from_string(ui_info, -1, &error))
    {
        std::cout << "building menus and toolbar failed: << " << error.message() << std::endl;
    }

    // Get a pointer to the menubar and pack it into main_vbox    
    Gtk::Widget *menubar = manager->get_widget("/MenuBar");
    main_vbox->pack_start(*menubar, false);    
    menubar->show();
    
    // Get a pointer to the toolbar and pack it into main_vbox    
    Gtk::Toolbar *toolbar = static_cast<Gtk::Toolbar*>(manager->get_widget("/ToolBar"));
    toolbar->set_tooltips(true);
    main_vbox->pack_start(*toolbar, false);
    toolbar->show();
    
    // Use the GNOME value for 'toolbar_style' to place the progress bar.
    GConfClient *client = gconf_client_get_default();
    String text = gconf_client_get_string(client, "/desktop/gnome/interface/toolbar_style", 0);
    Gtk::ToolbarStyle toolbar_style;
    if (text.compare("text") == 0)
        toolbar_style = Gtk::TOOLBAR_TEXT;
    else if (text.compare("both") == 0)
        toolbar_style = Gtk::TOOLBAR_BOTH;
    else if (text.compare("both_horiz") == 0)
        toolbar_style = Gtk::TOOLBAR_BOTH_HORIZ;
    else
        toolbar_style = Gtk::TOOLBAR_ICONS;
    toolbar->set_style(toolbar_style);

    // Boxes don't receive button events so use an eventbox first and add all other widgets to this.
    Gtk::EventBox *eventbox = new Gtk::EventBox;
    main_vbox->pack_start(*eventbox);
    eventbox->show();

    // Set the events the eventbox is to receive. These can be one or more values from Gdk::EventMask.
    eventbox->set_events(Gdk::BUTTON_PRESS_MASK);
    
    // Add a vertical packing box to eventbox for the client area.
    Gtk::VBox *client_vbox = new Gtk::VBox;
    eventbox->add(*client_vbox);
    client_vbox->show();
    
    // As an example bind the file menu to the button_press event and use it as the popup menu.
    Gtk::MenuItem *item = static_cast<Gtk::MenuItem*>(manager->get_widget("/MenuBar/FileMenu"));
    Gtk::Menu *menu = item->get_submenu();
    eventbox->signal_button_press_event().connect(sigc::bind(sigc::mem_fun(this, &XfcApp::on_button_press), menu));

    // Show main_vbox so it's visible
    main_vbox->show();
    
    // As an example, set the progress bar to pulse   
    statusbar_->begin_progress(100, true);
}

XfcApp::~XfcApp()
{
    statusbar_->end_progress();
}

void
XfcApp::add_actions()
{
    using namespace Gtk;
    using namespace sigc;

    action_group->add("FileMenu", "_File");
    action_group->add("EditMenu", "_Edit");
    action_group->add("HelpMenu", "_Help");

    Action *action = action_group->add("New", "_New", StockId::NEW, AccelKey("<control>N"), "Create a new file");    
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_file_new));
    action = action_group->add("Open", "_Open", StockId::OPEN, AccelKey("<control>O"), "Open a file");    
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_file_open));
    action = action_group->add("Save", "_Save", StockId::SAVE, AccelKey("<control>S"), "Save current file");    
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_file_save));
    action = action_group->add("SaveAs", "Save _As...", StockId::SAVE, "Save to a file");    
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_file_save_as));
    action = action_group->add("Quit", "_Quit", StockId::QUIT, AccelKey("<control>Q"), "Quit");    
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_file_quit));
    action = action_group->add("Cut", "C_ut", StockId::CUT, AccelKey("<control>X"), "Cut the selection");    
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_edit_cut));
    action = action_group->add("Copy", "_Copy", StockId::COPY, AccelKey("<control>C"), "Copy the selection");
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_edit_copy));
    action = action_group->add("Paste", "_Paste", StockId::PASTE, AccelKey("<control>V"), "Paste the clipboard");
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_edit_paste));
    action = action_group->add("Clear", "C_lear", StockId::CLEAR, "Clear the selection");
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_edit_clear));
    action = action_group->add("Preferences", "Prefere_nces", StockId::PREFERENCES, "Configure the application");
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_edit_preferences));
    action = action_group->add("About", "_About", AccelKey("<control>A"), "About");    
    action->signal_activate().connect(mem_fun(this, &XfcApp::on_help_about));
}

void
XfcApp::install_menu_hints(Gtk::Action& action, Gtk::Widget& widget)
{
    using namespace sigc;
        
    if (!widget.is_a(GTK_TYPE_MENU_ITEM))
        return;
        
    Gtk::MenuItem& item = static_cast<Gtk::MenuItem&>(widget);
    String tooltip = action.property_tooltip();
    item.signal_select().connect(bind(mem_fun(statusbar, &Statusbar::push), tooltip));
    item.signal_deselect().connect(mem_fun(statusbar, &Statusbar::pop));
}

bool
XfcApp::on_button_press(const Gdk::EventButton& event, Gtk::Menu *menu)
{
    if (event.button() == 3)
        menu->popup(event.button(), event.time());
    return true;
}

void
XfcApp::on_file_new()
{
    statusbar->progress_bar()->hide();
    std::cout << "You activated action: New" << std::endl;
}

void
XfcApp::on_file_open()
{
    statusbar->progress_bar()->show();
    std::cout << "You activated action: Open" << std::endl;
}

void
XfcApp::on_file_save()
{
    std::cout << "You activated action: Save" << std::endl;
}

void
XfcApp::on_file_save_as()
{
    std::cout << "You activated action: SaveAs" << std::endl;
}

void
XfcApp::on_file_quit()
{
    dispose();
}

void
XfcApp::on_edit_cut()
{
    std::cout << "You activated action: Cut" << std::endl;
}

void
XfcApp::on_edit_copy()
{
    std::cout << "You activated action: Copy" << std::endl;
}

void
XfcApp::on_edit_paste()
{
    std::cout << "You activated action: Paste" << std::endl;
}

void
XfcApp::on_edit_clear()
{
    std::cout << "You activated action: Clear" << std::endl;
}

void
XfcApp::on_edit_preferences()
{
    std::cout << "You activated action: Preferences" << std::endl;
}

void
XfcApp::on_help_about()
{
    std::cout << "You activated action: About" << std::endl;
}

int main (int argc, char *argv[])
{
    using namespace Main;

    init(&argc, &argv);

    XfcApp window;
    window.signal_destroy().connect(sigc::ptr_fun(&Xfc::Main::quit));
    window.show();

    run();
    return 0;
}

Compiling XfcApp

If you compiled and installed XFC yourself, you will find the source code for this version of XfcApp in the <examples/tutorial/chapter07> source directory along with a Makefile. If XFC came pre-installed, or you installed it from an RPM package, you will find the source code in the </usr/share/doc/xfcui-X.X/examples/tutorial/chapter07> subdirectory. In this case you will have to create the Makefile yourself (replace X.X with the version number of the libXFCui library you have installed).

To create a Makefile for XfcApp, add the following lines to a new text file and save it using the name "Makefile":

CC = g++

CFLAGS = -Wall -O2

xfcapp: xfcapp.cc xfcapp.hh xfcapp.ui statusbar.cc statusbar.hh
    $(CC) xfcapp.cc statusbar.cc -o xfcapp `pkg-config xfcui-X.X gconf-2.0 --cflags --libs`

clean:
    rm -f *.o xfcapp


If you cut and paste these lines make sure the whitespace before $(CC) and rm is a tab character. Note the addition of 'gconf-2.0' to the pkg-config command. This is needed because XfcApp needs to retrieve the current value of GNOME "toolbar_style" and "status_bar_meter_on_right" from the GConf database. When you compile and run this program you will see the following window appear:



In the next chapter we will turn the XfcApp program into a fully compliant GNU autotools project.


Copyright © 2004-2005 The XFC Development Team Top
XFC 4.4
Index