Step 3: Creating a Composite Gadget
This step explains how to create a new gadget, that is, a direct subclass of the
IlvGadget class. The gadget to be created is a composite gadget made up of three gadgets: a text field and two buttons. The
UpDownField gadget appears as follows:
This step shows you how to perform the following tasks:
Creating a Gadget Composed of Other Gadgets
Creating a composite gadget means creating a new gadget that is composed of one or more existing gadgets. This is the case for the
IlvTreeGadget, which has internal
IlvScrollBar objects to allow scrolling operations. This is also the case for the
UpDownField class created in this step.
Declaring the Composite Gadget
To keep pointers to the objects that UpDownField is composed of, protected member variables are used:
class UpDownField
: public IlvGadget
{
....
protected:
IlvTextField* _textField;
IlvButton* _rightButton;
IlvButton* _leftButton;
};
Creating the Components
First, the objects that make up the composite gadget must be created. The UpDownField class has a protected member function init that is called by each constructor of the class:
void
UpDownField::init(const char* label)
{
// Compute the bounding boxes of each element.
IlvRect r1, r2, r3;
computeRects(r1, r2, r3);
// Text field.
_textField =
new IlvTextField(getDisplay(),
label,
r2,
getThickness(),
getPalette());
_focusGadget = _textField;
// Left Button.
_leftButton = new IlvButton(getDisplay(), "-", r1, getThickness(),
getPalette());
_leftButton->setCallback(_internal_Down, this);
// Right Button.
_rightButton = new IlvButton(getDisplay(), "+", r3, getThickness(),
getPalette());
_rightButton->setCallback(_internal_Up, this);
}
The init method first computes the bounding boxes of all the objects by calling the computeRects member function. Then, it creates and initializes the objects.
Note: The two buttons are given a callback. The left button callback will call the UpDownField::decrement method, and the right button callback will call the UpDownField::increment method. |
The Layout
Here is the description of the computeRects method responsible for the layout of the UpDownField class:
void
UpDownField::computeRects(IlvRect& r1,
IlvRect& r2,
IlvRect& r3,
const IlvTransformer* t) const
{
IlvRect rect = _drawrect;
if (t)
t->apply(rect);
r1.moveResize(rect.x(), rect.y(), (IlvDim)ButtonWidth, rect.h());
IlvDim width = rect.w() - (2*(ButtonWidth + Margin));
r2.moveResize(rect.x() + (IlvPos)(ButtonWidth + Margin),
rect.y(),
(IlvDim)IlvMax(width, (IlvDim)0),
rect.h());
IlvPos deltaX = (IlvPos)(rect.w() - ButtonWidth);
r3.moveResize(rect.x() + (IlvPos)IlvMax(deltaX, (IlvPos)0),
rect.y(),
ButtonWidth,
rect.h());
r1.intersection(rect);
r2.intersection(rect);
r3.intersection(rect);
}
r1 is the bounding box of the left button.
r2 is the bounding box of the text field.
r3 is the bounding box of the right button.
This method is called each time the composite gadget is moved or resized. As with all graphic objects, the applyTransform method is called in both these cases:
void
UpDownField::applyTransform(const IlvTransformer* t)
{
IlvGadget::applyTransform(t);
IlvRect r1, r2, r3;
computeRects(r1, r2, r3);
_rightButton->moveResize(r3);
_textField->moveResize(r2);
_leftButton->moveResize(r1);
}
It is also used to query the composite gadget about its component bounding boxes.
Drawing the Components
The components of the composite gadget are now well located. The composite gadget draws them by calling the draw member function of each component:
void
UpDownField::draw(IlvPort* dst,
const IlvTransformer* t,
const IlvRegion* clip) const
{
_textField->draw(dst, t, clip);
_rightButton->draw(dst, t, clip);
_leftButton->draw(dst, t, clip);
}
Redefining IlvGraphic Member Functions
Several member functions of the
IlvGraphic class have been redefined to allow delegation to the components of the composite gadget. The
setPalette method is given as an example:
void
UpDownField::setPalette(IlvPalette* palette)
{
IlvGadget::setPalette(palette);
_textField->setPalette(palette);
_rightButton->setPalette(palette);
_leftButton->setPalette(palette);
}
When the palette of UpDownField is changed, the same palette is set to each component of the UpDownField.
Finally, you need to override the setHolder method as follows:
void
UpDownField::setHolder(IlvGraphicHolder* holder)
{
IlvGadget::setHolder(holder);
_textField->setHolder(holder);
_rightButton->setHolder(holder);
_leftButton->setHolder(holder);
}
This method will be called each time UpDownField is added or removed from a holder. It ensures that all the components of UpDownField have the same holder. This is mandatory, as gadgets require a holder to behave properly.
Handling Keyboard Focus in a Gadget
The main difference between handling events in a graphic and in a gadget class is that a gadget can take the keyboard focus. Once a gadget has the keyboard focus, keyboard events are sent to this gadget. For more details, see “Focus Management” in Gadgets Main Properties.
Handling keyboard focus means:
Reacting to focus events. When a gadget is about to be given the focus, it receives the
IlvKeyboardFocusIn event. Similarly, when a gadget is about to lose the focus, it receives the
IlvKeyboardFocusOut event.
Handling the drawing of the focus. Two methods are involved in the drawing mechanism of the focus:
IlvGraphic::computeFocusRegion and
IlvGraphic::drawFocus. The first method gives information about the location of the focus drawing. The second method draws the focus.
It must be possible to give the focus to each component of UpDownField. When the focus is given to UpDownField, it is forwarded to the component that you chose to have it sent to. A protected member variable is added to keep a pointer to the current focus component of the UpDownField:
protected:
IlvGadget* _focusGadget;
A method named setFocus is also added to change the current focused object inside UpDownField:
void
UpDownField::setFocus(IlvGadget* gadget)
{
IlvRegion region;
// Send a focus_out event to the gadget that loses the focus.
if (_focusGadget) {
IlvEvent fo;
fo._type = IlvKeyboardFocusOut;
_focusGadget->computeFocusRegion(region, getTransformer());
_focusGadget->handleEvent(fo);
_focusGadget = 0;
}
_focusGadget = gadget;
// Send a focus_in event to the gadget that receives the focus.
if (_focusGadget) {
IlvEvent fi;
fi._type = IlvKeyboardFocusIn;
_focusGadget->handleEvent(fi);
_focusGadget->computeFocusRegion(region, getTransformer());
}
if (getHolder())
getHolder()->reDraw(®ion);
}
The method first sends an IlvKeyboardFocusOut event to the component that will lose the keyboard focus. Then it sends an IlvKeyboardFocusIn event to the new focused component. Finally, the modified region is redrawn using the IlvGraphicHolder API.
The following shows how the focus is drawn. The computeFocusRegion and drawFocus methods are involved in this process:
void
UpDownField::drawFocus(IlvPort* dst,
const IlvPalette* palette,
const IlvTransformer* t,
const IlvRegion* clip) const
{
_focusGadget->drawFocus(dst, palette, t, clip);
}
void
UpDownField::computeFocusRegion(IlvRegion& region,
const IlvTransformer* t) const
{
_focusGadget->computeFocusRegion(region, t);
}
The UpDownField delegates to the current focused object. The next section shows how to change the keyboard focus of the UpDownField.
Handling Events in a Gadget
The entry point for events in a gadget class is the
handleEvent method. This method is called by the gadget holder when it receives an event that should be handled by the gadget. The
UpDownField::handleEvent is very simple, as it delegates most of the events to the focused object (pointed by the
_focusGadget member variable):
IlvBoolean
UpDownField::handleEvent(IlvEvent& event)
{
IlvBoolean result = IlvFalse;
switch (event.type()) {
case IlvButtonDown:
{
// Changing focus on click
IlvRect r1, r2, r3;
IlvPoint evp(event.x(), event.y());
computeRects(r1, r2, r3, getTransformer());
if (r2.contains(evp) && _focusGadget != _textField)
setFocus(_textField);
else
if (r3.contains(evp) && _focusGadget != _rightButton)
setFocus(_rightButton);
else
if (r1.contains(evp) && _focusGadget != _leftButton)
setFocus(_leftButton);
result = _focusGadget->handleEvent(event);
break;
}
case IlvKeyDown:
{
// Moving focus with the Tab key.
if (event.data() == IlvTab &&
(!(event.modifiers() & IlvShiftModifier)) &&
(!(event.modifiers() & IlvCtrlModifier))) {
if (_focusGadget == _textField)
setFocus(_rightButton);
else
if (_focusGadget == _rightButton)
setFocus(_leftButton);
else
if (_focusGadget == _leftButton)
setFocus(_textField);
return IlvTrue;
}
// Moving focus with the Shift Tab key.
if (event.data() == IlvTab &&
((event.modifiers() & IlvShiftModifier)) &&
(!(event.modifiers() & IlvCtrlModifier))) {
if (_focusGadget == _leftButton)
setFocus(_rightButton);
else
if (_focusGadget == _rightButton)
setFocus(_textField);
else
if (_focusGadget == _textField)
setFocus(_leftButton);
return IlvTrue;
}
}
default:
result = _focusGadget->handleEvent(event);
}
return result;
}
The main part of the handleEvent method deals with keyboard focus management. The IlvButtonDown case is responsible for changing the current focused object when a button down event is received by the composite gadget. The IlvKeyDown case is responsible for changing the current focus object when a TAB key or Shift-TAB key event is received by the composite gadget. The default case simply delegates to the current focused object.
Adding Callbacks to a Gadget
As shown at the beginning of this step, the left and right buttons of UpDownField are assigned a callback to allow user notification when pressed. Two callbacks are now defined: a Down callback, which will be called each time the left button is pressed, and an Up callback, which will be called each time the right button is pressed.
These callbacks are declared in the getCallbackType method:
IlvUInt
UpDownField::getCallbackTypes(const char* const** names,
const IlvSymbol* const** types) const
{
IlvUInt count = IlvGadget::getCallbackTypes(names, types);
AddToCallbackTypeList(count, names, types,
"Down", downCallbackType());
AddToCallbackTypeList(count, names, types,
"Up", upCallbackType());
return count;
}
Note: Redefining this method is not mandatory, but will allow easier integration of the UpDownField in an editor (such as Rogue Wave Views Studio), since it will be possible for the editor to query the list of callbacks handled by the UpDownField. |
Then, the UpDownField::increment method is implemented to call the Up callback and the UpDownField::decrement method is implemented to call the Down callback:
void
UpDownField::increment()
{
callCallbacks(upCallbackType());
}
void
UpDownField::decrement()
{
callCallbacks(downCallbackType());
}
Testing the Composite Gadget
The file main.cpp contains the code to test the UpDownField class. It shows how to implement the Up and Down callbacks of the UpDownField class to change the value of the text field—the Down callback will decrement the text field value, while the Up callback will increment it.
First, a container is created and an instance of the UpDownField class is added to it.
IlvDialog* dialog = new IlvDialog(display,
title,
title,
IlvRect(0, 0, 100, 100));
UpDownField* but = new UpDownField(display, IlvRect(5, 5, 100, 23) , "0");
dialog->addObject(but);
Then, the callbacks of the UpDownField are set:
but->setUpCallback(Increment);
but->setDownCallback(Decrement);
Here is the implementation of the callbacks:
static void Increment(IlvGraphic* g, IlvAny)
{
char buffer[1000];
UpDownField * obj = (UpDownField*)g;
const char* label = obj->getLabel();
if (label && *label) {
IlvInt value = ((IlvInt)atof(label))+1;
sprintf(buffer, "%ld", value);
obj->setLabel(buffer, IlvTrue);
} else
obj->setLabel("0", IlvTrue);
}
static void Decrement(IlvGraphic* g, IlvAny)
{
char buffer[1000];
UpDownField * obj = (UpDownField*)g;
const char* label = obj->getLabel();
if (label && *label) {
IlvInt value = ((IlvInt)atof(label))-1;
sprintf(buffer, "%ld", value);
obj->setLabel(buffer, IlvTrue);
} else
obj->setLabel("0", IlvTrue);
}
These callbacks retrieve the text field value of the UpDownField instance by calling UpDownField::getLabel, modify it by adding or removing the value 1, and set it as the label of the text field by calling UpDownField::setLabel.
This is the end of Step 3.
Version 6.2.1
Copyright © 2018, Rogue Wave Software, Inc. All Rights Reserved.