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. In addition we will focus a little bit on what is explained in Handling geometry data within a plugin. The plugin will provide the following functions:
For this purpose we will make use of the following 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 w, a, s and d 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() { // Set active object to -1 activeObject_ = -1; // Set rotation axes to x, y and z axis axis_x_ = ACG::Vec3d(1.0, 0.0, 0.0); axis_y_ = ACG::Vec3d(0.0, 1.0, 0.0); // Register keys emit registerKey(Qt::Key_W, Qt::NoModifier, "Rotate object down"); emit registerKey(Qt::Key_S, Qt::NoModifier, "Rotate object up"); emit registerKey(Qt::Key_A, Qt::NoModifier, "Rotate object left"); emit registerKey(Qt::Key_D, Qt::NoModifier, "Rotate object right"); tool_ = new QWidget(); QSize size(300, 300); tool_->resize(size); // Create button that can be toggled // to (de)activate plugin's picking mode pickButton_ = new QPushButton(tr("Select object")); pickButton_->setCheckable(true); // Create label QLabel* label = new QLabel(); label->setText("(De)activate pick mode"); // Set label to be above the button QGridLayout* grid = new QGridLayout; grid->addWidget(label, 0, 0); grid->addWidget(pickButton_, 1, 0); tool_->setLayout(grid); // Connect button to slotButtonClicked() connect( pickButton_, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); // Add the Toolbox emit addToolbox( tr("Mouse and Key") , tool_ ); } // End initializePlugin
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() { // Add pickmode emit addPickMode("MouseAndKeyPlugin"); // Add context menu entry // Create submenu contextMenuEntry_ = new QMenu("Mouse and key plugin"); QAction* lastAction; // Add action to recently created menu lastAction = contextMenuEntry_->addAction( "Hide object" ); lastAction->setToolTip("Hide selected object"); lastAction->setStatusTip( lastAction->toolTip() ); // Finally insert created context submenu to OpenFlipper's context menu // We want our action to be visible for triangle and polygon meshes emit addContextMenuItem(contextMenuEntry_->menuAction() , DATA_TRIANGLE_MESH , CONTEXTOBJECTMENU ); emit addContextMenuItem(contextMenuEntry_->menuAction() , DATA_POLY_MESH , CONTEXTOBJECTMENU ); // Connect the created context menu entry to local function contextMenuItemSelected(QAction*) connect(contextMenuEntry_, SIGNAL(triggered(QAction*)), this, SLOT(contextMenuItemSelected(QAction*))); } // End pluginsInitialized
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()) { // Picking mode of our plugin shall be activated // set OpenFlipper's action mode to picking PluginFunctions::actionMode( Viewer::PickingMode ); // Now activate our picking mode PluginFunctions::pickMode( "MouseAndKeyPlugin" ); } else { // Picking mode shall be deactivated PluginFunctions::actionMode( Viewer::ExamineMode ); } } // End slotButtonClicked
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) { // Set button checked if pick mode is our // plugin's pick mode pickButton_->setChecked(_mode == "MouseAndKeyPlugin"); } // End slotPickModeChanged
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 ( PluginFunctions::pickMode() == "MouseAndKeyPlugin" && PluginFunctions::actionMode() == Viewer::PickingMode ) { // If double click has been performed if (_event->type() == QEvent::MouseButtonDblClick) { unsigned int node_idx, target_idx; OpenMesh::Vec3d hitPoint; // Get picked object's identifier if ( PluginFunctions::scenegraphPick(ACG::SceneGraph::PICK_ANYTHING,_event->pos(), node_idx, target_idx, &hitPoint) ) { BaseObjectData* object; // Get picked object if ( PluginFunctions::getPickedObject(node_idx, object) ) { // Show small dialog window 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(); // Set last selected object activeObject_ = node_idx; } else { emit log(LOGINFO, "Picking failed"); } } return; } // Continue traversing scene graph ACG::SceneGraph::MouseEventAction action(_event); PluginFunctions::traverse(action); } } // End slotMouseEvent
Next method is called whenever any of the keys w, s, a or d 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 w or s is pressed) or y axis (if a or d 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). As said in Handling geometry data within a plugin we have to inform OpenFlipper's core about the changes by calling BaseInterface::updatedObject(int).
void MouseAndKeyPlugin::slotKeyEvent( QKeyEvent* _event ) { BaseObjectData* object; // Get last clicked object (selected in pick mode) if ( PluginFunctions::getPickedObject(activeObject_, object) ) { // Switch pressed keys switch (_event->key()) { case Qt::Key_W: object->manipulatorNode()->loadIdentity(); object->manipulatorNode()->rotate(10.0, axis_x_); break; case Qt::Key_S : object->manipulatorNode()->loadIdentity(); object->manipulatorNode()->rotate(-10.0, axis_x_); break; case Qt::Key_A : object->manipulatorNode()->loadIdentity(); object->manipulatorNode()->rotate(10.0, axis_y_); break; case Qt::Key_D : object->manipulatorNode()->loadIdentity(); object->manipulatorNode()->rotate(-10.0, axis_y_); break; default: break; } // Perform rotation if ( object->dataType( DATA_TRIANGLE_MESH ) ) transformMesh(object->manipulatorNode()->matrix(), (*PluginFunctions::triMesh(object))); if ( object->dataType( DATA_POLY_MESH ) ) transformMesh(object->manipulatorNode()->matrix(), (*PluginFunctions::polyMesh(object))); // Tell core that object has been modified updatedObject(object->id()); // Update view emit updateView(); } else { emit log(LOGINFO, "No object has been selected to rotate! Select object first."); } } // End slotKeyEvent
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. As described in Handling geometry data within a plugin we now have to call BaseInterface::visibilityChanged() for the core to be informed about the changes.
void MouseAndKeyPlugin::contextMenuItemSelected(QAction* _action) { // Get object id from QAction object QVariant contextObject = _action->data(); int objectId = contextObject.toInt(); if (objectId == -1) { log(LOGINFO, "Could not get associated object id."); return; } // Get node associated to object id ACG::SceneGraph::BaseNode* node = ACG::SceneGraph::find_node( PluginFunctions::getSceneGraphRootNode(), objectId); // Return if node id was not valid if (!node) { log(LOGINFO, "Could not find associated object."); return; } BaseObjectData* obj; if (!PluginFunctions::getObject(objectId, obj)) return; // Hide object obj->hide(); // Tell core that object's visibility has changed visibilityChanged(objectId); } // End contextMenuItemSelected
We use the same project file as created in How to build the plugin with qmake (obsolete).
#ifndef MOUSEANDKEYPLUGIN_HH #define MOUSEANDKEYPLUGIN_HH #include <OpenFlipper/BasePlugin/BaseInterface.hh> #include <OpenFlipper/BasePlugin/MouseInterface.hh> #include <OpenFlipper/BasePlugin/PickingInterface.hh> #include <OpenFlipper/BasePlugin/KeyInterface.hh> #include <OpenFlipper/BasePlugin/ContextMenuInterface.hh> #include <OpenFlipper/BasePlugin/ToolboxInterface.hh> #include <OpenFlipper/BasePlugin/LoggingInterface.hh> #include <OpenFlipper/common/Types.hh> class MouseAndKeyPlugin: public QObject, BaseInterface, MouseInterface, PickingInterface, ContextMenuInterface, ToolboxInterface, KeyInterface, LoggingInterface { Q_OBJECT Q_INTERFACES(BaseInterface) Q_INTERFACES(MouseInterface) Q_INTERFACES(PickingInterface) Q_INTERFACES(KeyInterface) Q_INTERFACES(ContextMenuInterface) Q_INTERFACES(ToolboxInterface) Q_INTERFACES(LoggingInterface) signals: //BaseInterface void updateView(); void updatedObject(int _id); void visibilityChanged(int _id); //LoggingInterface void log(Logtype _type, QString _message); void log(QString _message); //ContextMenuInterface void addContextMenuItem(QAction* _action , ContextMenuType _type); void addContextMenuItem(QAction* _action , DataType _objectType , ContextMenuType _type ); //PickingInterface void addPickMode(const std::string _mode); void addHiddenPickMode(const std::string _mode); //KeyInterface void registerKey(int _key, Qt::KeyboardModifiers _modifiers, QString _description, bool _multiUse = false); // ToolboxInterface void addToolbox( QString _name , QWidget* _widget ); private slots: // BaseInterface void initializePlugin(); void pluginsInitialized(); //MouseInterface void slotMouseEvent( QMouseEvent* _event ); //KeyInterface void slotKeyEvent( QKeyEvent* _event ); //PickingInterface void slotPickModeChanged( const std::string& _mode); public: // BaseInterface QString name() {return (QString("Mouse and Keyboard Plugin"));}; QString description() {return (QString("Shows some basic mouse and key embedding"));}; private: // Transform geometry with given Matrix template <typename MeshT> void transformMesh(ACG::Matrix4x4d _mat , MeshT& _mesh ); // The context menu action QMenu* contextMenuEntry_; // The toolbox widget and the button in it QWidget* tool_; QPushButton* pickButton_; // Last picked object int activeObject_; // Rotation axes ACG::Vec3d axis_x_; ACG::Vec3d axis_y_; private slots: // Is called each time the button in the toolbox is clicked void slotButtonClicked(); // Is called if context menu item has been selected void contextMenuItemSelected(QAction* _action); public slots: QString version() { return QString("1.0"); }; }; #endif //MOUSEANDKEYPLUGIN_HH
//============================================================================= // // OpenFlipper // Copyright (C) 2008 by Computer Graphics Group, RWTH Aachen // www.openflipper.org // //----------------------------------------------------------------------------- // // License // // OpenFlipper is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // OpenFlipper is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with OpenFlipper. If not, see <http://www.gnu.org/licenses/>. // //----------------------------------------------------------------------------- // // $Revision: 5409 $ // $Author: kremer $ // $Date: 2009-03-23 12:47:01 +0100 (Mo, 23 Mär 2009) $ // //============================================================================= #include "MouseAndKeyPlugin.hh" #include <ObjectTypes/PolyMesh/PolyMesh.hh> #include <ObjectTypes/TriangleMesh/TriangleMesh.hh> #include "OpenFlipper/BasePlugin/PluginFunctions.hh" /* * Initialize plugin */ void MouseAndKeyPlugin::initializePlugin() { // Set active object to -1 activeObject_ = -1; // Set rotation axes to x, y and z axis axis_x_ = ACG::Vec3d(1.0, 0.0, 0.0); axis_y_ = ACG::Vec3d(0.0, 1.0, 0.0); // Register keys emit registerKey(Qt::Key_W, Qt::NoModifier, "Rotate object down"); emit registerKey(Qt::Key_S, Qt::NoModifier, "Rotate object up"); emit registerKey(Qt::Key_A, Qt::NoModifier, "Rotate object left"); emit registerKey(Qt::Key_D, Qt::NoModifier, "Rotate object right"); tool_ = new QWidget(); QSize size(300, 300); tool_->resize(size); // Create button that can be toggled // to (de)activate plugin's picking mode pickButton_ = new QPushButton(tr("Select object")); pickButton_->setCheckable(true); // Create label QLabel* label = new QLabel(); label->setText("(De)activate pick mode"); // Set label to be above the button QGridLayout* grid = new QGridLayout; grid->addWidget(label, 0, 0); grid->addWidget(pickButton_, 1, 0); tool_->setLayout(grid); // Connect button to slotButtonClicked() connect( pickButton_, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); // Add the Toolbox emit addToolbox( tr("Mouse and Key") , tool_ ); } // End initializePlugin /* * Is called after all plugins have been initialized */ void MouseAndKeyPlugin::pluginsInitialized() { // Add pickmode emit addPickMode("MouseAndKeyPlugin"); // Add context menu entry // Create submenu contextMenuEntry_ = new QMenu("Mouse and key plugin"); QAction* lastAction; // Add action to recently created menu lastAction = contextMenuEntry_->addAction( "Hide object" ); lastAction->setToolTip("Hide selected object"); lastAction->setStatusTip( lastAction->toolTip() ); // Finally insert created context submenu to OpenFlipper's context menu // We want our action to be visible for triangle and polygon meshes emit addContextMenuItem(contextMenuEntry_->menuAction() , DATA_TRIANGLE_MESH , CONTEXTOBJECTMENU ); emit addContextMenuItem(contextMenuEntry_->menuAction() , DATA_POLY_MESH , CONTEXTOBJECTMENU ); // Connect the created context menu entry to local function contextMenuItemSelected(QAction*) connect(contextMenuEntry_, SIGNAL(triggered(QAction*)), this, SLOT(contextMenuItemSelected(QAction*))); } // End pluginsInitialized /* * Is called when button in toolbox has been clicked */ void MouseAndKeyPlugin::slotButtonClicked() { if(pickButton_->isChecked()) { // Picking mode of our plugin shall be activated // set OpenFlipper's action mode to picking PluginFunctions::actionMode( Viewer::PickingMode ); // Now activate our picking mode PluginFunctions::pickMode( "MouseAndKeyPlugin" ); } else { // Picking mode shall be deactivated PluginFunctions::actionMode( Viewer::ExamineMode ); } } // End slotButtonClicked /* * Is called when pick mode is changed in OpenFlipper */ void MouseAndKeyPlugin::slotPickModeChanged(const std::string& _mode) { // Set button checked if pick mode is our // plugin's pick mode pickButton_->setChecked(_mode == "MouseAndKeyPlugin"); } // End slotPickModeChanged /* * Is called each time the mouse has moved or been clicked */ void MouseAndKeyPlugin::slotMouseEvent(QMouseEvent* _event) { if ( PluginFunctions::pickMode() == "MouseAndKeyPlugin" && PluginFunctions::actionMode() == Viewer::PickingMode ) { // If double click has been performed if (_event->type() == QEvent::MouseButtonDblClick) { unsigned int node_idx, target_idx; OpenMesh::Vec3d hitPoint; // Get picked object's identifier if ( PluginFunctions::scenegraphPick(ACG::SceneGraph::PICK_ANYTHING,_event->pos(), node_idx, target_idx, &hitPoint) ) { BaseObjectData* object; // Get picked object if ( PluginFunctions::getPickedObject(node_idx, object) ) { // Show small dialog window 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(); // Set last selected object activeObject_ = node_idx; } else { emit log(LOGINFO, "Picking failed"); } } return; } // Continue traversing scene graph ACG::SceneGraph::MouseEventAction action(_event); PluginFunctions::traverse(action); } } // End slotMouseEvent /* * Is called when a key on the keyboard is pressed */ void MouseAndKeyPlugin::slotKeyEvent( QKeyEvent* _event ) { BaseObjectData* object; // Get last clicked object (selected in pick mode) if ( PluginFunctions::getPickedObject(activeObject_, object) ) { // Switch pressed keys switch (_event->key()) { case Qt::Key_W: object->manipulatorNode()->loadIdentity(); object->manipulatorNode()->rotate(10.0, axis_x_); break; case Qt::Key_S : object->manipulatorNode()->loadIdentity(); object->manipulatorNode()->rotate(-10.0, axis_x_); break; case Qt::Key_A : object->manipulatorNode()->loadIdentity(); object->manipulatorNode()->rotate(10.0, axis_y_); break; case Qt::Key_D : object->manipulatorNode()->loadIdentity(); object->manipulatorNode()->rotate(-10.0, axis_y_); break; default: break; } // Perform rotation if ( object->dataType( DATA_TRIANGLE_MESH ) ) transformMesh(object->manipulatorNode()->matrix(), (*PluginFunctions::triMesh(object))); if ( object->dataType( DATA_POLY_MESH ) ) transformMesh(object->manipulatorNode()->matrix(), (*PluginFunctions::polyMesh(object))); // Tell core that object has been modified updatedObject(object->id()); // Update view emit updateView(); } else { emit log(LOGINFO, "No object has been selected to rotate! Select object first."); } } // End slotKeyEvent /* * Transform a mesh with the given transformation matrix * * _mat : transformation matrix * _mesh : the mesh */ 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(); // Iterator over all vertices and transform them by _mat // Update normals for (; v_it != v_end; ++v_it) { _mesh.set_point(v_it, (typename MeshT::Point) _mat.transform_point( (OpenMesh::Vec3d)(_mesh.point(v_it)))); _mesh.set_normal(v_it, (typename MeshT::Point) _mat.transform_vector( (OpenMesh::Vec3d)(_mesh.normal(v_it)))); } typename MeshT::FaceIter f_it = _mesh.faces_begin(); typename MeshT::FaceIter f_end = _mesh.faces_end(); // Iterate over all faces and update face normals for (; f_it != f_end; ++f_it) _mesh.set_normal(f_it, (typename MeshT::Point) _mat.transform_vector( (OpenMesh::Vec3d)(_mesh.normal(f_it)))); } // End transformMesh /* * Is called when context menu entry has been clicked */ void MouseAndKeyPlugin::contextMenuItemSelected(QAction* _action) { // Get object id from QAction object QVariant contextObject = _action->data(); int objectId = contextObject.toInt(); if (objectId == -1) { log(LOGINFO, "Could not get associated object id."); return; } // Get node associated to object id ACG::SceneGraph::BaseNode* node = ACG::SceneGraph::find_node( PluginFunctions::getSceneGraphRootNode(), objectId); // Return if node id was not valid if (!node) { log(LOGINFO, "Could not find associated object."); return; } BaseObjectData* obj; if (!PluginFunctions::getObject(objectId, obj)) return; // Hide object obj->hide(); // Tell core that object's visibility has changed visibilityChanged(objectId); } // End contextMenuItemSelected Q_EXPORT_PLUGIN2( mouseandkeyplugin , MouseAndKeyPlugin );