[Tuto04SignalSlot] Signal-slot communication

The fourth tutorial explains the communication mechanism with signals and slots.

../../_images/tuto04SignalSlot.png

Prerequisites

Before to read this tutorial, you should have seen :

Structure

Properties.cmake

This file describes the project information and requirements :

set( NAME Tuto04SignalSlot )
set( VERSION 0.1 )
set( TYPE APP )
set( DEPENDENCIES  )
set( REQUIREMENTS
    dataReg
    servicesReg
    gui
    guiQt
    io
    ioVTK
    uiIO
    visuVTKQt
    vtkSimpleMesh # contains a visualization service of mesh.
    launcher
    appXml
)

bundleParam(appXml PARAM_LIST config PARAM_VALUES tutoSignalSlotConfig)

Note

The Properties.cmake file of the application is used by CMake to compile the application but also to generate the profile.xml: the file used to launch the application.

plugin.xml

This file is in the rc/ directory of the application. It defines the services to run.

<plugin id="Tuto04SignalSlot" version="@DASH_VERSION@">

    <requirement id="servicesReg" />

    <extension implements="::fwServices::registry::AppConfig">
        <id>tutoSignalSlotConfig</id>
        <config>

            <!-- The main data object is ::fwData::Mesh. -->
            <object uid="mesh" type="::fwData::Mesh" />

            <service uid="myFrame" type="::gui::frame::SDefaultFrame">
                <gui>
                    <frame>
                        <name>tutoSignalSlot</name>
                        <icon>@BUNDLE_PREFIX@/Tuto04SignalSlot_0-1/tuto.ico</icon>
                        <minSize width="720" height="600" />
                    </frame>
                    <menuBar />
                </gui>
                <registry>
                    <menuBar sid="myMenuBar" start="yes" />
                    <view sid="myDefaultView" start="yes" />
                </registry>
            </service>

            <service uid="myMenuBar" type="::gui::aspect::SDefaultMenuBar">
                <gui>
                    <layout>
                        <menu name="File" />
                    </layout>
                </gui>
                <registry>
                    <menu sid="myMenuFile" start="yes" />
                </registry>
            </service>

            <!--
                Default view service:
                This service defines the view layout. The type '::fwGui::CardinalLayoutManager' represents a main
                central view and other views at the 'right', 'left', 'bottom' or 'top'.
                Here the application contains a central view at the right.

                Each <view> declared into the <layout> tag, must have its associated <view> into the <registry> tag.
                A minimum window height and a width are given to the two non-central views.
            -->
            <service uid="myDefaultView" type="::gui::view::SDefaultView">
                <gui>
                    <layout type="::fwGui::CardinalLayoutManager">
                        <view caption="Rendering 1" align="center" />
                        <view caption="Rendering 2" align="right" minWidth="400" minHeight="100" />
                        <view caption="Rendering 3" align="right" minWidth="400" minHeight="100" />
                    </layout>
                </gui>
                <registry>
                    <view sid="myRendering1" start="yes" />
                    <view sid="myRendering2" start="yes" />
                    <view sid="myRendering3" start="yes" />
                </registry>
            </service>

            <service uid="myMenuFile" type="::gui::aspect::SDefaultMenu">
                <gui>
                    <layout>
                        <menuItem name="Open file" shortcut="Ctrl+O" />
                        <separator />
                        <menuItem name="Quit" specialAction="QUIT" shortcut="Ctrl+Q" />
                    </layout>
                </gui>
                <registry>
                    <menuItem sid="actionOpenFile" start="yes" />
                    <menuItem sid="actionQuit" start="yes" />
                </registry>
            </service>

            <service uid="actionOpenFile" type="::gui::action::SStarter">
                <start uid="myReaderPathFile" />
            </service>

            <service uid="actionQuit" type="::gui::action::SQuit" />

            <service uid="myReaderPathFile" type="::uiIO::editor::SIOSelector">
                <inout key="target" uid="mesh" />
                <type mode="reader" /><!-- mode is optional (by default it is "reader") -->
            </service>

            <!--
                Visualization services:
                We have three rendering service representing a 3D scene displaying the loaded mesh. The scene are
                shown in the windows defines in 'view' service.
            -->
            <service uid="myRendering1" type="::vtkSimpleMesh::SRenderer" autoConnect="yes" >
                <in key="mesh" uid="mesh" />
            </service>
            <service uid="myRendering2" type="::vtkSimpleMesh::SRenderer" autoConnect="yes" >
                <in key="mesh" uid="mesh" />
            </service>
            <service uid="myRendering3" type="::vtkSimpleMesh::SRenderer" autoConnect="yes" >
                <in key="mesh" uid="mesh" />
            </service>

            <!--
                Each 3D scene owns a 3D camera that can be moved by clicking in the scene.
                - When the camera move, a signal 'camUpdated' is emitted with the new camera information (position,
                focal, view up).
                - To update the camera without clicking, you could call the slot 'updateCamPosition'

                Here, we connect each rendering service signal 'camUpdated' to the others service slot
                'updateCamPosition', so the cameras are synchronized in each scene.
            -->
            <connect>
                <signal>myRendering1/camUpdated</signal>
                <slot>myRendering2/updateCamPosition</slot>
                <slot>myRendering3/updateCamPosition</slot>
            </connect>

            <connect>
                <signal>myRendering2/camUpdated</signal>
                <slot>myRendering1/updateCamPosition</slot>
                <slot>myRendering3/updateCamPosition</slot>
            </connect>

            <connect>
                <signal>myRendering3/camUpdated</signal>
                <slot>myRendering2/updateCamPosition</slot>
                <slot>myRendering1/updateCamPosition</slot>
            </connect>

            <start uid="myFrame" />

        </config>
    </extension>

</plugin>

You can also group the signals and all the slots together.

<connect>
    <signal>myRenderingTuto1/camUpdated</signal>
    <signal>myRenderingTuto2/camUpdated</signal>
    <signal>myRenderingTuto3/camUpdated</signal>

    <slot>myRenderingTuto1/updateCamPosition</slot>
    <slot>myRenderingTuto2/updateCamPosition</slot>
    <slot>myRenderingTuto3/updateCamPosition</slot>
</proxy>

Tip

You can remove a connection to see that a camera in the scene is no longer synchronized.

Signal and slot creation

RendererService.hpp

class VTKSIMPLEMESH_CLASS_API RendererService : public fwRender::IRender
{
public:
    // .....

    typedef ::boost::shared_array< double > SharedArray;

    typedef ::fwCom::Signal< void (SharedArray, SharedArray, SharedArray) > CamUpdatedSignalType;

    // .....

    /// This method is call when the VTK camera position is modified.
    /// It notifies the new camera position.
    void notifyCamPositionUpdated();

private:

    /// Slot: receives new camera information (position, focal, viewUp).
    /// Update camera with new information.
    void updateCamPosition(SharedArray positionValue,
                           SharedArray focalValue,
                           SharedArray viewUpValue);

    // ....

    /// Signal emitted when camera position is updated.
    CamUpdatedSignalType::sptr m_sigCamUpdated;
}

RendererService.cpp

RendererService::RendererService() throw()
{
    m_sigCamUpdated = newSignal<CamUpdatedSignalType>("camUpdated");

    newSlot("updateCamPosition", &RendererService::updateCamPosition, this);
}

//-----------------------------------------------------------------------------

void RendererService::updateCamPosition(SharedArray positionValue,
                                        SharedArray focalValue,
                                        SharedArray viewUpValue)
{
    vtkCamera* camera = m_render->GetActiveCamera();

    // Update the vtk camera
    camera->SetPosition(positionValue.get());
    camera->SetFocalPoint(focalValue.get());
    camera->SetViewUp(viewUpValue.get());
    camera->SetClippingRange(0.1, 1000000);

    // Render the scene
    m_interactorManager->getInteractor()->Render();
}


//-----------------------------------------------------------------------------

void RendererService::notifyCamPositionUpdated()
{
    vtkCamera* camera = m_render->GetActiveCamera();

    SharedArray position = SharedArray(new double[3]);
    SharedArray focal    = SharedArray(new double[3]);
    SharedArray viewUp   = SharedArray(new double[3]);

    std::copy(camera->GetPosition(), camera->GetPosition()+3, position.get());
    std::copy(camera->GetFocalPoint(), camera->GetFocalPoint()+3, focal.get());
    std::copy(camera->GetViewUp(), camera->GetViewUp()+3, viewUp.get());

    {
        // The Blocker blocks the connection between the "camUpdated" signal and the
        // "updateCamPosition" slot for this instance of service.
        // The block is release at the end of the scope.
        ::fwCom::Connection::Blocker block(
                            m_sigCamUpdated->getConnection(m_this->slot("updateCamPosition")));

        // Asynchronous emit of "camUpdated" signal
        m_sigCamUpdated->asyncEmit (position, focal, viewUp);
    }
}

//-----------------------------------------------------------------------------

// ......

Run

To run the application, you must call the following line into the install or build directory:

bin/fwlauncher Bundles/Tuto04SignalSlot_0-1/profile.xml