Overview
Mouse interaction (especially object picking) and keyboard shortcuts form essential and often used functionality in OpenFlipper. In this tutorial we will learn how to use picking modes, context menus and simple keyboard shortcuts. The plugin will provide the following functions:
- Hide object by right-clicking at it and selecting our defined action
- Select an object by entering user defined pick mode and double-clicking at object in the scene
- Rotate selected object by hitting the j,k,h,l keys on the keyboard
For this purpose we will make use of the following Plugin Interfaces
Since we outlined the details of overriding the BaseInterface methods in the previous tutorials (A simple plugin and Implementing a mesh smoother plugin) we can directly switch over to what happens within these methods. When initializing our plugin we set the active object identifier to -1 since no object has been selected yet. We initialize the axis vectors that we'll need for the rotation later on. Then we tell OpenFlipper that we will use the j, k, h and l key on the keyboard (so it'll call slotKeyEvent() each time one of the keys has been pressed). Note: OpenFlipper will show up a warning message in the log widget if the desired keys are already assigned to another plugin or core function.
void MouseAndKeyPlugin::initializePlugin() {
emit registerKey(Qt::Key_J, Qt::NoModifier, "Rotate object down");
emit registerKey(Qt::Key_K, Qt::NoModifier, "Rotate object up");
emit registerKey(Qt::Key_H, Qt::NoModifier, "Rotate object left");
emit registerKey(Qt::Key_L, Qt::NoModifier, "Rotate object right");
tool_ = new QWidget();
QSize size(300, 300);
tool_->resize(size);
pickButton_ = new QPushButton(tr("Select object"));
pickButton_->setCheckable(true);
QLabel* label = new QLabel();
label->setText("(De)activate pick mode");
QGridLayout* grid = new QGridLayout;
grid->addWidget(label, 0, 0);
grid->addWidget(pickButton_, 1, 0);
tool_->setLayout(grid);
connect(pickButton_, SIGNAL(clicked()), this, SLOT(slotButtonClicked()));
emit addToolbox(tr("Mouse and Key"), tool_);
}
If all plugins have been initialized, we add our own pick mode named "MouseAndKeyPlugin" to OpenFlipper and create the context menu entry (which is actually a QMenu object containing one (or generally multiple) object(s) of type QAction) which we connect to contextMenuItemSelected().
void MouseAndKeyPlugin::pluginsInitialized() {
emit addPickMode("MouseAndKeyPlugin");
contextMenuEntry_ = new QMenu("Mouse and key plugin");
QAction* lastAction;
lastAction = contextMenuEntry_->addAction( "Hide object" );
lastAction->setToolTip("Hide selected object");
lastAction->setStatusTip( lastAction->toolTip() );
connect(contextMenuEntry_, SIGNAL(triggered(QAction*)), this, SLOT(contextMenuItemSelected(QAction*)));
}
In intializeToolbox() we create a simple toolbox containing a label and a push-button. We connect the button to our method slotButtonClicked() which will then be called each time the button is clicked.
Now each time the button in our toolbox is clicked we want to activate the picking mode, such that if the button is checked pick mode "MouseAndKeyPlugin" that we defined at the beginning will be active and OpenFlipper switches over from ExamineMode to PickingMode (see PluginFunctions for details). Clicking once more at the button will return to ExamineMode (in which the user can navigate through the scene).
void MouseAndKeyPlugin::slotButtonClicked() {
if(pickButton_->isChecked()) {
} else {
}
}
If the pick mode has been changed externally, we want our button in the toolbox to appear pressed (or unpressed respectively). _mode holds the name of the currently selected pick mode.
void MouseAndKeyPlugin::slotPickModeChanged(const std::string& _mode) {
pickButton_->setChecked(_mode == "MouseAndKeyPlugin");
}
In the next method we describe how mouse actions are to be handled by the plugin. We want our plugin only to react to mouse events if our pick mode is active otherwise don't do anything. If OpenFlipper is in PickingMode and the currently active pick mode is "MouseAndKeyPlugin" we try to get the object on that the user has double clicked. If the object can be found, we'll show up a dialog window displaying the picked object's identifier and assign it to the member variable that holds the active object. Otherwise we display "Picking failed" in the log widget. Note that the mouse event has to be traversed through the rest of the scene graph after intercepting and treating the desired mouse action.
void MouseAndKeyPlugin::slotMouseEvent(QMouseEvent* _event) {
if (_event->type() == QEvent::MouseButtonDblClick) {
size_t node_idx, target_idx;
QDialog* dlg = new QDialog;
QGridLayout* grid = new QGridLayout;
QLabel* label = new QLabel;
QString str = QString("You selected object ");
str += QString::number(node_idx);
label->setText(str);
grid->addWidget(label, 0,0);
dlg->setLayout(grid);
dlg->show();
activeObject_ = node_idx;
}
else {
emit log(
LOGINFO,
"Picking failed");
}
}
return;
}
}
}
Next method is called whenever any of the keys j, k, h or l is pressed. If an object has been selected (accordingly the member variable activeObject_ holds a valid objects identifier -as described before-) we try to get its handle by calling PluginFunctions::getPickedObject(). We then set the rotation matrix of the selected object's transform node (manipulatorNode) to hold a matrix that describes a rotation around the x (if j or k is pressed) or y axis (if h or l is pressed) by +/- 10 degrees. We then call the method transformMesh and pass the recently calculated matrix and a handle to the mesh (triangle or polygon). We have to inform OpenFlipper's core about the changes by emitting the updatedObject signal and specifying that the geometry has changed.
void MouseAndKeyPlugin::slotKeyEvent( QKeyEvent* _event ) {
switch (_event->key())
{
case Qt::Key_J:
object->manipulatorNode()->rotate(10.0, axis_x_);
break;
case Qt::Key_K :
object->manipulatorNode()->loadIdentity();
object->manipulatorNode()->rotate(-10.0, axis_x_);
break;
case Qt::Key_H :
object->manipulatorNode()->loadIdentity();
object->manipulatorNode()->rotate(10.0, axis_y_);
break;
case Qt::Key_L :
object->manipulatorNode()->loadIdentity();
object->manipulatorNode()->rotate(-10.0, axis_y_);
break;
default:
break;
}
emit updateView();
} else {
emit log(
LOGINFO,
"No object has been selected to rotate! Select object first.");
}
}
This template method transforms the given mesh:
Last but not least, the method that is called each time our context menu has been clicked. We get the object's id on which the user has performed a right click from the action data. Then we try to get the node and its BaseObjectData handle. If successfully passed to this point we hide the object by calling its hide() method.
void MouseAndKeyPlugin::contextMenuItemSelected(QAction* _action) {
QVariant contextObject = _action->data();
int objectId = contextObject.toInt();
if (objectId == -1) {
log(
LOGINFO,
"Could not get associated object id.");
return;
}
if (!node) {
log(
LOGINFO,
"Could not find associated object.");
return;
}
return;
}
The complete source code of this example
We use the same project file as before.
MouseAndKeyPlugin.hh
#ifndef MOUSEANDKEYPLUGIN_HH
#define MOUSEANDKEYPLUGIN_HH
#include <QPushButton>
#include <QLabel>
#include <QGridLayout>
#include <QMenu>
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.OpenFlipper.Plugins.examples.MouseAndKeyPlugin")
signals:
void updateView();
void updatedObject(
int _identifier,
const UpdateType& _type);
void log(
Logtype _type, QString _message);
void log(QString _message);
void addPickMode(const std::string& _mode);
void addHiddenPickMode(const std::string& _mode);
void registerKey(int _key, Qt::KeyboardModifiers _modifiers, QString _description, bool _multiUse = false);
void addToolbox(QString _name, QWidget* _widget);
private slots:
void initializePlugin();
void pluginsInitialized();
void slotMouseEvent( QMouseEvent* _event );
void slotKeyEvent( QKeyEvent* _event );
void slotPickModeChanged( const std::string& _mode);
public:
QString
name() {
return (QString(
"Mouse and Keyboard Plugin"));};
QString
description() {
return (QString(
"Shows some basic mouse and key embedding"));};
private:
template <typename MeshT>
QMenu* contextMenuEntry_;
QWidget* tool_;
QPushButton* pickButton_;
int activeObject_;
private slots:
void slotButtonClicked();
void contextMenuItemSelected(QAction* _action);
public slots:
QString version() { return QString("1.0"); };
};
#endif //MOUSEANDKEYPLUGIN_HH
MouseAndKeyPlugin.cc
#include "MouseAndKeyPlugin.hh"
MouseAndKeyPlugin::MouseAndKeyPlugin() :
contextMenuEntry_(0),
tool_(0),
pickButton_(0),
activeObject_(-1),
axis_x_(
ACG::Vec3d(1.0, 0.0, 0.0)),
axis_y_(
ACG::Vec3d(0.0, 1.0, 0.0))
{
}
void MouseAndKeyPlugin::initializePlugin() {
emit registerKey(Qt::Key_J, Qt::NoModifier, "Rotate object down");
emit registerKey(Qt::Key_K, Qt::NoModifier, "Rotate object up");
emit registerKey(Qt::Key_H, Qt::NoModifier, "Rotate object left");
emit registerKey(Qt::Key_L, Qt::NoModifier, "Rotate object right");
tool_ = new QWidget();
QSize size(300, 300);
tool_->resize(size);
pickButton_ = new QPushButton(tr("Select object"));
pickButton_->setCheckable(true);
QLabel* label = new QLabel();
label->setText("(De)activate pick mode");
QGridLayout* grid = new QGridLayout;
grid->addWidget(label, 0, 0);
grid->addWidget(pickButton_, 1, 0);
tool_->setLayout(grid);
connect(pickButton_, SIGNAL(clicked()), this, SLOT(slotButtonClicked()));
emit addToolbox(tr("Mouse and Key"), tool_);
}
void MouseAndKeyPlugin::pluginsInitialized() {
emit addPickMode("MouseAndKeyPlugin");
contextMenuEntry_ = new QMenu("Mouse and key plugin");
QAction* lastAction;
lastAction = contextMenuEntry_->addAction( "Hide object" );
lastAction->setToolTip("Hide selected object");
lastAction->setStatusTip( lastAction->toolTip() );
connect(contextMenuEntry_, SIGNAL(triggered(QAction*)), this, SLOT(contextMenuItemSelected(QAction*)));
}
void MouseAndKeyPlugin::slotButtonClicked() {
if(pickButton_->isChecked()) {
} else {
}
}
void MouseAndKeyPlugin::slotPickModeChanged(const std::string& _mode) {
pickButton_->setChecked(_mode == "MouseAndKeyPlugin");
}
void MouseAndKeyPlugin::slotMouseEvent(QMouseEvent* _event) {
if (_event->type() == QEvent::MouseButtonDblClick) {
size_t node_idx, target_idx;
QDialog* dlg = new QDialog;
QGridLayout* grid = new QGridLayout;
QLabel* label = new QLabel;
QString str = QString("You selected object ");
str += QString::number(node_idx);
label->setText(str);
grid->addWidget(label, 0,0);
dlg->setLayout(grid);
dlg->show();
activeObject_ = node_idx;
}
else {
emit log(
LOGINFO,
"Picking failed");
}
}
return;
}
}
}
void MouseAndKeyPlugin::slotKeyEvent( QKeyEvent* _event ) {
switch (_event->key())
{
case Qt::Key_J:
object->manipulatorNode()->rotate(10.0, axis_x_);
break;
case Qt::Key_K :
object->manipulatorNode()->loadIdentity();
object->manipulatorNode()->rotate(-10.0, axis_x_);
break;
case Qt::Key_H :
object->manipulatorNode()->loadIdentity();
object->manipulatorNode()->rotate(10.0, axis_y_);
break;
case Qt::Key_L :
object->manipulatorNode()->loadIdentity();
object->manipulatorNode()->rotate(-10.0, axis_y_);
break;
default:
break;
}
} else {
emit log(
LOGINFO,
"No object has been selected to rotate! Select object first.");
}
}
template<typename MeshT>
void MouseAndKeyPlugin::transformMesh(
ACG::Matrix4x4d _mat, MeshT& _mesh) {
typename MeshT::VertexIter v_it = _mesh.vertices_begin();
typename MeshT::VertexIter v_end = _mesh.vertices_end();
for (; v_it != v_end; ++v_it) {
}
typename MeshT::FaceIter f_it = _mesh.faces_begin();
typename MeshT::FaceIter f_end = _mesh.faces_end();
for (; f_it != f_end; ++f_it)
}
void MouseAndKeyPlugin::contextMenuItemSelected(QAction* _action) {
QVariant contextObject = _action->data();
int objectId = contextObject.toInt();
if (objectId == -1) {
log(
LOGINFO,
"Could not get associated object id.");
return;
}
if (!node) {
log(
LOGINFO,
"Could not find associated object.");
return;
}
return;
}