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));
// 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->get_progress_bar()->hide();
std::cout << "You activated action: New"
<< std::endl;
}
void
XfcApp::on_file_open()
{
statusbar->get_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
<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/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. 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
|
|