Envision
A visual programming IDE for object-oriented languages
Creating a handler for a visualization

In this tutorial we will see how to enhance a visualization with a custom handler.

More specifically we will enhance the visualization for if statements that we developed in the Adding a new visualization item tutorial. You should be familiar with that tutorial before reading on. In the present tutorial we will use OOInteraction::HIfStatement as example.

Introduction

All visual items in Envision process events through handlers. All mouse, keyboard and application events are passed to the currently selected item by Qt, and the item directly forwards these events to its handler for processing. If you do not do anything, a newly created item type will use the default handler which is Interaction::GenericHandler. That handler already implements a lot of functionality related to copy and paste, selection, and others. However, sometimes we want to customize the interaction of our visualizations. To do this we need to create a new handler class and associate it with all visualizations types that it should handle events for.

In this tutorial we will look at the handler for the standard if statement visualization. This handler is OOInteraction::HIfStatement.

Associating a handler class with an item class

To set a handler to be the default handler to be used by an item class you need to call the item class' setDefaultClassHandler() method during the plug-in's initialize() method. Here is how this call looks in our example (OOInteraction/src/OOInteractionPlugin.cpp):

bool OOInteractionPlugin::initialize(Core::EnvisionManager&)
The EnvisionManager interface provides various information about the Envision system.
Definition: EnvisionManager.h:43
{
OOVisualization::VIfStatement::setDefaultClassHandler(HIfStatement::instance());

Now let's see how to write the handler.

HIfStatement.h

First, we'll have a look at the header file. Here is HIfStatement.h :

/***********************************************************************************************************************
**
** Copyright (c) 2011, 2014 ETH Zurich
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
** following conditions are met:
**
** * Redistributions of source code must retain the above copyright notice, this list of conditions and the
** following disclaimer.
** * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
** following disclaimer in the documentation and/or other materials provided with the distribution.
** * Neither the name of the ETH Zurich nor the names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**********************************************************************************************************************/
#pragma once
#include "../oointeraction_api.h"
#include "HStatement.h"
namespace OOInteraction {
class OOINTERACTION_API HIfStatement : public HStatement {
public:
static HIfStatement* instance();
virtual void keyPressEvent(Visualization::Item *target, QKeyEvent *event);
protected:
HIfStatement();
};
}
Definition: Item.h:50
Definition: OOActions.cpp:33

Each handler must inherit from Visualization::InteractionHandler. In practice the only handler that directly inherits from it is Interaction::GenericHandler. This is where a lot of common functionality is implemented. Therefore it is recommended that all new handlers inherit from Interaction::GenericHandler. In our case the base handler is OOInteraction::HStatement which is the default handler for all statement items. It implements some functionality regarding creating new statements in statement lists and itself inherits from Interaction::GenericHandler.

Each handler is a singleton and needs a public instance() method that returns a pointer to the only instance. The constructor is protected. This make it possible to create inherited handlers.

In the custom handler for if statements, we'll only customize keyboard interaction. Thus the only method that is overridden is keyPressEvent(). For a list of all possible methods that can be overridden have a look at Visualization::InteractionHandler.

Let's look at the .cpp file to see what this handler does exactly.

HIfStatement.cpp

Here is the complete contents of the HIfStatement.cpp :

/***********************************************************************************************************************
**
** Copyright (c) 2011, 2014 ETH Zurich
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
** following conditions are met:
**
** * Redistributions of source code must retain the above copyright notice, this list of conditions and the
** following disclaimer.
** * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
** following disclaimer in the documentation and/or other materials provided with the distribution.
** * Neither the name of the ETH Zurich nor the names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**********************************************************************************************************************/
#include "HIfStatement.h"
#include "OOVisualization/src/statements/VIfStatement.h"
#include "OOVisualization/src/elements/VStatementItemList.h"
#include "OOModel/src/expressions/EmptyExpression.h"
#include "OOModel/src/statements/ExpressionStatement.h"
#include "InteractionBase/src/events/SetCursorEvent.h"
#include "VisualizationBase/src/cursor/LayoutCursor.h"
#include "VisualizationBase/src/items/Static.h"
#include "VisualizationBase/src/items/NodeWrapper.h"
namespace OOInteraction {
{}
HIfStatement* HIfStatement::instance()
{
static HIfStatement h;
return &h;
}
void HIfStatement::keyPressEvent(Visualization::Item *target, QKeyEvent *event)
{
auto vif = DCast<OOVisualization::VIfStatement> ( target );
event->ignore();
bool enter = event->modifiers() == Qt::NoModifier &&
(event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return);
bool tab = event->modifiers() == Qt::NoModifier && event->key() == Qt::Key_Tab;
if ( ( vif->condition()->itemOrChildHasFocus() && enter)
|| (vif->elseBranch() && vif->elseBranch()->itemOrChildHasFocus() && tab))
{
event->accept();
if (vif->node()->thenBranch()->size() > 0)
{
new Interaction::SetCursorEvent{target, vif->node()->thenBranch()->at(0)});
}
else
{
auto empty = new OOModel::EmptyExpression{};
es->setExpression(empty);
vif->node()->beginModification("create then branch");
vif->node()->thenBranch()->append(es);
vif->node()->endModification();
vif->thenBranch()->setUpdateNeeded(Visualization::Item::StandardUpdate);
target->scene()->addPostEventAction( new Interaction::SetCursorEvent{target, empty});
}
}
else if (vif->thenBranch()->itemOrChildHasFocus() && tab )
{
event->accept();
if (vif->node()->elseBranch()->size() > 0)
{
vif->node()->elseBranch()->at(0)});
}
else
{
auto empty = new OOModel::EmptyExpression{};
es->setExpression(empty);
vif->node()->beginModification("create else branch");
vif->node()->elseBranch()->append(es);
vif->node()->endModification();
vif->setUpdateNeeded(Visualization::Item::StandardUpdate);
target->scene()->addPostEventAction( new Interaction::SetCursorEvent{target, empty});
}
}
else if (event->modifiers() == Qt::NoModifier
&& ((event->key() == Qt::Key_Backspace && vif->condition()->itemOrChildHasFocus())
|| ((event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete)
&& vif->icon()->itemOrChildHasFocus())))
{
event->accept();
removeFromList(target);
}
if (!event->isAccepted()) HStatement::keyPressEvent(target, event);
}
}
bool removeFromList(Visualization::Item *target)
Removes the node visualized by target from the list which contains it.
Definition: GenericHandler.cpp:698
Definition: SetCursorEvent.h:44
static HIfStatement * instance()
Definition: HIfStatement.cpp:45
virtual void keyPressEvent(Visualization::Item *target, QKeyEvent *event)
Definition: HIfStatement.cpp:51
HIfStatement()
Definition: HIfStatement.cpp:42
virtual void keyPressEvent(Visualization::Item *target, QKeyEvent *event)
Definition: HStatement.cpp:49
Definition: EmptyExpression.h:39
Definition: ExpressionStatement.h:40
Scene * scene() const
Definition: Item.h:633
@ StandardUpdate
Update the geometry of the item and properties depending on child items.
Definition: Item.h:120
virtual Model::Node * node() const
Definition: Item.cpp:371
void addPostEventAction(QEvent *action)
Adds an action to be executed after the current event.
Definition: Scene.cpp:353

As the rest is trivial we'll jump directly to the keyPressEvent() method and examine it piece by piece:

void HIfStatement::keyPressEvent(Visualization::Item *target, QKeyEvent *event)

Each handler method receives two parameters:

  • The target visualization. This is the visual item that received this event. It is safe to use static_cast to cast this directly to the item class for which this handler was registered. If you are using a handler for multiple visualizations then you should use dynamic_cast to distinguish between the different target types.
  • The event as received by Qt's Graphics View system. This contains info regarding the event (e.g. what key was pressed).

Since we only use the handler with OOVisualization::VIfStatement it is safe to cast it:

auto vif = DCast<OOVisualization::VIfStatement> ( target );

Next, we ignore the current event:

event->ignore();

By default, whenever we reimplement a handler method, Qt assumes that the event is handled. We need to mark the event as ignored and then we can try to process it. Only if we do some processing, will we mark it as accepted (handled).

Inside the event handler we can do pretty much anything we want. We can alter the AST, change properties of the visualization, schedule updates, move the cursor, and much more. In this particular case we want to provide some shortcuts for navigating between the various parts of an if statement. We distinguish between the Enter and the TAB keys:

bool enter = event->modifiers() == Qt::NoModifier &&
(event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return);
bool tab = event->modifiers() == Qt::NoModifier && event->key() == Qt::Key_Tab;

Generally speaking the Enter key moves us vertically down, while the TAB key moves us horizontally.

In the next part of the handler we indicate that we want to move to the then branch. Either by going down from the condition or by moving vertically from the else branch:

if ( ( vif->condition()->itemOrChildHasFocus() && enter)
|| (vif->elseBranch() && vif->elseBranch()->itemOrChildHasFocus() && tab))

If the right key combination is pressed, we will handle this event. Mark it as accepted:

event->accept();

Next we need to check whether the then branch already exists (has items) or not:

if (vif->node()->thenBranch()->size() > 0)

If there are already some items, simply switch to the first one:

new Interaction::SetCursorEvent{target, vif->node()->thenBranch()->at(0)});

otherwise create a new empty expression and focus it:

auto empty = new OOModel::EmptyExpression{};
es->setExpression(empty);
vif->node()->beginModification("create then branch");
vif->node()->thenBranch()->append(es);
vif->node()->endModification();
vif->thenBranch()->setUpdateNeeded(Visualization::Item::StandardUpdate);
target->scene()->addPostEventAction( new Interaction::SetCursorEvent{target, empty});

Notice how we explicitly request an update on the current item since we made a change to the tree.

The code for switching to the else branch when the then branch is selected is almost identical:

else if (vif->thenBranch()->itemOrChildHasFocus() && tab )
{
event->accept();
if (vif->node()->elseBranch()->size() > 0)
{
vif->node()->elseBranch()->at(0)});
}
else
{
auto empty = new OOModel::EmptyExpression{};
es->setExpression(empty);
vif->node()->beginModification("create else branch");
vif->node()->elseBranch()->append(es);
vif->node()->endModification();
vif->setUpdateNeeded(Visualization::Item::StandardUpdate);
target->scene()->addPostEventAction( new Interaction::SetCursorEvent{target, empty});
}

Finally, the last action we react on is pressing Backspace or Delete when the icon is selected, or pressing Backspace when the condition is selected:

else if (event->modifiers() == Qt::NoModifier
&& ((event->key() == Qt::Key_Backspace && vif->condition()->itemOrChildHasFocus())
|| ((event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete)
&& vif->icon()->itemOrChildHasFocus())))
{
event->accept();
removeFromList(target);
}

In both of these cases we completely remove the if statement from its enclosing list. Note that if the user presses backspace when editing the expression in the condition, our handler will only get notified if the cursor is at position 0 in the expression (right before the if statement's icon). In other cases the expression's own handler will handle this event by modifying the expression and we won't ever know about it.

Finally if the event wasn't handled here, pass it to the parent handler:

if (!event->isAccepted()) HStatement::keyPressEvent(target, event);

This is a typical reimplementation of a handler method. Look at other handlers in OOInteraction to see more examples.