【librviz源码解读】Tool类的添加和鼠标事件处理

发布于:2022-08-07 ⋅ 阅读:(731) ⋅ 点赞:(0)


前言

在使用rviz库编写自己的工具的时候,需要使用这样的语句,将工具注册到pluginlib。

#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(robot_upper_plugins::RouteGoalTool, rviz::Tool )

需要启用工具时,需要用注册时的名称来添加到tool_manager,类似于:

addTool("robot_upper_plugins/RouteGoalTool");

让我们来看看librviz是怎么从插件库中加载和启用工具类的。


1 Tool类的添加和鼠标事件处理

1.1 已注册工具类的添加

工具类添加到插件库后,当我们在rviz中点击添加工具,对应到代码里,就是调用了ToolManager类的addTool方法,用插件库内的唯一命名来创建工具,像这样:

void ToolManager::initialize()
{
	// 这些都是rviz中的默认插件
	addTool( "rviz/MoveCamera" );
	addTool( "rviz/Interact" );
	addTool( "rviz/Select" );
	addTool( "rviz/SetInitialPose" );
	addTool( "rviz/SetGoal" );
}

visualization_manager.cpp内:

当VisualizationManager实例化ToolManager后,成为其上下文;

tool_manager_ = new ToolManager( this );

ToolManager.cpp

addTool():根据插件class_id从工厂创建一个tool,将VisualizationManager作为context传入tool->initialize()。最后发出toolAdded(tool)信号;

ToolManager::ToolManager( DisplayContext* context ) : context_( context ) {}

Tool* ToolManager::addTool( const QString& class_id )
{
    Tool* tool = factory_->make( class_id, &error );
    tools_.append( tool );
    tool->initialize( context_ );

    Q_EMIT toolAdded( tool );
}

visualization_frame.cpp

addTool( Tool* tool ):toolAdded()的槽,当接收到toolAdded()信号,创建一个qaction到toolbar_actions_组里;

当这个qaction被触发时,调用ToolManager()->setCurrentToo()

connect( tool_man, SIGNAL( toolAdded( Tool* )), this, SLOT( addTool( Tool* )));

void VisualizationFrame::addTool( Tool* tool )
{
    QAction* action = new QAction( tool->getName(), toolbar_actions_ );
    toolbar_->insertAction( add_tool_action_, action );
    tool_to_action_map_[ tool ] = action;
}

toolbar_actions_ = new QActionGroup( this );
connect( toolbar_actions_, SIGNAL( triggered( QAction* )), this, SLOT( onToolbarActionTriggered( QAction* )));

void VisualizationFrame::onToolbarActionTriggered( QAction* action )
{
    Tool* tool = action_to_tool_map_[ action ];
    manager_->getToolManager()->setCurrentTool( tool );
}

ToolManager.cpp

setCurrentTool( Tool* tool ):设置当前工具后,发出toolChanged信号通知VisualizationManager;

void ToolManager::setCurrentTool( Tool* tool )
{ 
    ...
    Q_EMIT toolChanged( current_tool_ );
}

visualization_manager.cpp内:

connect( tool_manager_, SIGNAL( toolChanged( Tool* ) ), this, SLOT( onToolChanged( Tool* ) ));
void VisualizationManager::onToolChanged( Tool* tool ) {} /* 然鹅未启用 */

1.2 鼠标事件的传递和处理

我们知道,rviz::Tool类中有一个processMouseEvent( ViewportMouseEvent& event ),用于处理工具的鼠标事件,这是Tool类的核心功能,让我们来看一下它是怎么被调用的。

render_panel.cpp中:

fake_mouse_move_event_timer_:定时器,定时触发sendMouseMoveEvent();

sendMouseMoveEvent():创建虚拟的鼠标事件fake_event并作为参数调用onRenderWindowMouseEvents( &fake_event ),这也就是按照一定周期(33ms)发送鼠标事件;

onRenderWindowMouseEvents( QMouseEvent* event ):获取鼠标的当前位置,创建ViewportMouseEvent,传递给上下文(就是VisualizationManager)的handleMouseEvent( QMouseEvent* event )进行处理;

connect( fake_mouse_move_event_timer_, SIGNAL( timeout() ), this, SLOT( sendMouseMoveEvent() ));
fake_mouse_move_event_timer_->start( 33 /*milliseconds*/ );

void RenderPanel::sendMouseMoveEvent()
{
    ...
        
	QMouseEvent fake_event( QEvent::MouseMove,
                            mouse_rel_widget,
                            QApplication::mouseButtons(),
                            QApplication::keyboardModifiers() );
    onRenderWindowMouseEvents( &fake_event );
}

void RenderPanel::onRenderWindowMouseEvents( QMouseEvent* event )
{
    int last_x = mouse_x_;
    int last_y = mouse_y_;

    if (context_)
    {
        setFocus( Qt::MouseFocusReason );

        ViewportMouseEvent vme(this, getViewport(), event, last_x, last_y);
        context_->handleMouseEvent(vme);
        event->accept();
    }
    
    ...
 }

visualization_manager.cpp中:

handleMouseEvent( vme )从ToolManager获取当前工具,调用current_tool->processMouseEvent( _vme );(终于找到你了!)

void VisualizationManager::handleMouseEvent( const ViewportMouseEvent& vme )
{
    //process pending mouse events
    Tool* current_tool = tool_manager_->getCurrentTool();

    if( current_tool )
    {
        ViewportMouseEvent _vme = vme;
        flags = current_tool->processMouseEvent( _vme );
        vme.panel->setCursor( current_tool->getCursor() );
    }
    
    ...
}

1.3 实现工具类的无注册调用

上面我们了解了已注册的工具类如何加载和调用,那么能不能在程序中引入插件类的头文件,实现我们自己实例化的插件类的添加和调用呢,当然是可以的,而且由于librviz优秀的代码设计,这实现起来很容易。

tool.h中:

虚函数,提供给子类重新实现,实现多样的鼠标事件处理。

virtual int processMouseEvent( ViewportMouseEvent& event ) { return 0; }

举个例子,在move_tool.cpp中:

int MoveTool::processKeyEvent( QKeyEvent* event, RenderPanel* panel )
{
    if( context_->getViewManager()->getCurrent() )
    {
        context_->getViewManager()->getCurrent()->handleKeyEvent( event, panel );
    }
    return Render;
}

自己创建不被注册的工具类,想要其被启用,并且处理鼠标事件,关键在于这两点:

  • Tool::initialize( VisualizationManager ); // 实例化你的工具类,调用initialize函数,把render_panel的VisualizationManager传进去。
  • ToolManager::setCurrentTool( Tool ); // 设置为ToolManager的当前工具后,VisualizationManager便可以传递鼠标事件给工具了,让它进行处理了!

举个例子:

void initialize()
{
    // 创建3D面板、中央管理器和tool管理器
    render_panel_ = new rviz::RenderPanel();
    visualization_manager_ = new rviz::VisualizationManager(render_panel_);
    tool_manager_ = visualization_manager_->getToolManager();
    render_panel_->initialize(visualization_manager_->getSceneManager(), visualization_manager_);
    visualization_manager_->initialize();
    visualization_manager_->startUpdate();

    // 初始化工具 
    route_goal_tool_ = new rviz_plugins::RouteGoalTool();
    route_goal_tool_->initialize(visualization_manager_);
}

// 启动工具
void startTool()
{
    tool_manager_->setCurrentTool(route_goal_tool_);
}

当然,虽然这样可以实现,但也有很明显的缺点,ToolManager::addTool()中除了创建工具,还将它们添加进tools_列表,shortkey_to_tool_map_列表,一些操作,如删除工具、属性更改等都是依赖tools_列表实现,快捷键映射则依赖于shortkey_to_tool_map_列表。而没有注册的插件,这些都需要自己管理,容易造成混乱。

Tool* ToolManager::addTool( const QString& class_id )
{
  Tool* tool = factory_->make( class_id, &error );

  tools_.append( tool );
  tool->setName( addSpaceToCamelCase( factory_->getClassName( class_id )));
  tool->setIcon( factory_->getIcon( class_id ) );
  tool->initialize( context_ );

  if( tool->getShortcutKey() != '\0' )
  {
    uint key;
    QString str = QString( tool->getShortcutKey() );

    if( toKey( str, key ) )
    {
      shortkey_to_tool_map_[ key ] = tool;
    }
  }

  Property* container = tool->getPropertyContainer();
  connect( container, SIGNAL( childListChanged( Property* )), this, SLOT( updatePropertyVisibility( Property* )));
  updatePropertyVisibility( container );

  Q_EMIT configChanged();

  return tool;
}

总结

第一次比较深入地去看源码,不得不说人家写得真好呀,希望有一天我也能写出这么好的代码~


网站公告

今日签到

点亮在社区的每一天
去签到