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


前言

在使用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;
}

总结

第一次比较深入地去看源码,希望有一天也能写出这样的好代码~

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ViewRootImpl 是 Android 系统中负责协调视图层级结构与输入事件的重要。它负责处理应用程序中所有的输入事件,并将它们发送到正确的 View 上进行处理。 下面是 ViewRootImpl 处理事件的源码: ```java private void deliverPointerEvent(MotionEvent event) { if (event.isTouchEvent()) { boolean handled = false; final int action = event.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || actionMasked == MotionEvent.ACTION_POINTER_DOWN || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { // Check for interception. intercepted = dispatchTransformedTouchEvent(event, false, null, TouchTarget.ALL_POINTER_IDS); } else { intercepted = false; } if (!intercepted && mFirstTouchTarget == null) { // No touch targets so send to the root. handled = super.dispatchTouchEvent(event); } if (!handled && mFirstTouchTarget == null) { // Send to touch targets. TouchTarget target = mFirstTouchTarget; while (target != null) { if (target.pointerIdBits == TouchTarget.ALL_POINTER_IDS) { handled = target.child .dispatchTouchEvent(event); if (handled) { break; } } target = target.next; } } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } recyclePointerEvent(event); } } ``` 该方法主要是判断事件型,然后根据事件型分别处理: 1. 如果事件是触摸事件,那么会先检查是否需要拦截事件,如果需要拦截则不会继续往下分发事件。 2. 如果事件是按下事件,或者当前有触摸目标,则会先分发到 ViewRootImpl 自身处理。 3. 如果 ViewRootImpl 自身没有处理该事件,则会将事件分发到当前的触摸目标中,直到有一个触摸目标处理了该事件为止。 其中,触摸目标是指当前被触摸的 View,它是 ViewRootImpl 根据事件坐标找到的最上层的 View。在该方法中,触摸目标是通过遍历 TouchTarget 链表来查找的。每个 TouchTarget 包含了一个触摸目标 View 和该 View 关联的触摸点标识符(pointerIdBits)。 该方法中还包括了一个事件回收的操作,即将 MotionEvent 对象回收到对象池中。这是 Android 系统中常用的一种优化内存的方式,可以避免频繁地创建和销毁对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值