Managing Events: Object Interactors

An object interactor filters user events for the graphic object to which it is attached. If an appropriate series of events occurs, the object interactor triggers a visible response from that graphic object. This response is called the object behavior.

Views provides a comprehensive set of predefined object interactors. If you find yourself needing a very specific functionality not already predefined in Views, you can subtype one of the interactor classes and replace its member function handleEvent with the functionality you need.

Attaching an Interactor to an Object

The class IlvInteractor lets you associate a behavior with an object.

Note

These object interactors are not intended for creating editors. The Views Manager interactors that are associated with the whole view instead of individual objects are used to create interactive editors.

For more details on object interactors, see:

Using Object Interactors

The member functions that deal with object interactors are the following:

  • ::getInteractor  Returns the IlvInteractor instance associated with the IlvGraphic object given as an argument.

In the following example, a predefined Views interactor is associated with an IlvLabel graphic. It is an instance of the IlvMoveInteractor class which lets the user move an object by pressing the left mouse button when pointing to this object.

IlvRect size(0, 0, 300, 300);

IlvContainer* cont = new IlvContainer(display, "Cont", "My Window",

size, IlTrue, IlFalse);

IlvLabel* label = new IlvLabel(display, IlvPoint(100,100),

"Hello world!");

cont->addObject(label);

label->setInteractor(IlvInteractor::Get("Move"));

The static member function ::Get returns the unique instance of the object interactor whose name is “Move.” You usually do not create an interactor by calling its constructor directly, but by using this static member function. This is because most object interactors can be shared by numerous graphic objects at the same time.

Registering a New IlvInteractor Subclass

If you subtype the IlvInteractor class, you have to register your subclass in order to use the static member function ::Get. Below is that part of the header file where the IlvInteractor class in MyInteractor class is subtyped:

class MyInteractor

: public IlvInteractor {

public:

IlBoolean handleEvent(IlvGraphic* obj,

IlvEvent& event,

IlvTransformer* t);

...

DeclareInteractorTypeInfo(MyInteractor);

};

Note that we have added the line that calls the macro DeclareInteractorTypeInfo that allows for class persistence, as well as registration. This macro forces you to define a constructor that expects a reference to an IlvInputFile, a copy constructor that expects a reference to a MyInteractor, and a write member function that is needed to save the interactor instance. Of course the constructor and the write must match, just as in the case of the class IlvGraphic.

In the situation where your interactor has no extra information to save, you would have used the macro DeclareInteractorTypeInfoRO that does not force you to define a write member function.

In the example there is not any extra information to save, but for the completeness of the example, we pretend to have a dummy integer value to save and read:

MyInteractor::MyInteractor(IlvInputFile& file)

: IlvInteractor(file)

{

IlInt i;

file.getStream() >> i; // Read a (dummy) integer value

}

 

IlvInteractor*

MyInteractor::write(IlvOutputFile& file)

{

file.getStream() << (IlInt)0;

}

If you had used DeclareInteractorTypeInfoRO, the constructor would have been empty, and write would not have been defined.

In the source file, outside the body of any other function, you must write the two following instructions:

  • IlvPredefinedInteractorIOMembers(MyInteractor)

IlvPredefinedInteractorIOMembers is a macro that generates the proxy function for you that will call your constructor from input file and define the copy member function.

  • IlvRegisterInteractorClass(MyInteractor, IlvInteractor);

IlvRegisterInteractorClass is a macro that registers the class MyInteractor as a new available interactor class. The second parameter must be the name of the parent class.

Predefined Object Interactors

Several classes help you program predefined object interactors:

  • IlvButtonInteractor  This class can be attached to any graphic object to make it behave like a standard interface button.

  • IlvToggleInteractor  This class is a subtype of the class IlvButtonInteractor. This subclass inverts the object (calls its member function invert) to which this interactor is associated when the user presses then releases the mouse button over it.

  • IlvMoveInteractor  Lets the user move an object by clicking on it and dragging the pointing device to another place.

  • IlvReshapeInteractor  Lets the user reshape an object by creating a rectangle with the right mouse button. The rectangle becomes the object’s new bounding box.

  • IlvDragDropInteractor  Provides a way to drag and drop an object from a container to another view. It lets you click an object and move a copy of the object around, even outside the container itself.

Example: Linking an Interactor and an Accelerator

In the following example, we create the window below and the two enclosed drawings:

Container with Two Objects

In other words, we create a container as a top window, then we add two objects to the container: a gray ellipse and an arc. However, before actually placing these objects in the container, we create a reshaping interactor, enabling us to use the mouse to change the shape of a graphic object. We actually use this single interactor in two places:

  • Immediately after adding the ellipse to the container, we associate the reshaping interactor with this object.

  • Immediately after adding the arc to the container, we associate the same reshaping interactor with it.

We can now click either of the two graphic objects, and the reshaping interactor lets us change the shape of the selected object by dragging the mouse.

Furthermore, after creating the two objects and the unique interactor, we create an accelerator for the container, so we can get printed information about an object by double-clicking it with the left mouse button:

  • If we double-click the ellipse, we get the following message:
    Object is an IlvFilledEllipse

  • If we double-click the arc, we get the following message:
    Object is an IlvArc

We write a three-argument function called PrintType, which has the predefined type IlvContainerAction, to produce these messages. The name of this function is given to the container as the first argument of the call to the member function addAccelerator.

Following is the entire demonstration program. To keep the example short, we do not provide any way to exit from the program:

#include <ilviews/contain/contain.h>

#include <ilviews/graphics/ellipse.h>

#include <ilviews/graphics/arc.h>

#include <ilviews/graphics/inter.h>

 

static void PrintType(IlvContainer*, IlvEvent&, IlAny);

 

int

main(int argc, char* argv[])

{

    IlvDisplay* display = new IlvDisplay("Demo", "", argc, argv);

if (!display || display->isBad()) {

IlvFatalError("Couldn't open display");

delete display;

IlvExit(-1);

}

IlvContainer* container =

new IlvContainer(display, "Demo", "Demo",

IlvRect(0, 0, 200, 200),

IlTrue, IlFalse);

IlvInteractor* reshape = IlvInteractor::Get("Reshape");

IlvGraphic* object =

new IlvFilledEllipse(display, IlvRect(150, 50, 40, 20));

 

container->addObject(object);

object->setInteractor(reshape);

container->addObject(object =

new IlvArc(display, IlvRect(10, 150, 40, 40), 0., 60.));

object->setInteractor(reshape);

container->addAccelerator(PrintType,

IlvDoubleClick,

IlvLeftButton);

container->show();

IlvMainLoop();

return 0;

}

 

static void

PrintType(IlvContainer* view, IlvEvent& event, IlAny)

{

IlvGraphic* object =

view->contains(IlvPoint(event.x(), event.y()));

if (object)

IlvPrint("Object is an ‘%s’\n" , object->className());

}

Analyzing the Example

This section explains the code used for the above example.

static void PrintType(IlvContainer*, IlvEvent&, IlAny);

The user-defined PrintType function is called by an accelerator, which is launched when the user double-clicks a graphic object with the left mouse button. The signature of the PrintType function corresponds to the type that is called IlvContainerAction.

IlvContainer* container = new IlvContainer(display, "Demo", "Demo",

IlvRect(0, 0, 200, 20), IlTrue, IlFalse);

A container is created as the top-level view.

IlvInteractor* reshape = IlvInteractor::Get("Reshape");

A reshaping interactor is located. This was registered automatically when you included <ilviews/graphics/inter.h>.

IlvGraphic* object = new IlvFilledEllipse(display, IlvRect(150,50, 40,20));

container->addObject(object);

A filled ellipse is created and added to the container.

object->setInteractor(reshape);

The reshaping interactor is associated with the filled ellipse.

container->addObject(object =

new IlvArc(display, IlvRect(10, 150, 40, 40), 0., 60.));

An arc is added to the container.

object->setInteractor(reshape);

The reshaping interactor is associated with the arc.

container->addAccelerator(PrintType, IlvDoubleClick, IlvLeftButton);

An accelerator is added to the container. This accelerator applies the user-defined function named PrintType whenever the user double-clicks an object with the left mouse button.

static void

PrintType(IlvContainer* view, IlvEvent& ev, IlAny)

{

IlvGraphic* object = view->contains(IlvPoint(ev.x(), ev.y()));

if (object)

IlvPrint("Object is a '%s’\n", object–>className());

}

This is the actual implementation of the user-defined PrintType function. We access the object located under the mouse pointer by calling the member function contains. We use the function IlvPrint to ensure the portability of this example.