Tutorials > Gadgets > Customizing Gadgets > Step 1: Extending a Gadget by Changing its Graphical Appearance
Step 1: Extending a Gadget by Changing its Graphical Appearance
This first step describes how to create a subclass of an existing gadget to change its graphical appearance.
In this step the IlvTreeGadget class will be extended. Its drawing will be modified to allow the user to change the color of the children of an item, as shown in the following figure:
This step shows you how to perform the following tasks:
Creating a Subclass of an Existing Gadget
Creating a new subclass of an existing gadget is similar to creating a subclass of any existing IlvGraphic class. The main steps to properly create a subclass of an IlvGraphic object are described here.
Declare the Subclass
Declare your subclass as a subclass of the chosen graphic. In this tutorial, IlvTreeGadget is subclassed to create a new object that can display colored items. This subclass will be called ColoredTreeGadget. Here is the declaration of the ColoredTreeGadget class:
class ColoredTreeGadget
: public IlvTreeGadget {
public:
ColoredTreeGadget(IlvDisplay* display,
const IlvRect& rect,
IlvUShort thickness = IlvDefaultGadgetThickness,
IlvPalette* palette = 0);
virtual ~ColoredTreeGadget();
};
This declaration is made up of a constructor and the destructor of the ColoredTreeGadget class.
Add the registration of the ColoredTreeGadget to allow dynamic typing as well as persistence:
DeclareTypeInfo();
DeclareIOConstructors(ColoredTreeGadget);
DeclareGraphicAccessors();
The DeclareGraphicAccessors macro declares the queryValue and applyValue methods to allow the new object to be scriptable.
Implement the Methods
The implementation of the ColoredTreeGadget methods that have been declared is shown here.
First, the constructor is implemented as follows:
ColoredTreeGadget::ColoredTreeGadget(IlvDisplay* display,
const IlvRect& rect,
IlvUShort thickness,
IlvPalette* palette)
: IlvTreeGadget(display, rect, thickness, palette)
{
}
The copy constructor, declared by the DeclareIOContructors macro:
ColoredTreeGadget::ColoredTreeGadget(const ColoredTreeGadget& source)
: IlvTreeGadget(source)
{
}
Then, the IO constructor, also declared by the DeclareIOConstructors macro:
ColoredTreeGadget::ColoredTreeGadget(IlvInputFile& is, IlvPalette* palette)
: IlvTreeGadget(is, palette)
{
}
The write method, declared by the DeclareTypeInfo macro:
void
ColoredTreeGadget::write(IlvOutputFile& os) const
{
IlvTreeGadget::write(os);
}
The destructor:
ColoredTreeGadget::~ColoredTreeGadget()
{
}
The accessor related methods, declared by the DeclareGraphicAccessors macro:
IlvValue&
ColoredTreeGadget::queryValue(IlvValue& value) const
{
return IlvTreeGadget::queryValue(value);
}
 
IlvBoolean
ColoredTreeGadget::applyValue(const IlvValue& value)
{
return IlvTreeGadget::applyValue(value);
}
 
void
ColoredTreeGadget::GetAccessors(const IlvSymbol* const** a,
const IlvValueTypeClass* const** t,
IlvUInt& c)
{
}
Then, the implementation of the copy and read methods are given by the IlvPredefinedIOMembers macro:
IlvPredefinedIOMembers(ColoredTreeGadget)
Finally, the class is registered as a subclass of the IlvTreeGadget class:
IlvRegisterClass(ColoredTreeGadget, IlvTreeGadget);
These items are necessary to properly register the ColoredTreeGadget class. Although many of these methods are empty, they will be filled in the next tasks.
Adding an API to the New Gadget Class
The declaration of the ColoredTreeGadget class can be found in the coltree.h file, and its implementation can be found in the coltree.cpp file.
The ColoredTreeGadget class requires the following:
*A way to associate a tree item with a specific color. This color will be used to draw the background of the children of this item.
*A way to enable or disable the coloring of the tree items.
*Adding accessors to it.
Associating an Item With a Color
In order to associate a tree item with a color, the setChildrenBackground method has been added. It uses a property set on the tree item to store the color:
void
ColoredTreeGadget::setChildrenBackground(IlvTreeGadgetItem* item,
IlvColor* color,
IlvBoolean redraw)
{
// Retrieve the old color.
IlvPalette* oldPalette =
(IlvPalette*)item->getProperty(GetChildrenBackgroundSymbol());
// Compute the new color.
IlvPalette* palette = color
? getDisplay()->getPalette(0, color)
: 0;
// Lock it.
if (palette)
palette->lock();
// Unlock the old one.
if (oldPalette)
oldPalette->unLock();
// Set the property to the item.
item->setProperty(GetChildrenBackgroundSymbol(), (IlvAny)palette);
// Redraw if asked.
if (redraw)
reDraw();
}
The GetChildrenBackgroundSymbol static method returns a symbol that identifies the property set on the item. Its definition is simple:
static IlvSymbol*
GetChildrenBackgroundSymbol()
{
// This symbol is used to connect a tree gadget item to the color of its
// children.
static IlvSymbol* symbol = IlvGetSymbol("ChildrenBackground");
return symbol;
}
The getChildrenBackground method returns the color associated with a given tree item.
IlvColor*
ColoredTreeGadget::getChildrenBackground(const IlvTreeGadgetItem* item) const
{
// Returns the color stored in the property list of the specified item.
IlvPalette* palette =
(IlvPalette*)item->getProperty(GetChildrenBackgroundSymbol());
return palette
? palette->getForeground()
: 0;
}
Enable/Disable the Coloring
The code that will allow the user to enable or disable the coloring of a tree item must be implemented. To do this, a protected member variable is added to the ColoredTreeGadget class:
protected:
IlvBoolean _drawChildrenBg;
This Boolean value will be initialized by the constructors to IlvTrue by default. Here are the member functions that give access to the _drawChildrenBg member variable:
IlvBoolean isDrawingChildrenBackground() const;
void drawChildrenBackground(IlvBoolean value,
IlvBoolean redraw = IlvTrue);
Since the _drawChildrenBg member variable must be saved, both the IO constructor and the write method must be modified:
ColoredTreeGadget::ColoredTreeGadget(IlvInputFile& is, IlvPalette* palette)
: IlvTreeGadget(is, palette),
_drawChildrenBg(IlvTrue)
{
// Read the _drawChildrenBg flag.
int drawChildrenBg;
is.getStream() >> drawChildrenBg;
_drawChildrenBg = (IlvBoolean)drawChildrenBg;
}
 
void
ColoredTreeGadget::write(IlvOutputFile& os) const
{
IlvTreeGadget::write(os);
// Write the _drawChildrenBg flag.
os.getStream() << IlvSpc() << (int)_drawChildrenBg << IlvSpc();
}
Adding Accessors
Adding accessors to objects makes them available for scripting. The following description shows an example of an accessor to the _drawChildrenBg member variable.
First, the symbol that will be used to access this variable is defined. This is done through the static function GetDrawChildrenBackgroundSymbol.
static IlvSymbol*
GetDrawChildrenBackgroundSymbol()
{
// This symbol is used to access to drawChildrenBackground accessor of
// the colored tree gadget.
static IlvSymbol* symbol = IlvGetSymbol("drawChildrenBackground");
return symbol;
}
Then, the new accessor is registered in GetAccessors:
void
ColoredTreeGadget::GetAccessors(const IlvSymbol* const** a,
const IlvValueTypeClass* const** t,
IlvUInt& c)
{
DeclareAccessor(GetDrawChildrenBackgroundSymbol(),
IlvValueBooleanType,
a,
t,
c);
}
The queryValue method is called when the value of the accessor is queried.
IlvValue&
ColoredTreeGadget::queryValue(IlvValue& value) const
{
if (value.getName() == GetDrawChildrenBackgroundSymbol())
return value = isDrawingChildrenBackground();
else
return IlvTreeGadget::queryValue(value);
}
The applyValue method is called when the value of the accessor is modified.
IlvBoolean
ColoredTreeGadget::applyValue(const IlvValue& value)
{
if (value.getName() == GetDrawChildrenBackgroundSymbol()) {
drawChildrenBackground((IlvBoolean)value, IlvFalse);
return IlvTrue;
} else
return IlvTreeGadget::applyValue(value);
}
Modifying How the New Gadget is Drawn
The next step is to change the way ColoredTreeGadget is drawn. The standard way to change the drawing of a graphical object is to override its draw method defined at the IlvGraphic level:
virtual void draw(IlvPort* dst,
const IlvTransformer* t = 0,
const IlvRegion* clip = 0) const = 0;
However, the IlvTreeGadget is a complex object and has several other methods that can help customize its drawing. Here, you want to draw the background before each item is drawn. You can use the IlvGadgetItemHolder::drawGadgetItem method. It is called by the tree to draw each item.
void
ColoredTreeGadget::drawGadgetItem(const IlvGadgetItem* item,
IlvPort* port,
const IlvRect& rect,
const IlvTransformer* t,
const IlvRegion* clip) const
{
if (isDrawingChildrenBackground()) {
// Check if the item being drawn has a special palette.
IlvPalette* palette = getBackgroundPalette((IlvTreeGadgetItem*)item);
if (palette) {
// Compute the visible bounding box.
IlvRect bbox;
visibleBBox(bbox, t);
// Move and resize it to match the item bounding box.
bbox.y(rect.y());
bbox.h(rect.h());
if (clip)
palette->setClip(clip);
port->fillRectangle(palette, bbox);
if (clip)
palette->setClip();
}
}
// Draw the item.
IlvTreeGadget::drawGadgetItem(item, port, rect, t, clip);
}
The getBackgroundPalette method is used to retrieve the palette used to draw the background of a tree item.
IlvPalette*
ColoredTreeGadget::getBackgroundPalette(const IlvTreeGadgetItem* item) const
{
// Returns the palette that will be used to draw the background of ’item’
// This information is stored in its parent
if (item->getParent()) {
IlvPalette* palette = (IlvPalette*)
item->getParent()->getProperty(GetChildrenBackgroundSymbol());
if (!palette)
palette = getBackgroundPalette(item->getParent());
return palette;
} else
return 0;
}
The getBackgroundPalette method tries to find the first parent of ‘item’ that has been set as a background palette. This means that a tree item inherits its color from its parent—changing the background color of an item may not only change the background color of its children, but also the background color of the children of its children, and so on.
Testing the New Gadget Class
The file main.cpp contains the code to test the ColoredTreeGadget class. It reads the file coltree.ilv, which contains the description of a ColoredTreeGadget instance, and changes the color of all items according to their level.
// Read the file that contains the colored tree.
container->readFile("../doc/gadgets/tutorials/custgad/data/coltree.ilv");
 
// Retrieve the tree.
ColoredTreeGadget* tree = (ColoredTreeGadget*)container->getObject("Tree");
 
// Set the background of the tree to gray.
// This color will be used as the reference color to compute the children
// colors.
tree->setBackground(display->getColor("gray"));
 
// Now change the color of each level of items.
tree->applyToItems(ChangeColor, (IlvAny)tree);
The applyToItems method applies the ChangeColor function to every item in the tree. Here is the description of this ChangeColor function, which calls the ColoredTreeGadget::setChildrenBackground method:
static IlvBoolean
ChangeColor(IlvGadgetItem* item, IlvAny arg)
{
// The argument is a pointer to the ColoredTreeGadget instance.
ColoredTreeGadget* tree = (ColoredTreeGadget*)arg;
// Change the background of the children of ‘item’.
tree->setChildrenBackground((IlvTreeGadgetItem*)item,
GetChildrenColor((IlvTreeGadgetItem*)item,
tree->getBackground()),
IlvFalse);
// Continue.
return IlvTrue;
}
Another static function is used to compute the color using a tree item and a reference color:
static IlvColor*
GetChildrenColor(IlvTreeGadgetItem* item, IlvColor* color)
{
// Get the item level to choose the right color.
IlvUInt level = item->getLevel();
// Compute the HSV components of the reference color.
IlvFloat h, s, v;
color->getHSV(h, s, v);
// Increase the V component.
v = (IlvFloat)IlvMin((IlvFloat)1., (IlvFloat)(v + level*.08));
// Return the new color.
return color->getDisplay()->getColor(h, s, v);
}
This function uses the level of the item and the specified reference color to compute a new color. This new color is computed by changing the V component of the reference color in the HSV model.
This is the end of Step 1. The test program should look like this:
Published date: 05/24/2022
Last modified date: 02/24/2022