Tutorials > Gadgets > Customizing Gadgets > Step 2: Extending a Gadget by Changing its Behavior
Step 2: Extending a Gadget by Changing its Behavior
This step explains how to change the behavior of an existing gadget. You will see how to extend the ColoredTreeGadget developed in Step 1 of this tutorial. You will change the behavior of the tree so that a contextual menu is displayed when the user clicks in the tree with the right mouse button. This contextual menu allows the user to change the color of the item where the user clicked, as shown in the following figure:
This step shows you how to perform the following tasks:
Choosing the Right Way to Modify the Behavior of an Existing Gadget
There are two ways to modify the behavior of an existing gadget:
1. Subclass the gadget and override its handleEvent method.
This is the more logical solution when the behavior change is strongly linked with the kind of gadget it applies to.
2. Create a new interactor that will be set on the gadget.
Creating a new interactor is a good solution when you want to temporarily modify the behavior of an object, or when the new interactor is generic, that is, you can set it on several different kinds of gadgets.
Creating a Generic Interactor
The second solution (creating a new interactor) has been chosen for this tutorial. The behavior to implement in this example (displaying a contextual menu after a right click) is generic—it can be used for any gadget class.
The complete code for the ContextualMenuInteractor class can be found in ctxminter.h and ctxminter.cpp. Here is the description of the class.
Purpose
The purpose of the ContextualMenuInteractor class is to provide a generic interactor that can handle a contextual menu, and that keeps the gadget behavior to which it is connected. For this reason, the ContextualMenuInteractor class should be a subclass of the IlvGadgetInteractor class, that is, the predefined interactor class that is set on every gadget.
class ContextualMenuInteractor
: public IlvGadgetInteractor
{
public:
virtual IlBoolean handleEvent(IlvGraphic* obj,
IlvEvent&,
const IlvTransformer* t);
virtual IlBoolean shouldShowMenu(IlvGraphic*,
IlvEvent&,
const IlvTransformer*) const;
virtual IlvPopupMenu* getMenu(IlvGraphic*,
IlvEvent&,
const IlvTransformer*) const = 0;
};
Each of these methods will now be seen in more detail.
The shouldShowMenu Method
The shouldShowMenu method should return IlvTrue if the interactor needs to display the pop-up menu on the specified event. The default behavior returns IlvTrue when the right button of the mouse is released. This method can be redefined in a subclass to display the pop-up menu on any other event.
IlBoolean
ContextualMenuInteractor::shouldShowMenu(IlvGraphic*,
IlvEvent& event,
const IlvTransformer*) const
{
// The contextual menu is displayed by default when the right button
// is released.
return event.type() == IlvButtonUp && event.button() == IlvRightButton;
}
The getMenu Method
The getMenu method is pure, and therefore needs to be defined in a subclass to return the pop-up menu to display.
virtual IlvPopupMenu* getMenu(IlvGraphic*,
IlvEvent&,
const IlvTransformer*) const = 0;
This method is called by the handleEvent method when the shouldShowMenu method has returned IlvTrue.
The handleEvent Method
Here is the implementation of the handleEvent method:
IlBoolean
ContextualMenuInteractor::handleEvent(IlvGraphic* obj,
IlvEvent& event,
const IlvTransformer* t)
{
// Check that the object is a gadget.
IlvGadget* gadget = accept(obj) ? IL_CAST(IlvGadget*, obj) : 0;
if (gadget && gadget->isActive()) {
// Is it time to display the contextual menu?
if (shouldShowMenu(obj, event, t)) {
// Get the menu.
IlvPopupMenu* menu = getMenu(obj, event, t);
// Show the menu.
if (menu) {
menu->get(IlvPoint(event.gx(), event.gy()),
/* transient */ 0);
return IlvTrue;
}
}
}
// Default behavior of the gadget.
return IlvGadgetInteractor::handleEvent(obj, event, t);
}
Creating a Dedicated Interactor
A subclass of the ContextualMenuInteractor class dedicated to the ColoredTreeGadget class will now be created. This subclass, called ColoredTreeGadgetInteractor, overrides the getMenu method to create a menu where some common colors are available, as shown in the following picture:
When the user selects a color, the color of the item where the mouse was located before the pop-up menu was displayed is changed.
Creating the Menu
Here is the implementation of the getMenu method:
IlvPopupMenu*
ColoredTreeGadgetInteractor::getMenu(IlvGraphic* graphic,
IlvEvent& event,
const IlvTransformer* t) const
{
ColoredTreeGadget* tree = (ColoredTreeGadget*)graphic;
// Find the item located at the event location.
IlvTreeGadgetItem* item = tree->pointToItemLine(IlvPoint(event.x(),
event.y()),
(IlvTransformer*)t);
if (!item)
return 0;
// Create the menu if needed.
static IlvPopupMenu* menu = 0;
if (!menu) {
// Create the pop-up menu.
menu = new IlvPopupMenu(tree->getDisplay());
// Fill it with predefined colors.
AddColor(menu, 0);
AddColor(menu, "yellow");
AddColor(menu, "green");
AddColor(menu, "red");
AddColor(menu, "blue");
AddColor(menu, "gray");
AddColor(menu, "brown");
}
// Set the item that asked for the contextual menu so that the menu
// will be able to use it later.
SetSelectedItem(menu, tree, item);
// Return the menu.
return menu;
}
After retrieving the item located under the mouse by using the IlvTreeGadget::pointToItemLine method, the menu is created with predefined colors by calling the static function AddColor.
Note: The menu is created only once.
Then the menu is connected to the tree and the tree item by calling the static member function SetSelectedItem. This static member function also sets the menu callback, as described in The Menu Callback.
Finally, the menu is returned.
The contents of the AddColor static function are shown in more detail:
static void
AddColor(IlvPopupMenu* menu, const char* color)
{
// This static function is used to fill the contextual menu.
IlvDisplay* display = menu->getDisplay();
// A menu item is created with the specified color name.
IlvMenuItem* item = new IlvMenuItem(color? color : "None");
if (color) {
// An IlvFilledRectangle object is created with the specfied color.
IlvPalette* palette = display->getPalette(0, display->getColor(color));
IlvFilledRectangle* rectangle =
new IlvFilledRectangle(display,
IlvRect(0, 0, 20, 20),
palette);
// The IlvFilledRectangle object is set as the picture of the item.
item->setGraphic(rectangle);
}
// The item is inserted into the pop-up menu.
menu->insertItem(item);
}
After creating the menu item by using the name given as the second parameter in the function, an IlvFilledRectangle is created with the right palette, and is set as the picture of the menu item. Then the menu item is inserted into the menu.
The Menu Callback
The details of how the color of an item is changed when a menu item is selected are now presented. The code of the menu callback is shown in detail:
static void
ChangeColor(IlvGraphic* g, IlvAny arg)
{
IlvPopupMenu* menu = (IlvPopupMenu*)g;
// Retrieve the selected item of the pop-up menu.
IlvShort selected = menu->whichSelected();
if (selected != -1) {
// Retrieve its graphical representation.
IlvSimpleGraphic* graphic =
(IlvSimpleGraphic*)menu->getItem(selected)->getGraphic();
// Use the second argument of the callback: A pointer to the colored
// tree gadget object.
ColoredTreeGadget* tree = (ColoredTreeGadget*)arg;
// Retrieve the item whose color is going to be changed.
IlvTreeGadgetItem* item =
ColoredTreeGadgetInteractor::GetSelectedItem(menu);
// Change the color of all the children of the parent item.
if (item && item->getParent())
tree->setChildrenBackground(item->getParent(),
graphic
? graphic->getForeground()
: (IlvColor*)0);
}
}
The color is taken from the picture of the selected menu item, and is set on the ColoredTreeGadget by calling the setChildrenBackground method.
Testing the New Interactor
The file main.cpp contains the code to test the ColoredTreeGadgetInteractor class. It is the same as the main.cpp file of Step 1, except that the interactor is set on the ColoredTreeGadget instance:
tree->setInteractor(new ColoredTreeGadgetInteractor());
This is the end of Step 2. The test program should look like this:
Published date: 05/24/2022
Last modified date: 02/24/2022