/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2024 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 "RendererWidget.h"
#include "InteractiveViewer.h"
#include "SliderSpinBoxWidget.h"
#include "Log.h"
#include "MeshComponent.h"
#include "MeshDataModel.h"
#include "PropertyObject.h"

// -- Qt stuff
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QFileDialog>
#include <QWhatsThis>
#include <QColorDialog>
#include <QToolBar>
#include <QSettings>
#include <QWidgetAction>
#include <QTextStream>
#include <QSplitter>
#include <QDateTime>

// -- 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 <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>

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 the frame as it will automatically be deleted
    // when the embedder widget will be deleted
}

// ---------------------- init ----------------------------
void InteractiveViewer::init() {
    // frame is NULL, but the rendererWidget and sliceSlider have to be instantiated
    // (even if the InteractiveViewer is not visible yet, it can still do some actions)
    frame = nullptr;

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

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

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

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

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

    if (myType == SLICE_VIEWER) {
        // by default, the 2D scenes are set to pick slice pixels
        initPicking(InteractiveViewer::PIXEL_PICKING);
    }
    else {
        // no picking
        initPicking(InteractiveViewer::NO_PICKING);
    }

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

    //-- connection for selection
    connector = vtkSmartPointer<vtkEventQtSlotConnect>::New();
#if VTK_MAJOR_VERSION < 9
    connector->Connect(rendererWidget->GetInteractor(), vtkCommand::EndPickEvent, this, SLOT(picked()));
#else
    connector->Connect(rendererWidget->interactor(), vtkCommand::EndPickEvent, this, SLOT(picked()));
#endif

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

    // init from settings
    initSettings();

}

// ---------------------- 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());
        propertyObject->setProperty(backgroundColorProperty->getName().toStdString().c_str(), bg);
    }
    else {
        // default is black
        bg.setNamedColor(settings.value("backgroundColor", QColor::fromRgbF(0.0, 0.0, 0.0)).toString());
    }

    // the gradient background for 3D viewers
    if (myType == GEOMETRY_VIEWER) {
        bool gradientBackground;
        gradientBackground = settings.value("gradientBackground", true).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
    RendererWidget::CameraOrientation cameraOrientation;
    cameraOrientation = (RendererWidget::CameraOrientation) settings.value("cameraOrientation", rendererWidget->getCameraOrientation()).toInt();
    rendererWidget->setCameraOrientation(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()
    if (myType == GEOMETRY_VIEWER) {
        propertyObject->installEventFilter(this);
        // trigger change for all the values
        InteractiveViewer::eventFilter(this, new QEvent(QEvent::DynamicPropertyChange));
    }

}

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

        //-- create the actions
        initActions();

        //-- build the frame
        frame = new InteractiveViewerFrame(nullptr, this);
        frame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);

        //-- handle layout
        auto* myLayout = new QHBoxLayout(frame);
        auto* horizontalSplitter = new QSplitter(frame);
        myLayout->addWidget(horizontalSplitter);
        myLayout->setSpacing(0);
        myLayout->setMargin(0);

        //-- show the renderer!
        rendererWidget->setParent(frame);
        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->setMargin(0);

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

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

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

            // 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);

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

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

        // TODO A feature disappeared when switching to vtk5...
        // use a vtkOutlineSource to highlight the picked actor (show-hide picked actor bounding box</td>
        // set the what's this text
        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("Alt+P", "Toggle point rendering");
        addWhatsThisItem("P", "Pick (flip selection flag for point/cell)");
        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
        frame->setWhatsThis(whatsThis);
    }

    return frame;
}

// ---------------------- getPropertyObject ----------------------------
PropertyObject* InteractiveViewer::getPropertyObject() {
    if (myType == GEOMETRY_VIEWER) {
        return propertyObject;
    }
    else {
        return nullptr;
    }
}

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

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

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

    //-- first remove non active Component (if InteractiveViewer was called by itself, no need to do that)
    if (v != this) {
        //-- check all present
        QList<Component*> compRendered = actorMap.keys();

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

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

    switch (myType) {
        case GEOMETRY_VIEWER:

            // check all Components
            foreach (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 (NULL is return if the actor is not appropriate, i.e. hidden or not in proper mode)
                        addActor(comp, comp->getActor(InterfaceGeometry::Surface));
                        addActor(comp, comp->getActor(InterfaceGeometry::Wireframe));
                        addActor(comp, comp->getActor(InterfaceGeometry::Points));

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

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

                if (comp->getFrameVisibility(this->getName())) {
                    addActor(comp, comp->getFrameAxisActor());
                }

            }

            break;

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

            foreach (Component* comp, compRendered) {
                removeAllActors(comp);
            }

            // only display the last selected (if exists otherwise the last added)
            int idOfLastSelected = Application::getSelectedComponents().size() - 1;

            if (idOfLastSelected >= 0) {
                while (idOfLastSelected >= 0 && !(Application::getSelectedComponents() [idOfLastSelected]->getVisibility(this->getName()) && Application::getSelectedComponents() [idOfLastSelected]->getRepresentation() == Component::SLICE)) {
                    idOfLastSelected--;
                }
            }

            if (idOfLastSelected >= 0) {

                // there is valid Component to display
                Component* comp = Application::getSelectedComponents() [idOfLastSelected];
                // add both the slices, the plane used for picking and the pixel highlighter
                addActor(comp, comp->getPixelActor());
                addActor(comp, comp->get2DImageActor());
                addActor(comp, comp->getPickPlaneActor());

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

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

                // no selection, get the last added Component
                idOfLastSelected = allComponents.size() - 1;

                while (idOfLastSelected >= 0 && !(allComponents[idOfLastSelected]->getVisibility(this->getName()) && allComponents[idOfLastSelected]->getRepresentation() == Component::SLICE)) {
                    idOfLastSelected--;
                }

                if (idOfLastSelected >= 0) {

                    // there is valid Component to display
                    Component* comp = allComponents[idOfLastSelected];
                    // add both the slices, the plane used for picking and the pixel highlighter
                    addActor(comp, comp->getPixelActor());
                    addActor(comp, comp->get2DImageActor());
                    addActor(comp, comp->getPickPlaneActor());

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

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

            break;
    }

    // refresh only camera on slice viewer which are not being picked nor is changing slice or 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
        }

        rendererWidget->refresh();
    }
    else {
        // there is some difference => reset camera in case of Geometry Viewer
        rendererWidget->updateAxes();
        resetCamera();
        displayedTopLevelComponents = Application::getTopLevelComponents().size();
    }

    //-- update actions
    updateActions();
}

// ---------------------- refreshRenderer ----------------------------
void InteractiveViewer::refreshRenderer() {
    rendererWidget->refresh();
}

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

    foreach (vtkSmartPointer<vtkProp> a, actorRendered) {
        rendererWidget->removeProp(a);
    }

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

// ---------------------- addActor ----------------------------
void InteractiveViewer::addActor(Component* comp, vtkSmartPointer<vtkProp> a) {
    if (a) {
        rendererWidget->addProp(a);
        actorMap.insert(comp, a);
    }
}

// ---------------------- getMenu ----------------------------
QMenu* InteractiveViewer::getMenu() {
    if (frame && !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);
        }

        // AxesView mode
        QMenu* cameraOrientationMenu = new QMenu("Camera Orientation");
        options->addMenu(cameraOrientationMenu);
        cameraOrientationMenu->addAction(cameraOrientationRightDownAction);
        cameraOrientationMenu->addAction(cameraOrientationLeftUpAction);
        cameraOrientationMenu->addAction(cameraOrientationRightUpAction);

        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(pickPointAction);
            pickingMenu->addAction(pickCellAction);
            viewerMenu->addMenu(pickingMenu);

            viewerMenu->addMenu(renderingMenu);
        }

        viewerMenu->addAction(screenshotAction);

        //-- update actions
        updateActions();
    }

    return viewerMenu;
}

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

            viewerToolbar->addAction(pickPointAction);
            viewerToolbar->addAction(pickCellAction);
            viewerToolbar->addAction(pickPointRegionAction);
            viewerToolbar->addAction(pickCellRegionAction);

            viewerToolbar->addSeparator();

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

            if (colorAction) {
                viewerToolbar->addAction(colorAction);
            }

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

            viewerToolbar->addSeparator();

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

            viewerToolbar->addSeparator();

            viewerToolbar->addAction(scalarDataColorAction);
        }
    }

    return viewerToolbar;
}

// ---------------------- 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)));

        // check if the action is available
        Action* changeColorAction = Application::getAction("Change Color");

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

        // -- show scalar data
        // scalar data model, filter that will only select the scalars for the point and cell data
        scalarDataModel = new MeshDataFilterModel(MeshComponent::POINTS | MeshComponent::CELLS, MeshComponent::SCALARS, this);

        // combo box for viewing the scalar data model
        scalarDataComboBox = new QComboBox(viewerToolbar);
        scalarDataComboBox->setModel(scalarDataModel);
        scalarDataComboBox->setMinimumWidth(150);

        scalarDataColorAction = new QWidgetAction(this);
        scalarDataColorAction->setDefaultWidget(scalarDataComboBox);
        // TODO connect the combobox to the model (so that you can use the checkbox in the combo directly without going to the property tab)

        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)"));
    }

    //-- camera orientation mode
    auto* cameraOrientationGrp = new QActionGroup(this);
    connect(cameraOrientationGrp, SIGNAL(triggered(QAction*)), this, SLOT(cameraOrientationChanged(QAction*)));

    cameraOrientationRightDownAction = new QAction(tr("x-right y-down z-back"), cameraOrientationGrp);
    cameraOrientationRightDownAction->setCheckable(true);
    cameraOrientationRightDownAction->setChecked(rendererWidget->getCameraOrientation() == RendererWidget::RIGHT_DOWN);
    cameraOrientationRightDownAction->setStatusTip(tr("Set the camera such as x points to the right, y points downward, z points backward"));
    cameraOrientationRightDownAction->setWhatsThis(tr("Set the camera such as x points to the right, y points downward, z points backward"));

    cameraOrientationLeftUpAction = new QAction(tr("x-left y-up z-back"), cameraOrientationGrp);
    cameraOrientationLeftUpAction->setCheckable(true);
    cameraOrientationLeftUpAction->setChecked(rendererWidget->getCameraOrientation() == RendererWidget::LEFT_UP);
    cameraOrientationLeftUpAction->setStatusTip(tr("Set the camera such as x points to the left, y points upward, z points backward"));
    cameraOrientationLeftUpAction->setWhatsThis(tr("Set the camera such as x points to the left, y points upward, z points backward"));

    cameraOrientationRightUpAction = new QAction(tr("x-right y-up z-front"), cameraOrientationGrp);
    cameraOrientationRightUpAction->setCheckable(true);
    cameraOrientationRightUpAction->setChecked(rendererWidget->getCameraOrientation() == RendererWidget::RIGHT_UP);
    cameraOrientationRightUpAction->setStatusTip(tr("Set the camera such as x points to the right, y points upward, z points to the front"));
    cameraOrientationRightUpAction->setWhatsThis(tr("Set the camera such as x points to the right, y points upward, z points to the front"));

    //-- 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->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"));
    }
}

// ----------------------initWhatsThis ----------------------------
void InteractiveViewer::initWhatsThis() {
    QColor bgColor = frame->palette().color(QPalette::Button);
    QColor textColor = frame->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 = frame->palette().color(QPalette::Button);
    QColor textColor = frame->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 = frame->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;

            break;

        case Qt::Key_P:

            // addWhatsThisItem("Alt+P", "Toggle point rendering");

            if (e->modifiers() == Qt::AltModifier) {
                foreach (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::Points);       //XOR
                    }
                }

                refresh(this);
            }
            else

                // addWhatsThisItem("P", "Picking action");
                if (e->modifiers() == Qt::NoModifier) {
                    rendererWidget->pick();
                }

            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) {
                foreach (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) {
                foreach (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" << Qt::endl;
                                }
                                else if (c->getActor(InterfaceGeometry::Wireframe) == p) {
                                    found = true;
                                    debugStream << "\t- Wireframe Actor" << Qt::endl;
                                }
                                else if (c->getActor(InterfaceGeometry::Points) == p) {
                                    found = true;
                                    debugStream << "\t- Points Actor" << Qt::endl;
                                }

                                break;

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

                                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" << Qt::endl;
                            }
                            else {
                                debugStream << "\t- Additional Prop #" << i << 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 devided 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));
    }
}

// ---------------------- 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;
        }
    }
}

// ---------------------- slotXAngleChanged ----------------------------
void InteractiveViewer::xAngleChanged(double angle) {
    if (myType == SLICE_VIEWER) {
        if (actorMap.size() > 0) {
            actorMap.begin().key()->rotate(angle, 0, 0);

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

// ---------------------- slotYAngleChanged ----------------------------
void InteractiveViewer::yAngleChanged(double angle) {
    if (myType == SLICE_VIEWER) {
        if (actorMap.size() > 0) {
            actorMap.begin().key()->rotate(0, angle, 0);

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

// ---------------------- slotZAngleChanged ----------------------------
void InteractiveViewer::zAngleChanged(double angle) {
    if (myType == SLICE_VIEWER) {
        if (actorMap.size() > 0) {
            actorMap.begin().key()->rotate(0, 0, angle);

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

//--------------------- 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() && comp->getFrameAxisActor() != actor) {
            // 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< vtkPointPicker > picker = vtkSmartPointer<vtkPointPicker>::New();
            picker->SetTolerance(0.01);
            rendererWidget->setPicker(picker);
            break;
        }

        case POINT_PICKING: {
            vtkSmartPointer< vtkPointPicker > picker = vtkSmartPointer< vtkPointPicker >::New();
            picker->SetTolerance(0.01);
            this->rendererWidget->setPicker(picker);
            this->rendererWidget->setAreaPicking(false);
            break;
        }

        case CELL_PICKING: {
            vtkSmartPointer< vtkCellPicker > picker = vtkSmartPointer< vtkCellPicker >::New();
            picker->SetTolerance(0.0001);
            this->rendererWidget->setPicker(picker);
            this->rendererWidget->setAreaPicking(false);
            break;
        }

        case AREA_CELL_PICKING: {
            vtkSmartPointer< vtkRenderedAreaPicker > picker = vtkSmartPointer< vtkRenderedAreaPicker >::New();
            this->rendererWidget->setPicker(picker);
            this->rendererWidget->setAreaPicking(true);
            break;
        }

        case AREA_POINT_PICKING: {
            vtkSmartPointer< vtkRenderedAreaPicker > picker = vtkSmartPointer< vtkRenderedAreaPicker >::New();
            this->rendererWidget->setPicker(picker);
            this->rendererWidget->setAreaPicking(true);
            break;
        }

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

    // 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);
        scalarDataColorAction->blockSignals(true);

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

        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());

                    auto* mesh = dynamic_cast< MeshComponent* >(first);

                    if (mesh) {
                        // active scalar data colors and populate combobox with scalar data from the component
                        scalarDataColorAction->setEnabled(true);
                        scalarDataModel->setSourceModel(mesh->getDataModel());
                    }

                }
                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);
        scalarDataColorAction->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
    foreach (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();
}

//// --------------- getHighlightMode --------------------
//InteractiveViewer::HighlightMode InteractiveViewer::getHighlightMode() const {
//    return highlightMode;
//}

// --------------- 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 == highlightModeXtraHotAction) {
            highlightMode = InteractiveViewer::XTRA_HOT;
        }
        else
            */
        if (selectedAction == highlightOffAction) {
            propertyObject->setProperty(highlightModeProperty->getName().toStdString().c_str(), InteractiveViewer::OFF);
        }

    refresh(this);
}

// --------------- slotCameraOrientationChanged -----------------------------------
void InteractiveViewer::cameraOrientationChanged(QAction* selectedAction) {
    if (selectedAction == cameraOrientationRightDownAction) {
        rendererWidget->setCameraOrientation(RendererWidget::RIGHT_DOWN);
        resetCamera();
        rendererWidget->refresh();
    }
    else if (selectedAction == cameraOrientationLeftUpAction) {
        rendererWidget->setCameraOrientation(RendererWidget::LEFT_UP);
        resetCamera();
        rendererWidget->refresh();
    }
    else if (selectedAction == cameraOrientationRightUpAction) {
        rendererWidget->setCameraOrientation(RendererWidget::RIGHT_UP);
        resetCamera();
        rendererWidget->refresh();
    }

    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".InteractiveViewer." + objectName().simplified().replace(" ", ""));
    settings.setValue("cameraOrientation", rendererWidget->getCameraOrientation());
    refresh(this);
    settings.endGroup();
}

// -------------- 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
    foreach (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
    foreach (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);
    }
}

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

#if VTK_MAJOR_VERSION < 9
    vtkSmartPointer<vtkAbstractPropPicker> picker = vtkAbstractPropPicker::SafeDownCast(this->rendererWidget->GetInteractor()->GetPicker());
#else
    vtkSmartPointer<vtkAbstractPropPicker> picker = vtkAbstractPropPicker::SafeDownCast(this->rendererWidget->interactor()->GetPicker());
#endif

    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;
                }
            }

            if (pickedPlanePts != nullptr && pickedPlanePts->GetNumberOfPoints() > 0) {
                // picking worked
                isPicking = true;
                //-- tells the Component it's been picked in (x,y,z)
                comp->pixelPicked(pickedPlanePts->GetPoint(pointIndex) [0], pickedPlanePts->GetPoint(pointIndex) [1], pickedPlanePts->GetPoint(pointIndex) [2]);

                refresh(this);

                // 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);

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

                    // 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);
                    }

                    refresh(this);
                    emit selectionChanged();
                }
            }
        }
        break;

        case CELL_PICKING : {
#if VTK_MAJOR_VERSION < 9
            vtkSmartPointer<vtkCellPicker> cellPicker = vtkCellPicker::SafeDownCast(this->rendererWidget->GetInteractor()->GetPicker());
#else
            vtkSmartPointer<vtkCellPicker> cellPicker = vtkCellPicker::SafeDownCast(this->rendererWidget->interactor()->GetPicker());
#endif
            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);
                    refresh(this);
                    emit selectionChanged();
                }
            }
        }
        break;

        case AREA_CELL_PICKING : {
#if VTK_MAJOR_VERSION < 9
            vtkSmartPointer<vtkRenderedAreaPicker> areaPicker = vtkRenderedAreaPicker::SafeDownCast(this->rendererWidget->GetInteractor()->GetPicker());
#else
            vtkSmartPointer<vtkRenderedAreaPicker> areaPicker = vtkRenderedAreaPicker::SafeDownCast(this->rendererWidget->interactor()->GetPicker());
#endif
            auto* mesh = dynamic_cast<MeshComponent*>(comp);

            if (areaPicker && mesh) {
                vtkSmartPointer<vtkExtractSelectedFrustum> extractor = vtkSmartPointer<vtkExtractSelectedFrustum>::New();
                extractor->SetInputData(mesh->getPointSet());
                extractor->PreserveTopologyOff();
                extractor->SetFrustum(areaPicker->GetFrustum());
                extractor->Update();
                vtkDataSet* dataSet = vtkDataSet::SafeDownCast(extractor->GetOutput());

                if (dataSet) {
                    Application::showStatusBarMessage("Picked : " + comp->getName() + " (" + QString::number(dataSet->GetCellData()->GetArray(0)->GetNumberOfTuples()) + " cells )");
                    mesh->addToSelectedSelection(vtkSelectionNode::CELL, vtkSelectionNode::INDICES, dataSet->GetCellData()->GetArray(0));
                    refresh(this);
                    emit selectionChanged();
                }
            }
        }
        break;

        case AREA_POINT_PICKING : {
#if VTK_MAJOR_VERSION < 9
            vtkSmartPointer<vtkRenderedAreaPicker> areaPicker = vtkRenderedAreaPicker::SafeDownCast(this->rendererWidget->GetInteractor()->GetPicker());
#else
            vtkSmartPointer<vtkRenderedAreaPicker> areaPicker = vtkRenderedAreaPicker::SafeDownCast(this->rendererWidget->interactor()->GetPicker());
#endif
            auto* mesh = dynamic_cast<MeshComponent*>(comp);

            if (areaPicker && mesh) {
                vtkSmartPointer<vtkExtractSelectedFrustum> extractor = vtkSmartPointer<vtkExtractSelectedFrustum>::New();
                extractor->SetInputData(mesh->getPointSet());
                extractor->PreserveTopologyOff();
                extractor->SetFrustum(areaPicker->GetFrustum());
                extractor->SetFieldType(vtkSelection::POINT);
                extractor->Update();
                vtkDataSet* dataSet = vtkDataSet::SafeDownCast(extractor->GetOutput());

                if (dataSet) {
                    Application::showStatusBarMessage("Picked : " + comp->getName() + " (" + QString::number(dataSet->GetPointData()->GetArray(0)->GetNumberOfTuples()) + " points )");
                    mesh->addToSelectedSelection(vtkSelectionNode::POINT, vtkSelectionNode::INDICES, dataSet->GetPointData()->GetArray(0));
                    refresh(this);
                    emit selectionChanged();
                }
            }
        }
        break;

        case NO_PICKING :
        default :
            break;
    }

    isPicking = false;
    comp = nullptr;
}

//-------------------- 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 ELECTION_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);

    // 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) {
    // watch propertyObject instance for dynamic property changes
    if (event->type() == QEvent::DynamicPropertyChange) {
        // 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();

        // 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.endGroup();

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

}

}
