/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

// -- Core stuff
#include "Action.h"
#include "FrameOfReference.h"
#include "RendererWidget.h"
#include "InteractiveViewer.h"
#include "SliderSpinBoxWidget.h"
#include "TransformationManager.h"
#include "Application.h"
#include "MeshComponent.h"
#include "ImageComponent.h"
#include "ArbitrarySingleImageComponent.h"
#include "PropertyObject.h"
#include "ScreenshotFormatInfo.h"
#include "Log.h"

// -- Qt stuff
#include <QVBoxLayout>
#include <QKeyEvent>
#include <QFileDialog>
#include <QWhatsThis>
#include <QColorDialog>
#include <QToolBar>
#include <QSettings>
#include <QWidgetAction>
#include <QTextStream>
#include <QSplitter>
#include <QDateTime>
#include <QInputDialog>
#include <QComboBox>
#include <QActionGroup>

// -- vtk stuff
// disable warning generated by clang about the surrounded headers
#include "CamiTKDisableWarnings"
#include <vtkDataSetMapper.h>
#include <vtkProperty.h>
#include <vtkPointPicker.h>
#include <vtkCamera.h>
#include <vtkCellPicker.h>
#include <vtkEventQtSlotConnect.h>
#include <vtkAreaPicker.h>
#include <vtkRenderedAreaPicker.h>
#include <vtkPropPicker.h>
#include <vtkHardwareSelector.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkImageProperty.h>
#include "CamiTKReEnableWarnings"

#include <vtkImageActor.h>
#include <vtkActor.h>
#include <vtkUnstructuredGrid.h>
#include <vtkTransform.h>
#include <vtkFloatArray.h>
#include <vtkIdTypeArray.h>
#include <vtkVertex.h>
#include <vtkCellData.h>
#include <vtkCallbackCommand.h>
#include <vtkActor2D.h>
#include <vtkActor.h>
#include <vtkActorCollection.h>
#include <vtkProp3DCollection.h>
#include <vtkSelectionNode.h>
#include <vtkSelection.h>
#include <vtkRendererCollection.h>
#include <vtkExtractSelectedFrustum.h>
#include <vtkPointData.h>
#include <vtkIdentityTransform.h>

namespace camitk {

// ---------------------- constructor ----------------------------
InteractiveViewer::InteractiveViewer(QString& name, ViewerType type) : Viewer(name) {
    myType = type;
    init();
}

// ---------------------- destructor ----------------------------
InteractiveViewer::~InteractiveViewer() {

    // do not delete the menu as it will be deleted automatically
    // if it was inserted inside the application "View" for instance

    if (viewerToolbar != nullptr) {
        viewerToolbar->deleteLater();
    }

    // do not delete myWidget as it will automatically be deleted
    // when the embedder widget will be deleted
}

// ---------------------- init ----------------------------
void InteractiveViewer::init() {
    frameOfReference = TransformationManager::getFrameOfReferenceOwnership(TransformationManager::getWorldFrame());

    // myWidget is nullptr, but the rendererWidget and sliceSlider have to be instantiated
    // (even if the InteractiveViewer is not visible yet, it can still do some actions)
    myWidget = nullptr;

    //-- create and init the RendererWidget object for display and interactions

    if (myType == SLICE_VIEWER) {
        // block rotations (interactions)
        rendererWidget = new RendererWidget(myWidget, RendererWidget::TRACKBALL_2D);
        sliceSlider = new SliderSpinBoxWidget(myWidget);
    }
    else {
        // classic 3D view
        rendererWidget = new RendererWidget(myWidget);
        sliceSlider = nullptr;
    }

    cameraMap.insert("default", rendererWidget->getActiveCamera());

    //-- create the slice slider if needed
    viewerMenu = nullptr;
    viewerToolbar = nullptr;
    screenshotActionMenu = nullptr;
    displayedTopLevelComponents = 0;

    //-- current interaction is not changing slice
    isChangingSlice = false;

    //-- connection for selection
    connector = vtkSmartPointer<vtkEventQtSlotConnect>::New();
    connector->Connect(rendererWidget->getInteractor(), vtkCommand::EndPickEvent, this, SLOT(picked()));

    // create the different properties of the viewers
    createProperties();

    // init from settings
    initSettings();

    //-- set picking mode
    pickingEffectIsSelecting = true; // default effect
    isPicking = false; // by default, viewer is not picking
}

// ---------------------- initSettings ----------------------------
void InteractiveViewer::initSettings() {
    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".InteractiveViewer." + objectName().simplified().replace(" ", ""));

    // the background color
    QColor bg;

    if (myType == GEOMETRY_VIEWER) {
        // default is white
        bg.setNamedColor(settings.value("backgroundColor", QColor::fromRgbF(1.0, 1.0, 1.0)).toString());
    }
    else {
        // default is black
        bg.setNamedColor(settings.value("backgroundColor", QColor::fromRgbF(0.0, 0.0, 0.0)).toString());
    }
    propertyObject->setProperty(backgroundColorProperty->getName().toStdString().c_str(), bg);

    // Geometry viewers have a gradient background, but not the slice viewers
    bool gradientBackground = settings.value("gradientBackground", (myType == GEOMETRY_VIEWER)).toBool();
    propertyObject->setProperty(backgroundGradientColorProperty->getName().toStdString().c_str(), gradientBackground);

    // the control mode
    RendererWidget::ControlMode controlMode;

    if (myType == GEOMETRY_VIEWER) {
        controlMode = (RendererWidget::ControlMode) settings.value("controlMode", RendererWidget::TRACKBALL).toInt();
    }
    else {
        controlMode = (RendererWidget::ControlMode) settings.value("controlMode", RendererWidget::TRACKBALL_2D).toInt();
    }

    rendererWidget->setControlMode(controlMode);

    // the initial camera orientation
    InteractiveViewer::CameraOrientation cameraOrientation;
    cameraOrientation = static_cast<InteractiveViewer::CameraOrientation>(settings.value("cameraOrientation", static_cast<int>(getCameraOrientation())).toInt());
    propertyObject->setProperty("Camera Orientation", static_cast<int>(cameraOrientation));

    // highlight mode
    HighlightMode highlightMode = (HighlightMode) settings.value("highlightMode", SELECTION).toInt();
    propertyObject->setProperty(highlightModeProperty->getName().toStdString().c_str(), highlightMode);

    // lines as tubes
    bool linesAsTubes = settings.value("linesAsTubes", false).toBool();    // default false
    propertyObject->setProperty(linesAsTubesProperty->getName().toStdString().c_str(), linesAsTubes);

    // screenshot Action visibility
    bool screenshotActionVisible = settings.value("screenshotActionVisible", false).toBool();
    propertyObject->setProperty(screenshotActionProperty->getName().toStdString().c_str(), screenshotActionVisible);

    // backface culling
    bool backfaceCulling = settings.value("backfaceCulling", false).toBool();    // default false
    propertyObject->setProperty(backfaceCullingProperty->getName().toStdString().c_str(), backfaceCulling);

    // fxaaAntialiasing
    bool fxaaAntialiasing = settings.value("fxaaAntialiasing", true).toBool();    // default true
    propertyObject->setProperty(fxaaAntialiasingProperty->getName().toStdString().c_str(), fxaaAntialiasing);

    // point size
    double pointSize = settings.value("pointSize", 4.0).toDouble();
    propertyObject->setProperty(pointSizeProperty->getName().toStdString().c_str(), pointSize);

    // orientation letters
    if (myType == GEOMETRY_VIEWER) {
        rendererWidget->toggleOrientationDecorations(false);
    }

    settings.endGroup();

    // -- propertyObject event filter delegation
    // This method changes the properties, which will automatically trigger the
    // QEvent::DynamicPropertyChange
    // This in turns will call eventFilter each time
    // As the settings group is opened, it will generate nested beginGroup
    // Therefore the event filter should only be installed at the end,
    // when all properties are set.

    // propertyObject is monitored by the 3D viewer instance (this) when its properties change
    // All event filter on the property object are delegated to the InteractiveViewer class
    // @see eventFilter()
    propertyObject->installEventFilter(this);
    // trigger change for all the values
    InteractiveViewer::eventFilter(propertyObject, new QEvent(QEvent::DynamicPropertyChange));

}

// ---------------------- getWidget ----------------------------
QWidget* InteractiveViewer::getWidget() {
    if (!myWidget) {

        // init actions (including picking actions)
        initActions();

        // init picking (requires the picking actions)
        if (myType == SLICE_VIEWER) {
            // by default, the 2D scenes are set to pick slice pixels
            initPicking(InteractiveViewer::PIXEL_PICKING);
        }
        else {
            // point picking by default for 3D
            initPicking(InteractiveViewer::POINT_PICKING);
        }

        //-- build myWidget
        myWidget = new InteractiveViewerFrame(nullptr, this);
        myWidget->setObjectName("InteractiveViewerFrame");
        updateCurrentFrameOfReferenceColorIndicator();
        myWidget->setFrameShape(QFrame::StyledPanel);
        myWidget->setFrameShadow(QFrame::Plain);
        myWidget->installEventFilter(this);

        //-- handle layout
        auto* myLayout = new QVBoxLayout(myWidget);
        auto* horizontalSplitter = new QSplitter(myWidget);
        auto* myToolbar = this->getToolBar();
        if (myToolbar) {
            myLayout->addWidget(myToolbar);
            myToolbar->setVisible(getToolBarVisibility());
        }
        myLayout->addWidget(horizontalSplitter);
        myLayout->setSpacing(0);
        myLayout->setContentsMargins(0, 0, 0, 0);

        //-- show the renderer!
        rendererWidget->setParent(myWidget);
        rendererWidget->show();
        horizontalSplitter->addWidget(rendererWidget);

        connect(rendererWidget, SIGNAL(rightButtonPressed()), this, SLOT(rightClick()));

        //-- create the slider if needed
        if (myType == SLICE_VIEWER) {
            auto* rightSideLayout = new QVBoxLayout;
            rightSideLayout->setSpacing(0);
            rightSideLayout->setContentsMargins(0, 0, 0, 0);
            rightSideLayout->setContentsMargins(0, 0, 0, 0);

            // add snapshot widget
            screenshotActionMenu = new QToolBar(rendererWidget);
            screenshotActionMenu->addAction(screenshotAction);
            screenshotActionMenu->setEnabled(true);
            screenshotActionMenu->setVisible(false);
            screenshotActionMenu->layout()->setSpacing(0);
            screenshotActionMenu->layout()->setContentsMargins(0, 0, 0, 0);
            screenshotActionMenu->setContentsMargins(0, 0, 0, 0);
            screenshotActionMenu->layout()->setContentsMargins(0, 0, 0, 0);

            rightSideLayout->addWidget(screenshotActionMenu, 0, Qt::AlignCenter);

            // add slider
            sliceSlider->setParent(myWidget);

            // connect the slider to this scene
            connect(sliceSlider, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int)));
            rightSideLayout->addWidget(sliceSlider);

            // add the right side layout
            sideFrame = new QFrame;
            sideFrame->setLayout(rightSideLayout);
            QSizePolicy sideFrameSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
            sideFrame->setSizePolicy(sideFrameSizePolicy);
            sideFrame->setMinimumWidth(15);
            sideFrame->setMaximumWidth(60);
            sideFrame->setContentsMargins(0, 0, 0, 0);

            horizontalSplitter->addWidget(sideFrame);
        }
        else {
            sideFrame = nullptr;
        }

        // do this for receiving key-events
        myWidget->setFocusPolicy(Qt::ClickFocus);

        initWhatsThis();

        startWhatsThisSection("Mouse bindings");
        addWhatsThisItem("Left", "Rotate");
        addWhatsThisItem("Ctrl+Left", "Pick (flip selection flag for point/cell)");
        addWhatsThisItem("Shift+Left", "Translate selection");
        addWhatsThisItem("Middle", "Pan");
        addWhatsThisItem("Right", "Zoom");
        endWhatsThisSection();
        startWhatsThisSection("Keyboard bindings (upper or lower case)");
        addWhatsThisItem("3", "Toggle 3D red/blue stereo display");
        addWhatsThisItem("A", "Toggle view axes");
        addWhatsThisItem("C", "Toggle color scale");
        addWhatsThisItem("F", "Toggle backface culling");
        addWhatsThisItem("Alt+A", "Toggle FXAA Antialiasing");
        addWhatsThisItem("I", "Toggle image interpolation on slices");
        addWhatsThisItem("J", "Joystick interaction mode");
        addWhatsThisItem("L", "Toggle view labels");
        addWhatsThisItem("Alt+L", "Toggle light follows camera");
        addWhatsThisItem("R", "Reset Image Interactor Look Up Table");
        addWhatsThisItem("S", "Take a screenshot");
        addWhatsThisItem("Alt+S", "Toggle surfacerendering");
        addWhatsThisItem("T", "Trackball interaction mode");
        addWhatsThisItem("Alt+W", "Toggle wireframe rendering");
        endWhatsThisSection();

        startWhatsThisSection();
        addWhatsThisItem("SPACE", "Update/refresh view");
        addWhatsThisItem("HOME", "Reset camera to default view point or so that everything is visible");
        addWhatsThisItem("LEFT", "Turn -5&deg; around camera Y axis");
        addWhatsThisItem("Ctrl+LEFT", "Turn -90&deg; around camera Y axis");
        addWhatsThisItem("RIGHT", "Turn 5&deg; around camera Y axis");
        addWhatsThisItem("Ctrl+RIGHT", "Turn 90&deg; around camera Y axis");
        addWhatsThisItem("UP", "Turn -5&deg; around camera X axis");
        addWhatsThisItem("Ctrl+UP", "Turn -90&deg; around camera X axis");
        addWhatsThisItem("DOWN", "Turn 5&deg; around camera X axis");
        addWhatsThisItem("Ctrl+DOWN", "Turn 90&deg; around camera X axis");
        addWhatsThisItem("+", "Move slider a step above");
        addWhatsThisItem("-", "Move slider a step below");
        addWhatsThisItem("PAGE Up", "Move slider a page above");
        addWhatsThisItem("PAGE Down", "Move slider a page down");
        addWhatsThisItem("ESC", "Clear current selection");
        endWhatsThisSection();

        startWhatsThisSection("Other Shortcuts");
        addWhatsThisItem("F1", "Show this help (i.e. the what's this help)");
        addWhatsThisItem("F2", "Print debugging information on console");
        endWhatsThisSection();

        // add the text as whatsThis
        myWidget->setWhatsThis(whatsThis);
    }

    return myWidget;
}

// ---------------------- updateCurrentFrameOfReferenceColorIndicator ----------------------------
void InteractiveViewer::updateCurrentFrameOfReferenceColorIndicator() {
    if (myWidget != nullptr) {
        const QColor& color = frameOfReference.get()->getColor();
        myWidget->setStyleSheet(QString("#InteractiveViewerFrame { border-top: 5px solid rgb(%1,%2,%3); }").arg(color.red()).arg(color.green()).arg(color.blue()));
    }
}

// ---------------------- getPropertyObject ----------------------------
PropertyObject* InteractiveViewer::getPropertyObject() {
    return propertyObject;
}

// ---------------------- getName ----------------------------
QString InteractiveViewer::getName() const {
    return objectName();
}

// ---------------------- refresh ----------------------------
void InteractiveViewer::refresh(Viewer* v) {

    //-- first remove non active Components (if InteractiveViewer was called by itself, no need to do that)
    // This must be done even if the Viewer is not visible,
    // otherwise when closing a component, refresh is called,
    // and  a pointer to the closed Component may still be in actorMap
    if (v != this) {
        //-- check all present
        QList<Component*> compRendered = actorMap.uniqueKeys();

        for (Component* comp : compRendered) {
            if (!Application::isAlive(comp)) {
                // remove from the renderer and map
                removeAllActors(comp);
            }

            // remove all 2D actors of the deleted comp
            for (auto it = transformed2DActors.begin(); it != transformed2DActors.end();) {
                if (!actorMap.values().contains(it.key())) {
                    it = transformed2DActors.erase(it); // this will also increment the iterator
                }
                else {
                    ++it;
                }
            }
        }

    }

    // No more actors from components -> reset frame to worldFrame
    if (actorMap.size() == 0) {
        setFrame(TransformationManager::getFrameOfReferenceOwnership(TransformationManager::getWorldFrame()));
    }

    //-- do not refresh if the widget was not created or if myWidget is not visible
    if (myWidget == nullptr || !myWidget->isVisible()) {
        return;
    }

    //-- now check the full component list
    ComponentList allComponents = Application::getAllComponents();

    switch (myType) {
        case GEOMETRY_VIEWER:

            // check all Components
            for (Component* comp : allComponents) {
                // remove from the renderer and map
                removeAllActors(comp);

                // check if the Component is to be displayed here
                if (comp->getVisibility(this->getName())) {
                    if (comp->getRepresentation() == Component::GEOMETRY) {
                        //-- set the correct rendering parameters
                        updateSelectionDisplay(comp);

                        //-- check the line as tube representation
                        bool linesAsTubes = propertyObject->property(linesAsTubesProperty->getName().toStdString().c_str()).toBool();

                        if (linesAsTubes) {
                            comp->setLinesAsTubes(linesAsTubes);
                        }

                        //-- add the correct actors (nullptr is return if the actor is not appropriate, i.e. hidden or not in proper mode)
                        addActor(comp, comp->getActor(InterfaceGeometry::Surface), comp->getFrame());
                        addActor(comp, comp->getActor(InterfaceGeometry::Wireframe), comp->getFrame());
                        addActor(comp, comp->getActor(InterfaceGeometry::Points), comp->getFrame());

                        for (unsigned int i = 0; i < comp->getNumberOfProp(); i++) {
                            addActor(comp, comp->getProp(i), comp->getFrame());
                        }
                    }
                    else {
                        if (comp->getRepresentation() == Component::SLICE) {
                            addActor(comp, comp->get3DImageActor());

                            for (unsigned int i = 0; i < comp->getNumberOfProp(); i++) {
                                addActor(comp, comp->getProp(i));
                            }
                        }
                        else {
                            // Display the cursor if it has one (e.g. ImageComponent)
                            ImageComponent* imageComp = dynamic_cast<ImageComponent*>(comp);
                            if (imageComp != nullptr) {
                                addActor(imageComp, imageComp->get3DCursor(), imageComp->getDataFrame());
                            }
                        }
                    }
                }

                if (comp->getFrameVisibility(this->getName())) {
                    addActor(comp, comp->getFrameAxisActor(this->getName()));
                    // FIXME scale using 3D viewer property ("Frame Size" (%age of the bounding box radius))
                }
            }

            break;

        case SLICE_VIEWER:
            // remove all from the renderer and map
            QList<Component*> compRendered = actorMap.keys();

            for (Component* comp : compRendered) {
                removeAllActors(comp);
            }

            // Find the last selected component which has visibility, or the last added component which has visibility
            Component* currentlySelectedComponent = nullptr;

            // Search in selection
            int idOfLastSelected = Application::getSelectedComponents().size() - 1;

            if (idOfLastSelected >= 0) {
                // there is one, check for one which has visibility
                while (idOfLastSelected >= 0 && !(Application::getSelectedComponents()[idOfLastSelected]->getVisibility(this->getName()) && Application::getSelectedComponents()[idOfLastSelected]->getRepresentation() == Component::SLICE)) {
                    idOfLastSelected--;
                }
                if (idOfLastSelected >= 0) {// there is valid selected Component to display
                    currentlySelectedComponent = Application::getSelectedComponents()[idOfLastSelected];
                }
            }

            // No component found in selection, search in all components
            if (currentlySelectedComponent == nullptr) {
                // get the last added Component
                idOfLastSelected = allComponents.size() - 1;
                // Check for visibility
                while (idOfLastSelected >= 0 && !(allComponents[idOfLastSelected]->getVisibility(this->getName()) && allComponents[idOfLastSelected]->getRepresentation() == Component::SLICE)) {
                    idOfLastSelected--;
                }
                if (idOfLastSelected >= 0) {
                    // there is valid Component to display
                    currentlySelectedComponent = allComponents[idOfLastSelected];
                }
            }

            // Display the component
            if (currentlySelectedComponent != nullptr) {
                // Set the frame of the viewer to the data frame of the image component, that is the frame of the SingleImageComponent
                if (currentlySelectedComponent->getFrame() != getFrame()) {
                    setFrame(TransformationManager::getFrameOfReferenceOwnership(currentlySelectedComponent->getFrame()));
                }
                // add the 3 actors: the slice, the plane used for picking and the pixel highlighter
                addActor(currentlySelectedComponent, currentlySelectedComponent->get2DImageActor(), getFrame());
                // The pick plane is already computed in worldFrame
                addActor(currentlySelectedComponent, currentlySelectedComponent->getPickPlaneActor(), getFrame());
                // Add the cursor actor of the slice
                addActor(currentlySelectedComponent, currentlySelectedComponent->get3DCursor(), getFrame());

                for (unsigned int i = 0; i < currentlySelectedComponent->getNumberOfProp(); i++) {
                    addActor(currentlySelectedComponent, currentlySelectedComponent->getProp(i), getFrame());
                }

                // set the slider range and update widgets values
                sliceSlider->setRange(0, currentlySelectedComponent->getNumberOfSlices() - 1);
                sliceSlider->setValue(currentlySelectedComponent->getSlice());
            }

            // camera orientation update to refresh orientation letters
            CameraOrientation orientation = static_cast<CameraOrientation>(propertyObject->property(cameraOrientationProperty->getName().toStdString().c_str()).toInt());
            setCameraOrientation(orientation);
            break;
    }

    // refresh axis, camera and clipping planes only
    // - on slice viewer which are not being picked nor are changing slice
    // - on geometry viewer which needs additional refresh
    if (displayedTopLevelComponents == (unsigned) Application::getTopLevelComponents().size()) {
        rendererWidget->updateAxes();

        // refresh only camera on slice viewer which are not being picked or geometry viewer which needs additional refresh
        if (((myType == SLICE_VIEWER) && !isPicking)) {
            if (!isChangingSlice) {
                resetCamera();
            }
            else {
                // just reset clipping range
                rendererWidget->resetClippingPlanes();
            }

            // TODO add a boolean "Reset camera when picking" that can be set on or off
            // and saved as a setting
        }
    }
    else {
        // there is some difference => reset camera in case of Geometry Viewer
        rendererWidget->updateAxes();
        resetCamera();
        displayedTopLevelComponents = Application::getTopLevelComponents().size();
    }

    rendererWidget->refresh();

    //-- update actions
    updateActions();

}

// ---------------------- removeAllActors ----------------------------
void InteractiveViewer::removeAllActors(Component* comp) {
    QList<vtkSmartPointer<vtkProp> > actorRendered = actorMap.values(comp);

    for (vtkSmartPointer<vtkProp> a : actorRendered) {
        rendererWidget->removeProp(a);
    }

    // remove from the list
    actorMap.remove(comp);

}

// ---------------------- addActor ----------------------------
void InteractiveViewer::addActor(Component* comp, vtkSmartPointer<vtkProp> a, const FrameOfReference* sourceFrame) {
    if (a) {
        // We need to transform the Prop into the worldFrame if it is a Prop3D such as a vtkActor
        vtkSmartPointer<vtkProp3D> a3d = vtkProp3D::SafeDownCast(a);
        if (a3d != nullptr) {
            if (sourceFrame == nullptr) {
                if (dynamic_cast<ArbitrarySingleImageComponent*>(comp) != nullptr) {
                    sourceFrame = dynamic_cast<ArbitrarySingleImageComponent*>(comp)->getArbitraryFrame();
                }
                else {
                    sourceFrame = comp->getFrame();
                }
            }

            TransformationManager::ensurePathToWorld(comp->getFrame());

            // Get the transformation between the source frame and the viewer frame and apply it to the vtkProp3D
            Transformation* tr = TransformationManager::getTransformation(sourceFrame, getFrame());
            if (tr) {
                a3d->SetUserTransform(tr->getTransform());
            }
            else { // If there is no transformation, set identity
                a3d->SetUserTransform(vtkSmartPointer<vtkIdentityTransform>::New());

            }
        }
        else {
            vtkSmartPointer<vtkActor2D> a2d = vtkActor2D::SafeDownCast(a);
            if (a2d != nullptr) {
                double coordinatesInSourceFrame[3];
                if (!transformed2DActors.contains(a2d)) {
                    // this is the first time this 2D actor is added, store coordinates in component frame
                    a2d->GetPositionCoordinate()->GetValue(coordinatesInSourceFrame);
                    transformed2DActors[a2d] = QVector3D(coordinatesInSourceFrame[0], coordinatesInSourceFrame[1], coordinatesInSourceFrame[2]);
                }
                else {
                    QVector3D coord = transformed2DActors[a2d];
                    for (int i = 0; i < 3; i++) {
                        coordinatesInSourceFrame[i] = coord[i];
                    }
                }

                // apply the transformation from source to viewer frame to the prop location
                Transformation* tr = TransformationManager::getTransformation(sourceFrame, getFrame());
                double coordinatesInViewerFrame[3];
                tr->getTransform()->TransformPoint(coordinatesInSourceFrame, coordinatesInViewerFrame);
                a2d->GetPositionCoordinate()->SetValue(coordinatesInViewerFrame);
            }
        }

        rendererWidget->addProp(a);
        actorMap.insert(comp, a);
    }
}

// ---------------------- getMenu ----------------------------
QMenu* InteractiveViewer::getMenu() {
    if (myWidget && !viewerMenu) {
        //-- create the main menu
        viewerMenu = new QMenu(objectName());
        viewerMenu->setTearOffEnabled(true);

        //-- add actions to the menu
        QMenu* options = new QMenu("View Options");
        options->addAction(backgroundColorAction);
        options->addAction(toggleLogoAction);
        options->addAction(toggleAxesAction);

        if (myType == SLICE_VIEWER) {
            options->addAction(toggleOrientationDecorationsAction);
            options->addAction(toggleScreenshotAction);
        }

        if (myType == GEOMETRY_VIEWER) {
            options->addSeparator();
            options->addAction(toggleAxesAction);
            options->addAction(toggleLabelAction);
            options->addAction(toggleLinesAsTubesAction);
            options->addAction(toggleBackfaceCullingAction);
            options->addAction(toggleFxaaAntialiasingAction);

            // display mode
            QMenu* highlightMenu = new QMenu("Highlight Mode");
            options->addMenu(highlightMenu);
            highlightMenu->addAction(highlightOffAction);
            highlightMenu->addAction(highlightSelectionAction);
            highlightMenu->addAction(highlightSelectionOnlyAction);

            // camera menu
            QMenu* controlModeMenu = new QMenu("Camera Control");
            controlModeMenu->addAction(controlModeTrackballAction);
            controlModeMenu->addAction(controlModeJoystickAction);
            options->addMenu(controlModeMenu);

            // force toolbar creation and add it
            options->addSeparator();
            options->addAction(getToolBar()->toggleViewAction());
        }

        viewerMenu->addMenu(options);

        if (myType == GEOMETRY_VIEWER) {
            viewerMenu->addSeparator();
            // picking menu
            QMenu* pickingMenu = new QMenu("&Picking");
            pickingMenu->addAction(pickLocationAction);
            pickingMenu->addAction(pickPointAction);
            pickingMenu->addAction(pickCellAction);
            pickingMenu->addAction(pickPointRegionAction);
            pickingMenu->addAction(pickCellRegionAction);
            viewerMenu->addMenu(pickingMenu);

            viewerMenu->addMenu(renderingMenu);
        }

        viewerMenu->addAction(screenshotAction);

        //-- update actions
        updateActions();
    }

    return viewerMenu;
}

// ---------------------- getToolBar ----------------------------
QToolBar* InteractiveViewer::getToolBar() {
    if (myWidget && !viewerToolbar) {
        viewerToolbar = new QToolBar(objectName() + " Toolbar");
        // ensure object name is set for saving the state
        viewerToolbar->setObjectName(objectName() + " Toolbar");

        // Orientation icons
        orientationCombo = new QComboBox(viewerToolbar);
        orientationCombo->addItems({"XY", "XZ", "YZ", "Axial", "Coronal", "Sagittal", "Other"});
        orientationCombo->setCurrentIndex(propertyObject->getPropertyValue("Camera Orientation").toInt());
        viewerToolbar->addWidget(orientationCombo);
        connect(orientationCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
            this->getPropertyObject()->setProperty("Camera Orientation", index);
        });

        // Specific actions for 3D viewer
        if (myType == GEOMETRY_VIEWER) {
            pickingCombo = new QComboBox(viewerToolbar);
            pickingCombo->setToolTip("Select picking mode");
            pickingCombo->setIconSize(QSize(16, 16)); // Set the icon size
            pickingCombo->addItem(pickLocationAction->icon(), pickLocationAction->iconText());
            pickingCombo->addItem(pickPointAction->icon(), pickPointAction->iconText());
            pickingCombo->addItem(pickCellAction->icon(), pickCellAction->iconText());
            pickingCombo->addItem(pickPointRegionAction->icon(), pickPointRegionAction->iconText());
            pickingCombo->addItem(pickCellRegionAction->icon(), pickCellRegionAction->iconText());
            // Connect the combobox's activated signal to a slot
            connect(pickingCombo, QOverload<int>::of(&QComboBox::activated), [this](int i) {
                this->initPicking(static_cast<InteractiveViewer::PickingMode>(i));
            });

            // Make sure the pickingCombo is updated to the true picking mode
            initPicking(pickingMode);

            viewerToolbar->addWidget(pickingCombo);

            viewerToolbar->addAction(surfaceAction);
            viewerToolbar->addAction(wireframeAction);
            viewerToolbar->addAction(pointsAction);

            // Try to get the color action now, because when initActions is run, ActionExtension are not yet loaded
            Action* changeColorAction = Application::getAction("Change Color");

            // the InteractiveViewer colorAction is just the same as the action's QAction
            if (changeColorAction) {
                colorAction = changeColorAction->getQAction();
                viewerToolbar->addAction(colorAction);
            }
            else {
                colorAction = nullptr;
            }

            viewerToolbar->addAction(glyphAction);
            viewerToolbar->addAction(toggleLabelAction);

            viewerToolbar->addSeparator();

            viewerToolbar->addAction(toggleAxesAction);
            viewerToolbar->addAction(screenshotAction);
        }
    }

    return viewerToolbar;
}

// ---------------------- setFrame ----------------------------
void InteractiveViewer::setFrame(const std::shared_ptr<FrameOfReference>& frameOfRef) {
    // Ignore if we set the same frame to avoid useless refresh
    if (frameOfRef == frameOfReference) {
        return;
    }

    TransformationManager::ensurePathToWorld(frameOfRef.get());

    // Set the current FrameOfReference (Viewer class)
    frameOfReference = frameOfRef;

    // update GUI
    updateCurrentFrameOfReferenceColorIndicator();

    /// by default, the viewer must refresh all the actor user matrixes when its frame as changed
    /// but this is not needed if there are no actors in the viewer
    /// (for instance when called from inside the refresh() method itself)
    if (actorMap.size() > 0) {
        InteractiveViewer::refresh(this);
    }
}

// ---------------------- getFrame ----------------------------
const FrameOfReference* InteractiveViewer::getFrame() const {
    return frameOfReference.get();
}

// ---------------------- initActions ----------------------------
void InteractiveViewer::initActions() {
    //-- screenshot
    screenshotAction = new QAction(QPixmap(":/snapShot"), tr("Screenshot"), this);
    screenshotAction->setStatusTip(tr("Take a screenshot/snapshot of the renderer window"));
    screenshotAction->setWhatsThis(tr("Screenshot\n\nTake a screenshot/snapshot of the renderer viewer"));
    connect(screenshotAction, SIGNAL(triggered()), this, SLOT(screenshot()));

    if (myType == GEOMETRY_VIEWER) {
        //--- actions of the View menu
        surfaceAction = new QAction(QPixmap(":/surfaceRendering"), tr("&Surface"), this);
        surfaceAction->setCheckable(true);
        surfaceAction->setStatusTip(tr("Enables/disables surface rendering"));
        surfaceAction->setWhatsThis(tr("Enables/disables surface rendering"));
        connect(surfaceAction, SIGNAL(triggered()), this, SLOT(renderingActorsChanged()));

        wireframeAction = new QAction(QPixmap(":/wireframeRendering"), tr("&Wireframe"), this);
        wireframeAction->setCheckable(true);
        wireframeAction->setStatusTip(tr("Enables/disables wireframe rendering"));
        wireframeAction->setWhatsThis(tr("Enables/disables wireframe rendering"));
        connect(wireframeAction, SIGNAL(triggered()), this, SLOT(renderingActorsChanged()));

        pointsAction = new QAction(QPixmap(":/pointRendering"), tr("&Points"), this);
        pointsAction->setCheckable(true);
        pointsAction->setStatusTip(tr("Enables/disables points rendering"));
        pointsAction->setWhatsThis(tr("Enables/disables points rendering"));
        connect(pointsAction, SIGNAL(triggered()), this, SLOT(renderingActorsChanged()));

        toggleLabelAction = new QAction(QPixmap(":/label"), tr("Show &Label"), this);
        toggleLabelAction->setCheckable(true);
        toggleLabelAction->setStatusTip(tr("Show/hide label"));
        toggleLabelAction->setWhatsThis(tr("Show/hide label"));
        connect(toggleLabelAction, SIGNAL(toggled(bool)), this, SLOT(setLabel(bool)));

        glyphAction = new QAction(QPixmap(":/showGlyph"), tr("Show/Hide &Glyph"), this);
        glyphAction->setCheckable(true);
        glyphAction->setStatusTip(tr("Show/hide point glyph"));
        glyphAction->setWhatsThis(tr("Show/hide point glyph"));
        connect(glyphAction, SIGNAL(toggled(bool)), this, SLOT(setGlyph(bool)));

        // rendering mode
        renderingMenu = new QMenu("Rendering Mode");
        renderingMenu->addAction(surfaceAction);
        renderingMenu->addAction(wireframeAction);
        renderingMenu->addAction(pointsAction);

        // -- display mode
        auto* displayGrp = new QActionGroup(this);
        connect(displayGrp, SIGNAL(triggered(QAction*)), this, SLOT(highlightModeChanged(QAction*)));

        highlightSelectionAction = new QAction(tr("&Selection"), displayGrp);
        highlightSelectionAction->setCheckable(true);
        highlightSelectionAction->setChecked(true);    // by default the selected Item and the others are normal
        highlightSelectionAction->setStatusTip(tr("display the selected component(s) normally while the others are shaded"));
        highlightSelectionAction->setWhatsThis(tr("display the selected component(s) using their default rendering properties while the others are shaded (i.e., with transparency)"));

        highlightSelectionOnlyAction = new QAction(tr("Selection &Only"), displayGrp);
        highlightSelectionOnlyAction->setCheckable(true);
        highlightSelectionOnlyAction->setStatusTip(tr("display the selected component(s) normally while the others are hidden"));
        highlightSelectionOnlyAction->setWhatsThis(tr("display the selected component(s) using their default rendering properties while the others are masked (i.e., not displayed)"));

        highlightOffAction = new QAction(tr("&No Highlight"), displayGrp);
        highlightOffAction->setCheckable(true);
        highlightOffAction->setStatusTip(tr("display all component(s) the same way"));
        highlightOffAction->setWhatsThis(tr("display all component(s) using their default rendering properties, either they are selected or not"));

        //-- camera mode
        auto* cameraModeGrp = new QActionGroup(this);
        connect(cameraModeGrp, SIGNAL(triggered(QAction*)), this, SLOT(viewControlModeChanged(QAction*)));

        controlModeTrackballAction = new QAction(tr("&Trackball"), cameraModeGrp);
        controlModeTrackballAction->setCheckable(true);
        controlModeTrackballAction->setChecked(true);
        controlModeTrackballAction->setStatusTip(tr("Set the camera control mode to trackball\n(the camera moves with the mouse)"));
        controlModeTrackballAction->setWhatsThis(tr("Set the camera control mode to trackball\n(the camera moves with the mouse)"));

        controlModeJoystickAction = new QAction(tr("&Joystick"), cameraModeGrp);
        controlModeJoystickAction->setCheckable(true);
        controlModeJoystickAction->setStatusTip(tr("Set the camera control mode to joystick\n(the camera moves when the mouse button is pressed)"));
        controlModeJoystickAction->setWhatsThis(tr("Set the camera control mode to joystick\n(the camera moves when the mouse button is pressed)"));
    }

    //-- others
    backgroundColorAction = new QAction(tr("Background &Color"), this);
    backgroundColorAction->setStatusTip(tr("Change background color"));
    backgroundColorAction->setWhatsThis(tr("Change the color background of the InteractiveViewer"));

    connect(backgroundColorAction, SIGNAL(triggered()), this, SLOT(backgroundColor()));

    toggleLogoAction = new QAction(tr("Show &Logo"), this);
    toggleLogoAction->setCheckable(true);
    toggleLogoAction->setChecked(true);
    toggleLogoAction->setStatusTip(tr("Display the logo at the bottom right corner"));
    toggleLogoAction->setWhatsThis(tr("Display the logo at the bottom right corner"));
    connect(toggleLogoAction, SIGNAL(toggled(bool)), this, SLOT(toggleLogo(bool)));

    toggleAxesAction = new QAction(QPixmap(":/axes"), tr("Show &Axes"), this);
    toggleAxesAction->setCheckable(true);
    toggleAxesAction->setStatusTip(tr("Display the coordinate axes"));
    toggleAxesAction->setWhatsThis(tr("Display the coordinate axes of the InteractiveViewer"));
    connect(toggleAxesAction, SIGNAL(toggled(bool)), this, SLOT(toggleAxes(bool)));

    if (myType == SLICE_VIEWER) {
        toggleOrientationDecorationsAction = new QAction(tr(" Show Slice Orientation Decoration"), this);
        toggleOrientationDecorationsAction->setCheckable(true);
        toggleOrientationDecorationsAction->setChecked(true);
        toggleOrientationDecorationsAction->setStatusTip(tr("Display Slice Orientation Information (Right, Left, Anterior, Posterior, Superior, Inferior"));
        toggleOrientationDecorationsAction->setWhatsThis(tr("Display Slice Orientation Information (Right, Left, Anterior, Posterior, Superior, Inferior"));
        connect(toggleOrientationDecorationsAction, SIGNAL(toggled(bool)), this, SLOT(toggleOrientationDecorations(bool)));

        toggleScreenshotAction = new QAction(tr("&Toggle Screenshot Action"), this);
        toggleScreenshotAction->setCheckable(true);
        bool screenshotActionVisible = propertyObject->property(screenshotActionProperty->getName().toStdString().c_str()).toBool();
        toggleScreenshotAction->setChecked(screenshotActionVisible);
        toggleScreenshotAction->setStatusTip(tr("Show/Hide the screenshot button in the side bar of the slice viewer."));
        toggleScreenshotAction->setWhatsThis(tr("Show/Hide the screenshot button in the side bar of the slice viewer."));
        connect(toggleScreenshotAction, SIGNAL(toggled(bool)), this, SLOT(setScreenshotAction(bool)));

    }

    if (myType == GEOMETRY_VIEWER) {
        toggleLinesAsTubesAction = new QAction(tr("Show Lines as Tubes"), this);
        toggleLinesAsTubesAction->setCheckable(true);

        toggleLinesAsTubesAction->setChecked(propertyObject->property(linesAsTubesProperty->getName().toStdString().c_str()).toBool());
        toggleLinesAsTubesAction->setStatusTip(tr("Display the lines - if in a vtkDataSet - as tubes"));
        toggleLinesAsTubesAction->setWhatsThis(tr("Display the lines - if in a vtkDataSet - as tubes"));
        connect(toggleLinesAsTubesAction, SIGNAL(toggled(bool)), this, SLOT(setLinesAsTubes(bool)));

        toggleBackfaceCullingAction = new QAction(tr("&Backface Culling"), this);
        toggleBackfaceCullingAction->setCheckable(true);
        bool backfaceCulling = propertyObject->property(backfaceCullingProperty->getName().toStdString().c_str()).toBool();
        toggleBackfaceCullingAction->setChecked(backfaceCulling);
        toggleBackfaceCullingAction->setStatusTip(tr("Set cull face on/off\nIf backface culling is on, polygons facing away from camera are not drawn."));
        toggleBackfaceCullingAction->setWhatsThis(tr("Set cull face on/off\nIf backface culling is on, polygons facing away from camera are not drawn."));
        connect(toggleBackfaceCullingAction, SIGNAL(toggled(bool)), this, SLOT(setBackfaceCulling(bool)));

        toggleFxaaAntialiasingAction = new QAction(tr("&FXAA Antialiasing"), this);
        toggleFxaaAntialiasingAction->setCheckable(true);
        bool fxaaAntialiasing = propertyObject->property(fxaaAntialiasingProperty->getName().toStdString().c_str()).toBool();
        toggleFxaaAntialiasingAction->setChecked(fxaaAntialiasing);
        toggleFxaaAntialiasingAction->setStatusTip(tr("Set FXAA antialiasing on/off\nIf FXAA antialiasing is on, lines will appear smoother but might be blurred"));
        toggleFxaaAntialiasingAction->setWhatsThis(tr("Set FXAA antialiasing on/off\nIf FXAA antialiasing is on, lines will appear smoother but might be blurred"));
        connect(toggleFxaaAntialiasingAction, SIGNAL(toggled(bool)), this, SLOT(setFxaaAntialiasing(bool)));

        //--- actions of the picking menu
        auto* pickingGrp = new QActionGroup(this);
        connect(pickingGrp, SIGNAL(triggered(QAction*)), this, SLOT(pickingModeChanged(QAction*)));

        pickCellAction = new QAction(QPixmap(":/pick_cell"), tr("Pick &cell"), pickingGrp);
        pickCellAction->setCheckable(true);
        pickCellAction->setStatusTip(tr("Picking mode sets to pick cells"));
        pickCellAction->setWhatsThis(tr("Pick cell\n\nSets the picking mode to pick one or more cells\nUse Ctrl+Left clic to select a cell,\nEscape to reset selection"));

        pickPointAction = new QAction(QPixmap(":/pick_point"), tr("Pick &Points"), pickingGrp);
        pickPointAction->setCheckable(true);
        pickPointAction->setStatusTip(tr("Picking mode sets to pick points"));
        pickPointAction->setWhatsThis(tr("Pick points\n\nSets the picking mode to pick one or more points\nUse Ctrl+Left clic to select an point\n\nEscape to reset selection"));

        pickCellRegionAction = new QAction(QPixmap(":/pick_cell_region"), tr("Pick cell region"), pickingGrp);
        pickCellRegionAction->setCheckable(true);
        pickCellRegionAction->setStatusTip(tr("Picking mode set to pick cell region"));
        pickCellRegionAction->setWhatsThis(tr("Pick cells in rectangular region"));

        pickPointRegionAction = new QAction(QPixmap(":/pick_point_region"), tr("Pick point region"), pickingGrp);
        pickPointRegionAction->setCheckable(true);
        pickPointRegionAction->setStatusTip(tr("Picking mode set to pick point region"));
        pickPointRegionAction->setWhatsThis(tr("Pick points in rectangular region"));

        pickLocationAction = new QAction(QPixmap(":/pick_location"), tr("Pick location"), pickingGrp);
        pickLocationAction->setCheckable(true);
        pickLocationAction->setStatusTip(tr("Picking mode set to pick a location"));
        pickLocationAction->setWhatsThis(tr("Pick a location"));
    }
}

// ----------------------initWhatsThis ----------------------------
void InteractiveViewer::initWhatsThis() {
    QColor bgColor = myWidget->palette().color(QPalette::Button);
    QColor textColor = myWidget->palette().color(QPalette::ButtonText);

    whatsThis = "<div style=\"background:" + bgColor.name() + "\"><font size=\"8pt\" color=\"" + textColor.name() + "\" ><center><b><u><i>3D view interaction Shortcuts</i></u></b></center><br/></font></div>";
}

// ----------------------startWhatsThisSection ----------------------------
void InteractiveViewer::startWhatsThisSection(const QString& title) {
    QColor bgColor = myWidget->palette().color(QPalette::Button);
    QColor textColor = myWidget->palette().color(QPalette::ButtonText);

    whatsThis += "<div style=\"background:" + bgColor.name() + "\"><font size=\"9pt\" color=\"" + textColor.name() + "\"><center><b><i>" + title + "</i></b></center>\
               <table bgcolor=\"" + bgColor.name() + "\" cellpadding = \"0\" cellspacing=\"0\" width=\"100%\">";
    oddWhatsThis = true;
}

// ----------------------endWhatsThisSection ----------------------------
void InteractiveViewer::endWhatsThisSection() {
    whatsThis += "</table></font></div>\
               <br>";
}

// ----------------------addWhatsThisItem ----------------------------
void InteractiveViewer::addWhatsThisItem(const QString& key, const QString& description) {
    if (oddWhatsThis) {
        QColor bgColor = myWidget->palette().color(QPalette::Midlight);
        whatsThis += "<tr bgcolor=\"" + bgColor.name() + "\">";
    }
    else {
        whatsThis += "<tr>";
    }

    oddWhatsThis = !oddWhatsThis;

    whatsThis += R"(<td align="center"><font size="8pt">)" + key + "&nbsp;&nbsp;</font></td>\
    <td align=\"left\"><font size=\"8pt\">" + description + "</font></td>\
               </tr>";

}


// ----------------------keyPressEvent ----------------------------
void InteractiveViewer::keyPressEvent(QKeyEvent* e) {
    /// NOTE:
    ///
    /// PLEASE, PLEASE, PLEASE, PLEASE, PLEASE, PLEASE
    ///
    /// DO NOT FORGET TO ADD A NEW LINE IN THE "WHAT'S THIS" MESSAGE (see constructor)
    ///
    /// The call to methods startWhatsThisSection and addWhatsThisItem is a good comment line
    /// to add here (see below!). Please use the same order here than in the
    /// what's this help (by category, then by function, then by key name)
    ///
    /// (PLEASE)

    //-- Look at the key code
    switch (e->key()) {
        ///
        /// startWhatsThisSection("Keyboard bindings (upper or lower case)");
        ///
        case Qt::Key_3:
            rendererWidget->toogle3DRedBlue();
            rendererWidget->refresh();

            break;

        case Qt::Key_A:

            // addWhatsThisItem("A", "Toggle view axes");
            if (e->modifiers() == Qt::NoModifier /*&& myType == GEOMETRY_VIEWER*/) {
                toggleAxesAction->toggle();
            }
            else if (e->modifiers() == Qt::AltModifier) {
                // addWhatsThisItem("Alt+A", "Toggle FXAA Antialiasing");
                toggleFxaaAntialiasingAction->toggle();
            }

            break;

        case Qt::Key_C:

            // addWhatsThisItem("C", "Toggle color scale");

            if (e->modifiers() == Qt::NoModifier) {
                setColorScale(!rendererWidget->getColorScale());
                refresh(this);
            }

            break;

        case Qt::Key_F:

            // addWhatsThisItem("F", "Toggle backface culling");

            if (e->modifiers() == Qt::NoModifier) {
                rendererWidget->setBackfaceCulling(!rendererWidget->getBackfaceCulling());
            }

            break;

        case Qt::Key_I:

            // addWhatsThisItem("I", "Toggle image interpolation on slices");

            if (e->modifiers() == Qt::NoModifier) {
                toggleInterpolation();
            }

            break;

        case Qt::Key_J:

            if (e->modifiers() == Qt::NoModifier) {
                // addWhatsThisItem("J", "Joystick interaction mode");
                rendererWidget->setControlMode(RendererWidget::JOYSTICK);
            }

            break;

        case Qt::Key_L:

            // addWhatsThisItem("L", "Toggle view labels");

            if (e->modifiers() == Qt::NoModifier && myType == GEOMETRY_VIEWER) {
                toggleLabelAction->toggle();
            }
            else if (e->modifiers() == Qt::AltModifier) {
                // addWhatsThisItem("Alt+L", "Toggle light follows camera");
                rendererWidget->setLightFollowCamera(!rendererWidget->getLightFollowCamera());
                rendererWidget->refresh();
            }

            break;


        case Qt::Key_R:
            if (myType == SLICE_VIEWER) {
                resetLUT();
            }

            break;

        case Qt::Key_S:

            // addWhatsThisItem("Alt+S", "Toggle surface rendering");
            if (e->modifiers() == Qt::AltModifier) {
                for (Component* comp : Application::getSelectedComponents()) {
                    // check if the Component is to be displayed here
                    if (comp->getVisibility(this->getName()) && comp->getRepresentation() == Component::GEOMETRY) {
                        comp->setRenderingModes(comp->getRenderingModes() ^ InterfaceGeometry::Surface);       //XOR
                    }
                }

                refresh(this);
            }
            else if (e->modifiers() == Qt::NoModifier) {
                // addWhatsThisItem("S", "Take a screenshot");
                screenshot();
            }

            break;

        case Qt::Key_T:

            if (e->modifiers() == Qt::NoModifier) {
                // addWhatsThisItem("T", "Trackball interaction mode");
                rendererWidget->setControlMode(RendererWidget::TRACKBALL);
            }

            break;

        case Qt::Key_W:

            // addWhatsThisItem("Alt+W", "Toggle wireframe rendering");

            if (e->modifiers() == Qt::AltModifier) {
                for (Component* comp : Application::getSelectedComponents()) {
                    // check if the Component is to be displayed here
                    if (comp->getVisibility(this->getName()) && comp->getRepresentation() == Component::GEOMETRY) {
                        comp->setRenderingModes(comp->getRenderingModes() ^ InterfaceGeometry::Wireframe);       //XOR
                    }
                }

                refresh(this);
            }

            break;

        ///
        /// startWhatsThisSection();
        ///
        case Qt::Key_Space:
            // addWhatsThisItem("SPACE", "Update/refresh view");
            refresh(this);
            break;

        case Qt::Key_Home:
            // addWhatsThisItem("HOME", "Reset camera to default view point or so that everything is visible");
            resetCamera();
            break;

        case Qt::Key_Left:

            if (myType == GEOMETRY_VIEWER) {
                if (e->modifiers() == Qt::ControlModifier)
                    // addWhatsThisItem("Ctrl+LEFT", "Turn -90&deg; around camera Y axis");
                {
                    rendererWidget->rotateCamera(-90, 1);
                }
                else
                    // addWhatsThisItem("LEFT", "Turn -5&deg; around camera Y axis");
                {
                    rendererWidget->rotateCamera(-5, 1);
                }
            }

            break;

        case Qt::Key_Right:

            if (myType == GEOMETRY_VIEWER) {
                if (e->modifiers() == Qt::ControlModifier)
                    // addWhatsThisItem("Ctrl+RIGHT", "Turn 90&deg; around camera Y axis");
                {
                    rendererWidget->rotateCamera(90, 1);
                }
                else
                    // addWhatsThisItem("Ctrl+LEFT", "Turn -90&deg; around camera Y axis");
                {
                    rendererWidget->rotateCamera(5, 1);
                }
            }

            break;

        case Qt::Key_Up:

            if (myType == GEOMETRY_VIEWER) {
                if (e->modifiers() == Qt::ControlModifier)
                    // addWhatsThisItem("Ctrl+UP", "Turn -90&deg; around camera X axis");
                {
                    rendererWidget->rotateCamera(-90, 0);
                }
                else
                    // addWhatsThisItem("UP", "Turn -5&deg; around camera X axis");
                {
                    rendererWidget->rotateCamera(-5, 0);
                }
            }

            break;

        case Qt::Key_Down:

            if (myType == GEOMETRY_VIEWER) {
                if (e->modifiers() == Qt::ControlModifier)
                    // addWhatsThisItem("Ctrl+DOWN", "Turn 90&deg; around camera X axis");
                {
                    rendererWidget->rotateCamera(90, 0);
                }
                else
                    // addWhatsThisItem("DOWN", "Turn 5&deg; around camera X axis");
                {
                    rendererWidget->rotateCamera(5, 0);
                }
            }

            break;

        case Qt::Key_Plus:

            // addWhatsThisItem("+", "Move slider a step above");

            if (myType == SLICE_VIEWER) {
                sliceSlider->addSingleStep();
            }

            break;

        case Qt::Key_Minus:

            // addWhatsThisItem("-", "Move slider a step below");
            if (myType == SLICE_VIEWER) {
                sliceSlider->subSingleStep();
            }

            break;

        case Qt::Key_PageUp:

            // addWhatsThisItem("PAGE Up", "Move slider a page above");
            if (myType == SLICE_VIEWER) {
                sliceSlider->addPageStep();
            }

            break;

        case Qt::Key_PageDown:

            // addWhatsThisItem("PAGE Down", "Move slider a page down");
            if (myType == SLICE_VIEWER) {
                sliceSlider->subPageStep();
            }

            break;

        case Qt::Key_Escape: {
            // addWhatsThisItem("ESC", "Clear current selection");
            clearSelection();
            refresh(this);
        }

        break;

        ///
        /// startWhatsThisSection("Other Shortcuts");
        ///
        case Qt::Key_F1:

            if (e->modifiers() == Qt::NoModifier) {
                //addWhatsThisItem("Shift+F1", "Show this help (i.e. the what's this help)");
                QWhatsThis::showText(QCursor::pos(), whatsThis);
            }

            break;

        case Qt::Key_F2:

            if (e->modifiers() == Qt::NoModifier) {
                // addWhatsThisItem("F2", "Print debugging information on console");
                QString debugString;
                QTextStream debugStream(&debugString);

                debugStream << "=== F2 pressed (debug information) ===" << Qt::endl;
                debugStream << "InteractiveViewer: \"" << getName() << "\" (total nr of displayed actors: " << actorMap.size() << ")" << Qt::endl;
                debugStream << Qt::endl;

                double position[3];
                double focalPoint[3];
                double viewUp[3];

                rendererWidget->getCameraSettings(position, focalPoint, viewUp);
                debugStream << "Camera Position: (" << position[0] << "," << position[1] << "," << position[2] << ")" << Qt::endl;
                debugStream << "Camera Focal Point: (" << focalPoint[0] << "," << focalPoint[1] << "," << focalPoint[2] << ")" << Qt::endl;
                debugStream << "Camera Up Direction: (" << viewUp[0] << "," << viewUp[1] << "," << viewUp[2] << ")" << Qt::endl;
                debugStream << Qt::endl;

                double bounds[6];
                rendererWidget->computeVisiblePropBounds(bounds);
                debugStream << "Bounds Of All Components: xmin=" << bounds[0] << " xmax=" << bounds[1]
                            << " ymin=" << bounds[2] << " ymax=" << bounds[3]
                            << " zmin=" << bounds[4] << " zmax=" << bounds[5] << Qt::endl;

                getBoundsOfSelected(bounds);
                debugStream << "Bounds Of Selected Components: xmin=" << bounds[0] << " xmax=" << bounds[1]
                            << " ymin=" << bounds[2] << " ymax=" << bounds[3]
                            << " zmin=" << bounds[4] << " zmax=" << bounds[5] << Qt::endl;
                debugStream << Qt::endl;

                debugStream << "== Component vtkProp ==" << Qt::endl;

                for (auto& c : actorMap.uniqueKeys()) {
                    debugStream << "- Component: \"" << c->getName()
                                << "\" of type \"" << c->metaObject()->className()
                                << "\" has " << actorMap.values(c).size() << " vtkProp:" << Qt::endl;
                    std::list<vtkSmartPointer <vtkProp> > allActors;
                    for (auto& ac : actorMap.values(c)) {
                        allActors.push_back(ac);
                    };
                    allActors.sort();
                    allActors.unique();

                    for (auto& p : allActors) {
                        // get more information by comparing with the getActor/getProp method of c
                        bool found = false;

                        switch (myType) {
                            case GEOMETRY_VIEWER:
                                if (c->getActor(InterfaceGeometry::Surface) == p) {
                                    found = true;
                                    debugStream << "\t- Surface Actor";
                                }
                                else if (c->getActor(InterfaceGeometry::Wireframe) == p) {
                                    found = true;
                                    debugStream << "\t- Wireframe Actor";
                                }
                                else if (c->getActor(InterfaceGeometry::Points) == p) {
                                    found = true;
                                    debugStream << "\t- Points Actor";
                                }

                                break;

                            case SLICE_VIEWER:
                                if (c->getPixelActor() == p) {
                                    found = true;
                                    debugStream << "\t- Pixel Actor";
                                }
                                else if (c->get2DImageActor() == p) {
                                    found = true;
                                    debugStream << "\t- 2D Image Actor";
                                }
                                else if (c->getPickPlaneActor() == p) {
                                    found = true;
                                    debugStream << "\t- Picked Plane Actor";
                                }

                                break;

                            default:
                                // no other thing is possible
                                break;
                        }

                        // check additional prop
                        if (!found) {
                            unsigned int i = 0;

                            while (i < c->getNumberOfProp() && c->getProp(i) != p) {
                                i++;
                            }

                            if (i == c->getNumberOfProp()) {
                                debugStream << "\t- Unknown Prop";
                            }
                            else {
                                debugStream << "\t- Additional Prop #" << i;
                            }
                        }

                        p->Modified();
                        const double* propBounds;
                        propBounds = p->GetBounds();
                        debugStream << ": x[" << propBounds[0] << ".." << propBounds[1]
                                    << "] y[" << propBounds[2] << ".." << propBounds[3]
                                    << "] z[" << propBounds[4] << ".." << propBounds[5] << "]" << Qt::endl;
                    }

                }

                debugStream << Qt::endl << "=== Transformation manager state ===" << Qt::endl;
                debugStream << TransformationManager::toString() << Qt::endl;
                debugStream << "=== (end of debug information) ===" << Qt::endl;
                CAMITK_INFO(tr("InteractiveViewer Debug Information: %1").arg(debugString))
            }

            break;

        /// NOTE:
        ///
        /// PLEASE, PLEASE, PLEASE, PLEASE, PLEASE, PLEASE?
        ///
        /// DO NOT FORGET TO ADD A NEW LINE IN THE "WHAT'S THIS" MESSAGE (see constructor)
        ///
        /// The call to methods startWhatsThisSection and addWhatsThisItem is a good comment line
        /// to add here (see below!). Please use the same order here than in the
        /// what's this help (by category, then by function, then by key name)
        ///
        /// (PLEASE)

        default:                                // If not an interesting key,
            break;
    }

}

//---------------------resetCamera------------------------
void InteractiveViewer::resetCamera() {
    if (myType == SLICE_VIEWER && actorMap.size() >= 1) {
        double bounds[6];
        // get the first Component to determine the right bounds
        actorMap.begin().key()->get2DImageActor()->GetBounds(bounds);

        // scale correctly for square objects like a slice
        // instead of a bounding box, it transforms the bounds in a enclosed
        // box, thus the radius used in the VTK ResetCamera is now smaller
        // and correspond to the longest side of the slice divided by 2.0
        // => VTK is "tricked"
        double max = 0.0;
        double radius = 0.0;
        double center[3];
        double halfDist[3];

        for (unsigned int i = 0; i < 3; i++) {
            halfDist[i] = (bounds[i * 2 + 1] - bounds[i * 2]) / 2.0;
            center[i] = bounds[i * 2] + halfDist[i];
            radius += halfDist[i] * halfDist[i];

            if (halfDist[i] > max) {
                max = halfDist[i];
            }
        }

        radius = sqrt(radius);

        for (unsigned int i = 0; i < 3; i++) {
            // rescale
            halfDist[i] *= max / radius;
            // recompute bounds
            bounds[i * 2] = center[i] - halfDist[i];
            bounds[i * 2 + 1] = center[i] + halfDist[i];
        }

        rendererWidget->resetCamera(bounds);
    }
    else {
        // automatically set the camera so that everything is visible
        rendererWidget->resetCamera();
    }
}


// ---------------------- setActiveCamera ----------------------------
void InteractiveViewer::setActiveCamera(QString cameraName) {
    if (cameraMap.contains(cameraName)) {
        rendererWidget->setActiveCamera(cameraMap.value(cameraName));
    }
}


void InteractiveViewer::setCameraOrientation(InteractiveViewer::CameraOrientation orientation) {
    QStringList letters;
    const AnatomicalOrientation& anatomicalOrientation = getFrame()->getAnatomicalOrientation();
    switch (orientation) {
        case CameraOrientation::XY:
            getRendererWidget()->setCameraOrientation(RendererWidget::RIGHT_UP);
            if (anatomicalOrientation.isUnknown()) {
                letters = QStringList{"-X", "+X", "+Y", "-Y"};
            }
            else {
                letters = QStringList{anatomicalOrientation.getMinLabel(0), anatomicalOrientation.getMaxLabel(0),
                                      anatomicalOrientation.getMaxLabel(1), anatomicalOrientation.getMinLabel(1)};
            }
            break;
        case CameraOrientation::XZ:
            getRendererWidget()->setCameraOrientation(RendererWidget::LEFT_BACK);
            if (anatomicalOrientation.isUnknown()) {
                letters = QStringList{"-X", "+X", "+Z", "-Z"};
            }
            else {
                letters = QStringList{anatomicalOrientation.getMinLabel(0), anatomicalOrientation.getMaxLabel(0),
                                      anatomicalOrientation.getMaxLabel(2), anatomicalOrientation.getMinLabel(2)};
            }
            break;
        case CameraOrientation::YZ:
            getRendererWidget()->setCameraOrientation(RendererWidget::BACK_DOWN);
            if (anatomicalOrientation.isUnknown()) {
                letters = QStringList{"-Y", "+Y", "+Z", "-Z"};
            }
            else {
                letters = QStringList{anatomicalOrientation.getMinLabel(1), anatomicalOrientation.getMaxLabel(1),
                                      anatomicalOrientation.getMaxLabel(2), anatomicalOrientation.getMinLabel(2)};
            }
            break;
        case CameraOrientation::AXIAL:
            getRendererWidget()->setCameraOrientation(RendererWidget::RIGHT_DOWN);
            if (anatomicalOrientation.isUnknown()) {
                letters = QStringList{"-X", "+X", "-Y", "+Y"};
            }
            else {
                letters = QStringList{anatomicalOrientation.getMinLabel(0), anatomicalOrientation.getMaxLabel(0),
                                      anatomicalOrientation.getMinLabel(1), anatomicalOrientation.getMaxLabel(1)};
            }
            break;
        case CameraOrientation::CORONAL:
            getRendererWidget()->setCameraOrientation(RendererWidget::LEFT_BACK);
            if (anatomicalOrientation.isUnknown()) {
                // note: a classic coronal viewer should have QStringList{"R", "L", "S", "I"};
                letters = QStringList{"-X", "+X", "+Z", "-Z"};
            }
            else {
                letters = QStringList{anatomicalOrientation.getMinLabel(0), anatomicalOrientation.getMaxLabel(0),
                                      anatomicalOrientation.getMaxLabel(2), anatomicalOrientation.getMinLabel(2)};
            }
            break;
        case CameraOrientation::SAGITTAL:
            getRendererWidget()->setCameraOrientation(RendererWidget::BACK_DOWN);
            if (anatomicalOrientation.isUnknown()) {
                // note: a classic sagittal viewer should have  QStringList{"A", "P", "S", "I"};
                letters = QStringList{"-Y", "+Y", "+Z", "-Z"};
            }
            else {
                letters = QStringList{anatomicalOrientation.getMinLabel(1), anatomicalOrientation.getMaxLabel(1),
                                      anatomicalOrientation.getMaxLabel(2), anatomicalOrientation.getMinLabel(2)};
            }
            break;
        case CameraOrientation::CUSTOM:
        default:
            letters = QStringList{"", "", "", ""};
    }
    getRendererWidget()->setOrientationDecorationsLetters(letters);
    toggleOrientationDecorations(true);
}

InteractiveViewer::CameraOrientation InteractiveViewer::getCameraOrientation() {
    return static_cast<InteractiveViewer::CameraOrientation>(propertyObject->getPropertyValue("Camera Orientation").toInt());
}

// ---------------------- getCamera ----------------------------
vtkSmartPointer<vtkCamera> InteractiveViewer::getCamera(QString cameraName) {
    if (cameraMap.contains(cameraName)) {
        return cameraMap.value(cameraName);
    }
    else {
        // create a new camera
        vtkSmartPointer<vtkCamera> newCam = vtkSmartPointer<vtkCamera>::New();

        // associate it with the given name
        cameraMap.insert(cameraName, newCam);
        return newCam;
    }
}

// ---------------------- slotSliderChanged ----------------------------
void InteractiveViewer::sliderChanged(int i) {
    if (myType == SLICE_VIEWER) {
        if (actorMap.size() > 0) {
            // current interaction changed
            isChangingSlice = true;

            actorMap.begin().key()->setSlice(i);

            // the Component was modified in the viewer, ask the Component to update all its other viewers
            actorMap.begin().key()->refresh();

            // user has changed the slide, the selection has changed then
            emit selectionChanged();

            // current interaction is processed
            isChangingSlice = false;
        }
    }
}

//--------------------- setGradientBackground --------------
void InteractiveViewer::setGradientBackground(bool g) {
    propertyObject->setProperty(backgroundGradientColorProperty->getName().toStdString().c_str(), g);
}

// --------------- setBackgroundColor --------------------
void InteractiveViewer::setBackgroundColor(QColor c) {
    propertyObject->setProperty(backgroundColorProperty->getName().toStdString().c_str(), c);
}

//--------------------- getBounds -------------------
void InteractiveViewer::getBounds(double* bounds) {
    rendererWidget->computeVisiblePropBounds(bounds);
}

//--------------------- getBoundsOfSelected -------------------
void InteractiveViewer::getBoundsOfSelected(double* bounds) {
    // init the bound to have the bigger box
    for (unsigned int i = 0; i < 6; i += 2) {
        bounds[i] = VTK_DOUBLE_MAX;
        bounds[i + 1] = -VTK_DOUBLE_MAX;
    }

    // check the bound of all selected Component in the InteractiveViewer
    for (auto comp : actorMap.keys()) {
        vtkSmartPointer<vtkProp> actor = actorMap.value(comp);

        // do not take the frame axis actor into account (this is just a decoration)
        if (comp->isSelected()) {
            // get the bounds
            double* actorBounds;
            double compBounds[6];
            actorBounds = actor->GetBounds();

            // if the bounds of the actor are wrong use the component
            if (actorBounds == nullptr) {
                if (comp->getRepresentation() == Component::GEOMETRY) {
                    comp->getBounds(compBounds);
                }
                else {
                    for (unsigned int i = 0; i < 6; i += 2) {
                        compBounds[i] = VTK_DOUBLE_MAX;
                        compBounds[i + 1] = -VTK_DOUBLE_MAX;
                    }
                }
            }
            else {
                // copy actor bounds to compBounds
                for (int i = 0; i < 6; i++) {
                    compBounds[i] = actorBounds[i];
                }
            }

            // check against min/max
            for (unsigned int i = 0; i < 6; i += 2) {
                if (compBounds[i] < bounds[i]) {
                    bounds[i] = compBounds[i];
                }

                if (compBounds[i + 1] > bounds[i + 1]) {
                    bounds[i + 1] = compBounds[i + 1];
                }
            }

        }
    }

    // if nothing element is selected then default to the scene bounds
    if (bounds[0] == VTK_DOUBLE_MAX || bounds[1] == -VTK_DOUBLE_MAX
            || bounds[2] == VTK_DOUBLE_MAX || bounds[3] == -VTK_DOUBLE_MAX
            || bounds[4] == VTK_DOUBLE_MAX || bounds[4] == -VTK_DOUBLE_MAX) {
        getBounds(bounds);
    }
}

//------------------------- setSideFrameVisible ----------------------------
void InteractiveViewer::setSideFrameVisible(bool visibility) {
    if (sideFrame) {
        sideFrame->setVisible(visibility);
    }
}

//------------------------- toggleInterpolation ----------------------------
void InteractiveViewer::toggleInterpolation() {
    for (auto comp : actorMap.keys()) {
        if (comp->getRepresentation() == Component::SLICE) {
            bool state = comp->get2DImageActor()->GetInterpolate();
            comp->get2DImageActor()->SetInterpolate(!state);
            state = comp->get3DImageActor()->GetInterpolate();
            comp->get3DImageActor()->SetInterpolate(!state);

            // the Component was modified inside the viewer, refresh all its other viewer
            comp->refresh();
        }
    }
}

//------------------------- resetLUT ----------------------------
void InteractiveViewer::resetLUT() {
    for (auto comp : actorMap.keys()) {
        if (comp->getRepresentation() == Component::SLICE) {
            // reset property LUT
            vtkSmartPointer<vtkImageProperty> initProp = vtkImageProperty::New();
            initProp->SetInterpolationTypeToLinear();
            initProp->SetAmbient(1.0);
            initProp->SetDiffuse(0.0);
            comp->get2DImageActor()->SetProperty(initProp);

            // the Component was modified inside the viewer, refresh all its other viewer
            comp->refresh();
        }
    }
}

//---------------------updateSelectionDisplay------------------------
void InteractiveViewer::updateSelectionDisplay(Component* comp) {
    InterfaceGeometry::EnhancedModes m = InterfaceGeometry::Normal;

    switch ((HighlightMode) propertyObject->property(highlightModeProperty->getName().toStdString().c_str()).toInt()) {   //set the option for the item(s) not selected
        case InteractiveViewer::SELECTION:

            // nobody is highlighted
            // shade if not selected
            if (!comp->isSelected()) {
                m = InterfaceGeometry::Shaded;
            }

            break;

        case InteractiveViewer::SELECTION_ONLY:

            // nobody is highlighted,
            if (!comp->isSelected()) {
                // hide if not selected
                m = InterfaceGeometry::Hidden;
            }

            break;

        default:
            break;
    }

    comp->setEnhancedModes(m);

}

// ---------------------- initPicking ----------------------------
void InteractiveViewer::initPicking(PickingMode pickingMode) {

    this->pickingMode = pickingMode;

    switch (pickingMode) {
        case PIXEL_PICKING: {
            vtkSmartPointer< vtkCellPicker > picker = vtkSmartPointer<vtkCellPicker>::New();
            picker->SetTolerance(0.01);
            rendererWidget->setPicker(picker);
            // Update actions for 3D viewer menu/toolbar
            if (myType == GEOMETRY_VIEWER && !pickPointAction->isChecked()) {
                pickPointAction->setChecked(true);
            }
            break;
        }

        case POINT_PICKING: {
            vtkSmartPointer< vtkPointPicker > picker = vtkSmartPointer< vtkPointPicker >::New();
            picker->SetTolerance(0.01);
            this->rendererWidget->setPicker(picker);
            this->rendererWidget->setAreaPicking(false);
            // Update actions for 3D viewer menu/toolbar
            if (myType == GEOMETRY_VIEWER && !pickPointAction->isChecked()) {
                pickPointAction->setChecked(true);
            }
            break;
        }

        case CELL_PICKING: {
            vtkSmartPointer< vtkCellPicker > picker = vtkSmartPointer< vtkCellPicker >::New();
            picker->SetTolerance(0.0001);
            this->rendererWidget->setPicker(picker);
            this->rendererWidget->setAreaPicking(false);
            // Update actions for 3D viewer menu/toolbar
            if (myType == GEOMETRY_VIEWER && !pickCellAction->isChecked()) {
                pickCellAction->setChecked(true);
            }
            break;
        }

        case AREA_CELL_PICKING: {
            vtkSmartPointer< vtkRenderedAreaPicker > picker = vtkSmartPointer< vtkRenderedAreaPicker >::New();
            this->rendererWidget->setPicker(picker);
            this->rendererWidget->setAreaPicking(true);
            // Update actions for 3D viewer menu/toolbar
            if (myType == GEOMETRY_VIEWER && !pickCellRegionAction->isChecked()) {
                pickCellRegionAction->setChecked(true);
            }
            break;
        }

        case AREA_POINT_PICKING: {
            vtkSmartPointer< vtkRenderedAreaPicker > picker = vtkSmartPointer< vtkRenderedAreaPicker >::New();
            this->rendererWidget->setPicker(picker);
            this->rendererWidget->setAreaPicking(true);
            // Update actions for 3D viewer menu/toolbar
            if (myType == GEOMETRY_VIEWER && !pickPointRegionAction->isChecked()) {
                pickPointRegionAction->setChecked(true);
            }
            break;
        }

        case LOCATION_PICKING: {
            vtkSmartPointer< vtkPropPicker > picker = vtkSmartPointer< vtkPropPicker >::New();
            this->rendererWidget->setPicker(picker);
            this->rendererWidget->setAreaPicking(false);
            // Update actions for 3D viewer menu/toolbar
            if (myType == GEOMETRY_VIEWER && !pickLocationAction->isChecked()) {
                pickLocationAction->setChecked(true);
            }
            break;
        }

        default:
        case NO_PICKING:
            rendererWidget->setPicker(nullptr);
            break;
    }

    if (myWidget != nullptr && myType == GEOMETRY_VIEWER && static_cast<InteractiveViewer::PickingMode>(pickingCombo->currentIndex()) != pickingMode) {
        pickingCombo->setCurrentIndex(static_cast<int>(pickingMode)); // warning: verify this if enum is updated
    }

    // picking effect no yet decided
    pickingEffectUpdated = true;
}

//------------------------- screenshot ----------------------------
void InteractiveViewer::screenshot() {
    // ask the user
    QString filename = QFileDialog::getSaveFileName(nullptr, tr("Save Screenshot As.."), QString("camitk-%1.png").arg(QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate)), ScreenshotFormatInfo::fileFilters());

    if (!filename.isEmpty()) {
        screenshot(filename);
    }
}

void InteractiveViewer::screenshot(QString filename) {
    // refresh the screen after the screenshot (so that the dialog does not appear in the screenshot!
    refresh(this);
    // take the snapshot
    rendererWidget->screenshot(filename);
}

//------------------------- getColorScale ----------------------------
bool InteractiveViewer::getColorScale() const {
    return rendererWidget->getColorScale();
}

//------------------------- setColorScale ----------------------------
void InteractiveViewer::setColorScale(bool state) {
    rendererWidget->setColorScale(state);
}

//------------------------- setColorScaleMinMax ----------------------------
void InteractiveViewer::setColorScaleMinMax(double min, double max) {
    rendererWidget->setColorScaleMinMax(min, max);
}

//------------------------- setColorScaleTitle ----------------------------
void InteractiveViewer::setColorScaleTitle(QString title) {

    rendererWidget->setColorScaleTitle(title);
}

// ------------- updateActions -----------------
void InteractiveViewer::updateActions() {
    if (myType == GEOMETRY_VIEWER) {
        // block signals
        surfaceAction->blockSignals(true);
        wireframeAction->blockSignals(true);
        pointsAction->blockSignals(true);
        glyphAction->blockSignals(true);
        toggleLabelAction->blockSignals(true);

        surfaceAction->setEnabled(true);
        wireframeAction->setEnabled(true);
        pointsAction->setEnabled(true);
        glyphAction->setEnabled(true);
        toggleLabelAction->setEnabled(true);

        if (colorAction) {
            colorAction->setEnabled(true);
        }

        // update the rendering mode buttons
        switch (Application::getSelectedComponents().size()) {
            case 0:
                surfaceAction->setEnabled(false);
                wireframeAction->setEnabled(false);
                pointsAction->setEnabled(false);
                glyphAction->setEnabled(false);
                toggleLabelAction->setEnabled(false);

                if (colorAction) {
                    colorAction->setEnabled(false);
                }

                break;

            case 1: {
                Component* first = Application::getSelectedComponents().first();

                if (first->getVisibility(this->getName()) && first->getRepresentation() == Component::GEOMETRY) {
                    surfaceAction->setChecked(first->getRenderingModes() & InterfaceGeometry::Surface);
                    wireframeAction->setChecked(first->getRenderingModes() & InterfaceGeometry::Wireframe);
                    pointsAction->setChecked(first->getRenderingModes() & InterfaceGeometry::Points);
                    glyphAction->setChecked(Application::getSelectedComponents().first()->getProp("glyph")->GetVisibility());
                    toggleLabelAction->setChecked(Application::getSelectedComponents().first()->getProp("label")->GetVisibility());
                }
                else {
                    surfaceAction->setEnabled(false);
                    wireframeAction->setEnabled(false);
                    pointsAction->setEnabled(false);
                    glyphAction->setEnabled(false);
                    toggleLabelAction->setEnabled(false);

                    if (colorAction) {
                        colorAction->setEnabled(false);
                    }
                }

                break;
            }

            default: {
                // they should all have the same rendering mode to be able to determine a default state
                InterfaceGeometry::RenderingModes m = Application::getSelectedComponents() [0]->getRenderingModes();
                int i = 1;

                while (i < Application::getSelectedComponents().size() && (m == Application::getSelectedComponents() [i]->getRenderingModes())) {
                    i++;
                }

                if (i == Application::getSelectedComponents().size()) {
                    surfaceAction->setChecked(m & InterfaceGeometry::Surface);
                    wireframeAction->setChecked(m & InterfaceGeometry::Wireframe);
                    pointsAction->setChecked(m & InterfaceGeometry::Points);
                }
                else {
                    surfaceAction->setChecked(false);
                    pointsAction->setChecked(false);
                    wireframeAction->setChecked(false);
                }

                Component* first = Application::getSelectedComponents().first();

                if (first->getVisibility(this->getName()) && first->getRepresentation() == Component::GEOMETRY) {
                    glyphAction->setChecked(first->getProp("glyph")->GetVisibility());
                    toggleLabelAction->setChecked(first->getProp("label")->GetVisibility());
                }

                break;
            }
        }

        // unblock signals
        surfaceAction->blockSignals(false);
        wireframeAction->blockSignals(false);
        pointsAction->blockSignals(false);
        glyphAction->blockSignals(false);
        toggleLabelAction->blockSignals(false);

        if (viewerMenu) {
            renderingMenu->setEnabled(Application::getSelectedComponents().size() > 0);
        }
    }
}

// ------------- slotRenderingActorsChanged -----------------
void InteractiveViewer::renderingActorsChanged() {
    InterfaceGeometry::RenderingModes m = InterfaceGeometry::None;

    if (surfaceAction->isChecked()) {
        m |= InterfaceGeometry::Surface;
    }

    if (wireframeAction->isChecked()) {
        m |= InterfaceGeometry::Wireframe;
    }

    if (pointsAction->isChecked()) {
        m |= InterfaceGeometry::Points;
    }

    // update the rendering mode of selected
    for (Component* comp : Application::getSelectedComponents()) {
        comp->setRenderingModes(m);
    }

    refresh(this);
}

// --------------- setHighlightMode --------------------
void InteractiveViewer::setHighlightMode() {
    HighlightMode highlightMode = (HighlightMode) propertyObject->property(highlightModeProperty->getName().toStdString().c_str()).toInt();
    refresh(this);
    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".InteractiveViewer." + objectName().simplified().replace(" ", ""));
    settings.setValue("highlightMode", highlightMode);
    settings.endGroup();
}

// --------------- slotHighlightModeChanged -----------------------------------
void InteractiveViewer::highlightModeChanged(QAction* selectedAction) {
    if (selectedAction == highlightSelectionAction) {
        propertyObject->setProperty(highlightModeProperty->getName().toStdString().c_str(), InteractiveViewer::SELECTION);
    }
    else if (selectedAction == highlightSelectionOnlyAction) {
        propertyObject->setProperty(highlightModeProperty->getName().toStdString().c_str(), InteractiveViewer::SELECTION_ONLY);
    }
    else {
        if (selectedAction == highlightOffAction) {
            propertyObject->setProperty(highlightModeProperty->getName().toStdString().c_str(), InteractiveViewer::OFF);
        }
    }

    refresh(this);
}

// -------------- slotViewControlModeChanged --------------
void InteractiveViewer::viewControlModeChanged(QAction* selectedAction) {
    if (selectedAction == controlModeTrackballAction) {
        rendererWidget->setControlMode(RendererWidget::TRACKBALL);
    }
    else {
        rendererWidget->setControlMode(RendererWidget::JOYSTICK);
    }
}

// ------------- slotBackgroundColor -----------------
void InteractiveViewer::backgroundColor() {
    QColor oldColor = propertyObject->property(backgroundColorProperty->getName().toStdString().c_str()).value<QColor>();
    QColor newColor = QColorDialog::getColor(oldColor);

    if (newColor.isValid()) {
        propertyObject->setProperty(backgroundColorProperty->getName().toStdString().c_str(), newColor);
    }
}

// ------------- slotToggleAxes -----------------
void InteractiveViewer::toggleAxes(bool f) {
    rendererWidget->toggleAxes(f);
    resetCamera();
    rendererWidget->refresh();
}

// ------------- toggleOrientationDecorations -----------------
void InteractiveViewer::toggleOrientationDecorations(bool f) {
    if (myType == SLICE_VIEWER) {
        rendererWidget->toggleOrientationDecorations(f);
        rendererWidget->refresh();
    }
}

// ------------- toggleLogo -----------------
void InteractiveViewer::toggleLogo(bool c) {
    rendererWidget->toggleLogo(c);
    rendererWidget->refresh();
}

// ------------- setLabel -----------------
void InteractiveViewer::setLabel(bool b) {
    // update the rendering mode of selected
    for (Component* comp : Application::getSelectedComponents()) {
        if (comp->getVisibility(this->getName()) && comp->getRepresentation() == Component::GEOMETRY) {
            comp->getProp("label")->SetVisibility(b);
        }
    }

    refresh(this);
}

// ------------- setGlyph -----------------
void InteractiveViewer::setGlyph(bool b) {
    // update the rendering mode of selected
    for (Component* comp : Application::getSelectedComponents()) {
        if (comp->getVisibility(this->getName()) && comp->getRepresentation() == Component::GEOMETRY) {
            comp->getProp("glyph")->SetVisibility(b);
        }
    }

    refresh(this);
}

// ------------- setLinesAsTubes -----------------
void InteractiveViewer::setLinesAsTubes(bool tubes) {
    propertyObject->setProperty(linesAsTubesProperty->getName().toStdString().c_str(), tubes);
}

// -------------- setBackfaceCulling --------------
void InteractiveViewer::setBackfaceCulling(bool b) {
    propertyObject->setProperty(backfaceCullingProperty->getName().toStdString().c_str(), b);
}

// -------------- setFxaaAntialiasing --------------
void InteractiveViewer::setFxaaAntialiasing(bool b) {
    propertyObject->setProperty(fxaaAntialiasingProperty->getName().toStdString().c_str(), b);
}

// -------------- setScreenshotAction --------------
void InteractiveViewer::setScreenshotAction(bool b) {
    propertyObject->setProperty(screenshotActionProperty->getName().toStdString().c_str(), b);

    if (screenshotActionMenu) {
        screenshotActionMenu->setVisible(b);
    }
}

//-------------------- pickingModeChanged ---------------------
void InteractiveViewer::pickingModeChanged(QAction* selectedAction) {
    if (selectedAction == pickCellAction) {
        initPicking(CELL_PICKING);
    }
    else if (selectedAction == pickPointAction) {
        initPicking(POINT_PICKING);
    }
    else if (selectedAction == pickCellRegionAction) {
        initPicking(AREA_CELL_PICKING);
    }
    else if (selectedAction == pickPointRegionAction) {
        initPicking(AREA_POINT_PICKING);
    }
    else if (selectedAction == pickLocationAction) {
        initPicking(LOCATION_PICKING);
    }
}

//-------------------- picked ---------------------
void InteractiveViewer::picked() {

    vtkSmartPointer<vtkAbstractPropPicker> picker = vtkAbstractPropPicker::SafeDownCast(rendererWidget->getInteractor()->GetPicker());

    if (!picker) {
        pickingEffectUpdated = false;
        return;
    }

    // get the Component that owns the picked actor
    Component* comp = actorMap.key(picker->GetProp3D());

    // if it is not a prop picker or if the selected actor is not a component one then good bye !
    if (!comp || (pickingMode == NO_PICKING)) {
        return;
    }

    vtkSmartPointer<vtkPicker> itemPicker;

    if (!pickingEffectUpdated && (itemPicker = vtkPicker::SafeDownCast(picker))) {
        pickingEffectUpdated = true;
        // decide of the state: if one of the possible picked is selected, then try unselect
        bool hasSelected = false;
        int i = 0;

        while (i < itemPicker->GetProp3Ds()->GetNumberOfItems() && !hasSelected) {
            vtkSmartPointer<vtkActor> actor = vtkActor::SafeDownCast(itemPicker->GetProp3Ds()->GetItemAsObject(i));
            Component* cpt = actorMap.key(actor);
            hasSelected = (cpt && cpt->isSelected());
            i++;
        }

        pickingEffectIsSelecting = !hasSelected;
    }

    switch (pickingMode) {
        case PIXEL_PICKING: {
            vtkSmartPointer<vtkPicker> pixelPicker = vtkPicker::SafeDownCast(picker);

            //-- get the points picked
            vtkSmartPointer<vtkPoints> pickedPlanePts = pixelPicker->GetPickedPositions();

            // Looking for the intersection point that as a zero coordinate in the Z direction.
            int pointIndex = 0;

            for (int i = 0 ; i < pickedPlanePts->GetNumberOfPoints(); i++) {
                if (pickedPlanePts->GetPoint(i) [2] == 0.0) {
                    pointIndex = i;
                    CAMITK_TRACE(QString("Picker found point %1 at %2, %3, %4").arg(i).arg(pickedPlanePts->GetPoint(i)[0]).arg(pickedPlanePts->GetPoint(i)[1]).arg(pickedPlanePts->GetPoint(i)[2]))
                }
            }

            if (pickedPlanePts != nullptr && pickedPlanePts->GetNumberOfPoints() > 0) {
                // picking worked
                isPicking = true;

                //-- tells the Component it's been picked in (x,y,z) converted in its data frame
                double pickedPoint[3];
                pickedPlanePts->GetPoint(pointIndex, pickedPoint); // It's in Viewer's Frame coordinates

                // TODO Special case for 2D Arbitrary: the picked coordinates are ArbitraryFrame, not Viewer's Frame (why ? see addActor special case)
                const FrameOfReference* pickedFrame = getFrame();
                ArbitrarySingleImageComponent* arbitrary = dynamic_cast<ArbitrarySingleImageComponent*>(comp);
                if (arbitrary != nullptr) {
                    pickedFrame = arbitrary->getArbitraryFrame();
                }
                Transformation* viewerFrameToComponentDataFrame = TransformationManager::getTransformation(pickedFrame, comp->getFrame());
                if (viewerFrameToComponentDataFrame != nullptr) { // Transform into Component's data Frame if necessary
                    viewerFrameToComponentDataFrame->getTransform()->TransformPoint(pickedPoint, pickedPoint);
                }
                comp->pixelPicked(pickedPoint[0], pickedPoint[1], pickedPoint[2]);

                // picking is a kind of selection, but for pixel picking, only one Component is allowed at a time
                selectionChanged(comp);
            }
        }
        break;

        case POINT_PICKING: {
            vtkSmartPointer<vtkPointPicker> pointPicker = vtkPointPicker::SafeDownCast(picker);

            if (pointPicker) {
                vtkIdType currentId = pointPicker->GetPointId();

                if (currentId != -1) {
                    vtkSmartPointer<vtkIdTypeArray> ids = vtkSmartPointer<vtkIdTypeArray>::New();
                    ids->InsertNextValue(currentId);

                    // -- tells the component it has been picked with the pointId
                    comp->pointPicked(currentId, pickingEffectIsSelecting);

                    // 3D position of picked point
                    vtkSmartPointer<vtkPoints> pickedPlanePts = pointPicker->GetPickedPositions();
                    if (pickedPlanePts != nullptr && pickedPlanePts->GetNumberOfPoints() > 0) {
                        //-- tells the Component it's been picked in (x,y,z) converted in its frame
                        double pickedPoint[3];
                        pickedPlanePts->GetPoint(0, pickedPoint); // It's in Viewer's Frame coordinates
                        Transformation* viewerFrameToComponentDataFrame = TransformationManager::getTransformation(getFrame(), comp->getFrame());
                        if (viewerFrameToComponentDataFrame != nullptr) { // Transform into Component's data Frame if necessary
                            viewerFrameToComponentDataFrame->getTransform()->TransformPoint(pickedPoint, pickedPoint);
                        }
                        comp->pixelPicked(pickedPoint[0], pickedPoint[1], pickedPoint[2]);
                    }
                    // show informations on the status bar
                    double* pickedPos;
                    pickedPos = pointPicker->GetPickPosition();
                    Application::showStatusBarMessage("Picked : " + comp->getName() + ", Point#" + QString::number(currentId) + ", position =(" + QString::number(pickedPos[0]) + "," + QString::number(pickedPos[1]) + "," + QString::number(pickedPos[2]) + ")");

                    // pointPicked can have changed something
                    auto* mesh = dynamic_cast<MeshComponent*>(comp);

                    if (mesh) {
                        mesh->addToSelectedSelection(vtkSelectionNode::POINT, vtkSelectionNode::INDICES, ids);
                    }

                    emit selectionChanged();
                }
            }
        }
        break;

        case CELL_PICKING : {
            vtkSmartPointer<vtkCellPicker> cellPicker = vtkCellPicker::SafeDownCast(rendererWidget->getInteractor()->GetPicker());
            auto* mesh = dynamic_cast<MeshComponent*>(comp);

            if (cellPicker && mesh) {
                vtkIdType currentId = cellPicker->GetCellId();

                if (currentId != -1) {
                    vtkSmartPointer<vtkIdTypeArray> ids = vtkSmartPointer<vtkIdTypeArray>::New();
                    ids->InsertNextValue(currentId);

                    // tell the component it has been picked
                    comp->cellPicked(currentId, pickingEffectIsSelecting);
                    Application::showStatusBarMessage("Picked : " + comp->getName() + ", Cell#" + QString::number(currentId));

                    // cellPicked can have change something
                    mesh->addToSelectedSelection(vtkSelectionNode::CELL, vtkSelectionNode::INDICES, ids);
                    emit selectionChanged();
                }
            }
        }
        break;

        case AREA_CELL_PICKING : {
            vtkSmartPointer<vtkRenderedAreaPicker> areaPicker = vtkRenderedAreaPicker::SafeDownCast(rendererWidget->getInteractor()->GetPicker());
            auto* mesh = dynamic_cast<MeshComponent*>(comp);

            if (areaPicker && mesh) {
                vtkSmartPointer<vtkExtractSelectedFrustum> extractor = vtkSmartPointer<vtkExtractSelectedFrustum>::New();
                extractor->SetInputData(mesh->getPointSet());
                extractor->PreserveTopologyOff();
                // Should we transform the points ?
                Transformation* viewerFrameToComponentDataFrame = TransformationManager::getTransformation(getFrame(), comp->getFrame());
                if (viewerFrameToComponentDataFrame != nullptr && !viewerFrameToComponentDataFrame->getMatrix()->IsIdentity()) { // Transform into Component's data Frame if necessary
                    vtkPoints* clipPointsViewerFrame = areaPicker->GetClipPoints();
                    // Apply the transformation to the points
                    vtkSmartPointer<vtkPoints> clipPointsComponentFrame = vtkSmartPointer<vtkPoints>::New();
                    viewerFrameToComponentDataFrame->getTransform()->TransformPoints(clipPointsViewerFrame, clipPointsComponentFrame);
                    double vertices[8 * 4 /* homogenous coordinates */];
                    for (int i = 0; i < 8; i++) {
                        clipPointsComponentFrame->GetPoint(i, vertices + 4 * i);
                        vertices[i * 4 + 3] = 1.0; // homogenous coordinates scale factor
                    }
                    extractor->CreateFrustum(vertices);
                }
                else {
                    extractor->SetFrustum(areaPicker->GetFrustum());
                }
                extractor->Update();
                vtkDataSet* dataSet = vtkDataSet::SafeDownCast(extractor->GetOutput());

                if (dataSet && dataSet->GetCellData()->GetArray(0) != nullptr) {
                    Application::showStatusBarMessage("Picked : " + comp->getName() + " (" + QString::number(dataSet->GetCellData()->GetArray(0)->GetNumberOfTuples()) + " cells )");
                    mesh->addToSelectedSelection(vtkSelectionNode::CELL, vtkSelectionNode::INDICES, dataSet->GetCellData()->GetArray(0));
                    emit selectionChanged();
                }
                else {
                    CAMITK_INFO("ERROR no mesh cell picked in mesh " + comp->getName())
                }
            }
        }
        break;

        case AREA_POINT_PICKING : {
            vtkSmartPointer<vtkRenderedAreaPicker> areaPicker = vtkRenderedAreaPicker::SafeDownCast(rendererWidget->getInteractor()->GetPicker());
            auto* mesh = dynamic_cast<MeshComponent*>(comp);

            if (areaPicker && mesh) {
                vtkSmartPointer<vtkExtractSelectedFrustum> extractor = vtkSmartPointer<vtkExtractSelectedFrustum>::New();
                extractor->SetInputData(mesh->getPointSet());
                extractor->PreserveTopologyOff();
                // Should we transform the points ?
                Transformation* viewerFrameToComponentDataFrame = TransformationManager::getTransformation(getFrame(), comp->getFrame());
                if (viewerFrameToComponentDataFrame != nullptr && !viewerFrameToComponentDataFrame->getMatrix()->IsIdentity()) { // Transform into Component's data Frame if necessary
                    vtkPoints* clipPointsViewerFrame = areaPicker->GetClipPoints();
                    // Apply the transformation to the points
                    vtkSmartPointer<vtkPoints> clipPointsComponentFrame = vtkSmartPointer<vtkPoints>::New();
                    viewerFrameToComponentDataFrame->getTransform()->TransformPoints(clipPointsViewerFrame, clipPointsComponentFrame);
                    double vertices[8 * 4 /* homogenous coordinates */];
                    for (int i = 0; i < 8; i++) {
                        clipPointsComponentFrame->GetPoint(i, vertices + 4 * i);
                        vertices[i * 4 + 3] = 1.0; // homogenous coordinates scale factor
                    }
                    extractor->CreateFrustum(vertices);
                }
                else {
                    extractor->SetFrustum(areaPicker->GetFrustum());
                }

                extractor->SetFieldType(vtkSelection::POINT);
                extractor->Update();
                vtkDataSet* dataSet = vtkDataSet::SafeDownCast(extractor->GetOutput());

                if (dataSet && dataSet->GetCellData()->GetArray(0) != nullptr) {
                    Application::showStatusBarMessage("Picked : " + comp->getName() + " (" + QString::number(dataSet->GetPointData()->GetArray(0)->GetNumberOfTuples()) + " points )");
                    mesh->addToSelectedSelection(vtkSelectionNode::POINT, vtkSelectionNode::INDICES, dataSet->GetPointData()->GetArray(0));
                    emit selectionChanged();
                }
                else {
                    CAMITK_INFO("ERROR no mesh point picked in mesh " + comp->getName())
                }
            }
        }
        break;

        case LOCATION_PICKING: {
            vtkSmartPointer<vtkPropPicker> propPicker = vtkPropPicker::SafeDownCast(picker);

            if (propPicker) {
                // TODO check which component was picked and inform that it was selected and where (pointPicked/pixelPicked)
                // show informations on the status bar
                double* pickedPos;
                pickedPos = propPicker->GetPickPosition();
                Application::showStatusBarMessage("Picked position in frame '" + getFrame()->getName() + "': (" + QString::number(pickedPos[0]) + "," + QString::number(pickedPos[1]) + "," + QString::number(pickedPos[2]) + ")");
            }
        }
        break;

        case NO_PICKING :
        default :
            break;
    }

    isPicking = false;
}

//-------------------- rightClick ---------------------
void InteractiveViewer::rightClick() {
    if (QApplication::keyboardModifiers() == Qt::ControlModifier) {
        // display the popup menu
        getMenu()->popup(QCursor::pos());
        // generate a synthetic event to tell the renderer the right button was released
        // see Qt Quarterly Issue 11 · Q3 2004
        QApplication::postEvent(rendererWidget, new QMouseEvent(QEvent::MouseButtonRelease, QCursor::pos(), Qt::RightButton, Qt::RightButton, Qt::ControlModifier));
    }
}


//-------------------- createProperties ---------------------
void InteractiveViewer::createProperties() {

    propertyObject = new PropertyObject(getName());

    // 3D Viewer properties
    // highlight mode
    highlightModeProperty = new Property("Highlight Mode", InteractiveViewer::SELECTION,
                                         "Select the highlight mode for selecting components. \n OFF: both selected and non-selected Components are in default mode \n SELECTION: the selected Components are in default mode, the non-selected Components are shaded \n SELECTION_ONLY: the selected Components are in default mode, the non-selected are hidden", "");
    highlightModeProperty->setEnumTypeName("camitk::InteractiveViewer::HighlightMode");
    QStringList highlightModeNames;
    highlightModeNames << "Off" << "Selection" << "Selection only";
    highlightModeProperty->setAttribute("enumNames", highlightModeNames);
    propertyObject->addProperty(highlightModeProperty);

    // camera orientation
    cameraOrientationProperty = new Property("Camera Orientation", static_cast<int>(InteractiveViewer::CameraOrientation::AXIAL),
            "Select the camera orientation. \n XY to display the XY plane, XZ, YZ, or Other for custom orientation", "");
    cameraOrientationProperty->setEnumTypeName("camitk::InteractiveViewer::CameraOrientation");
    QStringList cameraOrientationNames;
    cameraOrientationNames << "XY" << "XZ" << "YZ" << "Axial" << "Coronal" << "Sagittal" << "Custom";
    cameraOrientationProperty->setAttribute("enumNames", cameraOrientationNames);
    propertyObject->addProperty(cameraOrientationProperty);

    // background color
    backgroundColorProperty = new Property("Background color", QColor(255, 255, 255), "The background color of this viewer.", "");
    propertyObject->addProperty(backgroundColorProperty);

    // background color property
    backgroundGradientColorProperty = new Property("Use a background gradient color?", true, "Does this viewer use a gradient to render its background color?", "");
    propertyObject->addProperty(backgroundGradientColorProperty);

    // lines as tubes property
    linesAsTubesProperty = new Property("Lines considered as tube?", false, "Does this viewer replace drawn lines as tubes?", "");
    propertyObject->addProperty(linesAsTubesProperty);

    // screenshot action property
    screenshotActionProperty = new Property("Screenshot button visible", false, "Is the screenshot button visible in the slice viewer sidebar", "");
    propertyObject->addProperty(screenshotActionProperty);

    // backface culling property
    backfaceCullingProperty = new Property("Backface culling", false, "Compute the non visible polygons and display them?", "");
    propertyObject->addProperty(backfaceCullingProperty);

    // fxaa antialiasing property
    fxaaAntialiasingProperty = new Property("FXAA Antialiasing", true, "Use FXAA antialiasing to smooth edges?", "");
    propertyObject->addProperty(fxaaAntialiasingProperty);

    // point size property
    pointSizeProperty = new Property("Point size?", 4.0, "The 3D point size of each node", "");
    propertyObject->addProperty(pointSizeProperty);

    // TODO BlockZOOM
}

// ---------------- eventFilter ----------------
bool InteractiveViewer::eventFilter(QObject* object, QEvent* event) {
    // Double click on myWidget -> change the FrameOfReference of the viewer
    if (object == myWidget && event->type() == QEvent::MouseButtonDblClick) {
        const auto frames = TransformationManager::getFramesOfReference();
        QStringList frameNames;
        int currentFrameIndex = 0;
        int idx = 0;
        for (const auto& fr : frames) {
            idx++;
            frameNames << QString::number(idx) + ". " + fr->getName();
            if (fr == frameOfReference.get()) {
                currentFrameIndex = frameNames.size() - 1;
            }
        }
        QString result = QInputDialog::getItem(nullptr, "FrameOfReference", "Select frame of reference", frameNames, currentFrameIndex, false);
        idx = result.split(". ")[0].toInt() - 1;
        if (result != frameNames[currentFrameIndex]) {
            InteractiveViewer::setFrame(TransformationManager::getFrameOfReferenceOwnership(frames[idx]));
        }
        return true;
    }
    else {
        // watch propertyObject instance for dynamic property changes
        if (event->type() == QEvent::DynamicPropertyChange && object == propertyObject) {
            // first perform the proper action
            // and then save the settings accordingly

            // highlight mode update
            HighlightMode highlightMode = (HighlightMode) propertyObject->property(highlightModeProperty->getName().toStdString().c_str()).toInt();

            // camera orientation update
            CameraOrientation orientation = static_cast<CameraOrientation>(propertyObject->property(cameraOrientationProperty->getName().toStdString().c_str()).toInt());
            setCameraOrientation(orientation);

            // background color update
            QColor backgroundColor = propertyObject->property(backgroundColorProperty->getName().toStdString().c_str()).value<QColor>();
            rendererWidget->setBackgroundColor(backgroundColor.redF(), backgroundColor.greenF(), backgroundColor.blueF());

            // background gradient color
            bool useGradientBackgroundColor = propertyObject->property(backgroundGradientColorProperty->getName().toStdString().c_str()).toBool();
            rendererWidget->setGradientBackground(useGradientBackgroundColor);

            // lines as tubes
            bool linesAsTubes = propertyObject->property(linesAsTubesProperty->getName().toStdString().c_str()).toBool();

            // backface culling
            bool backfaceCulling = propertyObject->property(backfaceCullingProperty->getName().toStdString().c_str()).toBool();
            rendererWidget->setBackfaceCulling(backfaceCulling);

            // FXAA antialiasing
            bool fxaaAntialiasing = propertyObject->property(fxaaAntialiasingProperty->getName().toStdString().c_str()).toBool();
            rendererWidget->setFxaaAntialiasing(fxaaAntialiasing);

            // screenshot action
            bool screenshotActionVisible = propertyObject->property(screenshotActionProperty->getName().toStdString().c_str()).toBool();

            // point size
            double pointSize = propertyObject->property(pointSizeProperty->getName().toStdString().c_str()).toDouble();
            rendererWidget->setPointSize(pointSize);

            // ..and finally refresh
            InteractiveViewer::refresh(this);
            rendererWidget->refresh();

            // save the settings
            QSettings& settings = Application::getSettings();
            settings.beginGroup(Application::getName() + ".InteractiveViewer." + objectName().simplified().replace(" ", ""));

            settings.setValue("highlightMode", highlightMode);
            settings.setValue("backgroundColor", backgroundColor.name());
            settings.setValue("gradientBackground", useGradientBackgroundColor);
            settings.setValue("linesAsTubes", linesAsTubes);
            settings.setValue("backfaceCulling", backfaceCulling);
            settings.setValue("fxaaAntialiasing", fxaaAntialiasing);
            settings.setValue("screenshotActionVisible", screenshotActionVisible);
            settings.setValue("pointSize", pointSize);
            settings.setValue("cameraOrientation", static_cast<int>(orientation));

            settings.endGroup();

            return true;
        }
        else {
            // otherwise pass the event on to the parent class
            return Viewer::eventFilter(object, event);
        }
    }
}

} // camitk namespace
