/* Copyright (c) 2008-2025 the MRtrix3 contributors.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Covered Software is provided under this License on an "as is"
 * basis, without warranty of any kind, either expressed, implied, or
 * statutory, including, without limitation, warranties that the
 * Covered Software is free of defects, merchantable, fit for a
 * particular purpose or non-infringing.
 * See the Mozilla Public License v. 2.0 for more details.
 *
 * For more details, see http://www.mrtrix.org/.
 */

#include "gui/mrview/tool/fixel/fixel.h"

#include "mrtrix.h"
#include "gui/mrview/window.h"
#include "gui/mrview/tool/fixel/base_fixel.h"
#include "gui/mrview/tool/fixel/legacy.h"
#include "gui/mrview/tool/fixel/directory.h"
#include "gui/mrview/tool/fixel/image4D.h"
#include "gui/dialog/file.h"
#include "gui/mrview/tool/list_model_base.h"
#include "math/rng.h"

namespace MR
{
  namespace GUI
  {
    namespace MRView
    {
      namespace Tool
      {


        class Fixel::Model : public ListModelBase
        { MEMALIGN (Fixel::Model)

          public:
            Model (QObject* parent) :
              ListModelBase (parent) { }

            void add_items (vector<std::string>& filenames, Fixel& fixel_tool) {

              size_t old_size = items.size();
              for (size_t i = 0, N = filenames.size(); i < N; ++i) {
                BaseFixel* fixel_image(nullptr);
                try
                {
                  try
                  {
                    if(Path::has_suffix (filenames[i], {".msf", ".msh"}))
                      fixel_image = new Legacy (filenames[i], fixel_tool);
                    else
                      fixel_image = new Directory (filenames[i], fixel_tool);
                  }
                  catch (InvalidFixelDirectoryException& error)
                  {
                    error.push_back("Couldn't open \"" + filenames[i] + "\" as a Directory fixel dataset");
                    try
                    {
                      fixel_image = new Image4D (filenames[i], fixel_tool);
                    }
                    catch (InvalidImageException& e)
                    {
                      error.push_back(e);
                      error.push_back("Couldn't open \"" + filenames[i] + "\" as a 4D vector image");
                      throw error;
                    }
                  }
                  catch(InvalidImageException& e)
                  {
                    e.push_back("Couldn't open \"" + filenames[i] + "\" as a Legacy fixel dataset");
                    throw e;
                  }
                }
                catch(Exception& e)
                {
                  e.push_back("Error loading \"" + filenames[i] + "\" as a fixel dataset");
                  e.display();
                  continue;
                }
                items.push_back (std::unique_ptr<Displayable> (fixel_image));
              }

              beginInsertRows (QModelIndex(), old_size, items.size());
              endInsertRows ();
            }

            BaseFixel* get_fixel_image (QModelIndex& index) {
              return dynamic_cast<BaseFixel*>(items[index.row()].get());
            }
        };



        Fixel::Fixel (Dock* parent) :
          Base (parent),
          not_3D (true),
          line_opacity (1.0) {

            VBoxLayout* main_box = new VBoxLayout (this);
            HBoxLayout* layout = new HBoxLayout;
            layout->setContentsMargins (0, 0, 0, 0);
            layout->setSpacing (0);

            QPushButton* button = new QPushButton (this);
            button->setToolTip (tr ("Open fixel image"));
            button->setIcon (QIcon (":/open.svg"));
            connect (button, SIGNAL (clicked()), this, SLOT (fixel_open_slot ()));
            layout->addWidget (button, 1);

            button = new QPushButton (this);
            button->setToolTip (tr ("Close fixel image"));
            button->setIcon (QIcon (":/close.svg"));
            connect (button, SIGNAL (clicked()), this, SLOT (fixel_close_slot ()));
            layout->addWidget (button, 1);

            hide_all_button = new QPushButton (this);
            hide_all_button->setToolTip (tr ("Hide all fixel images"));
            hide_all_button->setIcon (QIcon (":/hide.svg"));
            hide_all_button->setCheckable (true);
            connect (hide_all_button, SIGNAL (clicked()), this, SLOT (hide_all_slot ()));
            layout->addWidget (hide_all_button, 1);

            main_box->addLayout (layout, 0);

            fixel_list_view = new QListView (this);
            fixel_list_view->setSelectionMode (QAbstractItemView::ExtendedSelection);
            fixel_list_view->setDragEnabled (true);
            fixel_list_view->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
            fixel_list_view->setTextElideMode (Qt::ElideLeft);
            fixel_list_view->viewport()->setAcceptDrops (true);
            fixel_list_view->setDropIndicatorShown (true);

            fixel_list_model = new Model (this);
            fixel_list_view->setModel (fixel_list_model);

            connect (fixel_list_model, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
                     this, SLOT (toggle_shown_slot (const QModelIndex&, const QModelIndex&)));

            connect (fixel_list_view->selectionModel(),
                     SIGNAL (selectionChanged (const QItemSelection &, const QItemSelection &)),
                     SLOT (selection_changed_slot (const QItemSelection &, const QItemSelection &)));

            main_box->addWidget (fixel_list_view, 1);


            HBoxLayout* hlayout = new HBoxLayout;
            hlayout->setContentsMargins (0, 0, 0, 0);
            hlayout->setSpacing (0);
            main_box->addLayout (hlayout);

            // Colouring
            colour_combobox = new ComboBoxWithErrorMsg (0, "  (variable)  ");
            hlayout->addWidget (new QLabel ("colour by "));
            colour_combobox->addItem ("direction");
            colour_combobox->addItem ("value");
            hlayout->addWidget (colour_combobox, 0);
            connect (colour_combobox, SIGNAL (activated(int)), this, SLOT (colour_changed_slot(int)));

            colourmap_option_group = new QGroupBox ("Colour map and intensity windowing");
            main_box->addWidget (colourmap_option_group);
            hlayout = new HBoxLayout;
            colourmap_option_group->setLayout (hlayout);

            colourmap_button = new ColourMapButton (this, *this, false);

            hlayout->addWidget (colourmap_button);

            min_value = new AdjustButton (this);
            connect (min_value, SIGNAL (valueChanged()), this, SLOT (on_set_scaling_slot()));
            hlayout->addWidget (min_value);

            max_value = new AdjustButton (this);
            connect (max_value, SIGNAL (valueChanged()), this, SLOT (on_set_scaling_slot()));
            hlayout->addWidget (max_value);

            // Thresholding
            hlayout = new HBoxLayout;
            main_box->addLayout (hlayout);
            hlayout->addWidget (new QLabel ("threshold by "));

            threshold_combobox = new ComboBoxWithErrorMsg (0, "  (variable)  ");
            threshold_combobox->addItem ("Fixel size");
            threshold_combobox->addItem ("Associated value");
            hlayout->addWidget (threshold_combobox, 0);
            connect (threshold_combobox, SIGNAL (activated(int)), this, SLOT (threshold_type_slot(int)));

            QGroupBox* threshold_box = new QGroupBox ("Thresholds");
            main_box->addWidget (threshold_box);
            hlayout = new HBoxLayout;
            threshold_box->setLayout (hlayout);

            threshold_lower_box = new QCheckBox (this);
            connect (threshold_lower_box, SIGNAL (stateChanged(int)), this, SLOT (threshold_lower_changed(int)));
            hlayout->addWidget (threshold_lower_box);
            threshold_lower = new AdjustButton (this, 0.1);
            connect (threshold_lower, SIGNAL (valueChanged()), this, SLOT (threshold_lower_value_changed()));
            hlayout->addWidget (threshold_lower);

            threshold_upper_box = new QCheckBox (this);
            hlayout->addWidget (threshold_upper_box);
            threshold_upper = new AdjustButton (this, 0.1);
            connect (threshold_upper_box, SIGNAL (stateChanged(int)), this, SLOT (threshold_upper_changed(int)));
            connect (threshold_upper, SIGNAL (valueChanged()), this, SLOT (threshold_upper_value_changed()));
            hlayout->addWidget (threshold_upper);

            // Scaling
            hlayout = new HBoxLayout;
            main_box->addLayout (hlayout);

            hlayout->addWidget (new QLabel ("scale by "));
            length_combobox = new ComboBoxWithErrorMsg (0, "  (variable)  ");
            length_combobox->addItem ("unity");
            length_combobox->addItem ("fixel size");
            length_combobox->addItem ("associated value");
            hlayout->addWidget (length_combobox, 0);
            connect (length_combobox, SIGNAL (activated(int)), this, SLOT (length_type_slot(int)));

            hlayout = new HBoxLayout;
            main_box->addLayout (hlayout);
            hlayout->addWidget (new QLabel ("length multiplier"));
            length_multiplier = new AdjustButton (this, 0.01);
            length_multiplier->setMin (0.001);
            length_multiplier->setValue (10.0);
            connect (length_multiplier, SIGNAL (valueChanged()), this, SLOT (length_multiplier_slot()));
            hlayout->addWidget (length_multiplier);

            GridLayout* default_opt_grid = new GridLayout;
            line_thickness_slider = new QSlider (Qt::Horizontal);
            line_thickness_slider->setRange (10,1000);
            line_thickness_slider->setSliderPosition (200);
            connect (line_thickness_slider, SIGNAL (valueChanged (int)), this, SLOT (line_thickness_slot (int)));
            default_opt_grid->addWidget (new QLabel ("line thickness"), 0, 0);
            default_opt_grid->addWidget (line_thickness_slider, 0, 1);

            opacity_slider = new QSlider (Qt::Horizontal);
            opacity_slider->setRange (1, 1000);
            opacity_slider->setSliderPosition (int (1000));
            connect (opacity_slider, SIGNAL (valueChanged (int)), this, SLOT (opacity_slot (int)));
            default_opt_grid->addWidget (new QLabel ("opacity"), 1, 0);
            default_opt_grid->addWidget (opacity_slider, 1, 1);

            lock_to_grid = new QCheckBox (tr("lock to grid"));
            lock_to_grid->setCheckable (true);
            lock_to_grid->setChecked (true);
            connect (lock_to_grid, SIGNAL (clicked (bool)), this, SLOT (on_checkbox_slot (bool)));
            default_opt_grid->addWidget (lock_to_grid, 2, 0, 1, 2);

            crop_to_slice = new QCheckBox (tr("crop to slice"));
            crop_to_slice->setCheckable (true);
            crop_to_slice->setChecked (true);
            connect (crop_to_slice, SIGNAL (clicked (bool)), this, SLOT (on_checkbox_slot (bool)));
            default_opt_grid->addWidget (crop_to_slice, 3, 0, 1, 2);

            bidirectional = new QCheckBox (tr("bi-directional"));
            bidirectional->setCheckable (true);
            bidirectional->setChecked (true);
            connect (bidirectional, SIGNAL (clicked (bool)), this, SLOT (on_checkbox_slot (bool)));
            default_opt_grid->addWidget (bidirectional, 4, 0, 1, 2);

            track_main_volume = new QCheckBox (tr("cine mode"));
            track_main_volume->setCheckable (true);
            track_main_volume->setChecked (false);
            track_main_volume->setToolTip (
                tr("When activated, the vector field displayed will be\n"
                   "taken from the volume group (5th axis) at the same index\n"
                   "as the volume displayed in the main image.\n\n"
                   "This allows the vector field to follow the time course of the\n"
                   "the main image"));
            connect (track_main_volume, SIGNAL (clicked (bool)), this, SLOT (on_set_tracking_slot (bool)));
            default_opt_grid->addWidget (track_main_volume, 5, 0, 1, 2);

            main_box->addLayout (default_opt_grid, 0);

            main_box->addStretch ();
            setMinimumSize (main_box->minimumSize());
            update_gui_controls ();
        }



        void Fixel::draw (const Projection& transform, bool is_3D, int, int)
        {
          GL::assert_context_is_current();
          not_3D = !is_3D;
          for (int i = 0; i < fixel_list_model->rowCount(); ++i) {
            if (fixel_list_model->items[i]->show && !hide_all_button->isChecked())
              dynamic_cast<BaseFixel*>(fixel_list_model->items[i].get())->render (transform);
          }
          GL::assert_context_is_current();
        }


        void Fixel::draw_colourbars ()
        {
          if(hide_all_button->isChecked()) return;

          for (size_t i = 0, N = fixel_list_model->rowCount(); i < N; ++i) {
            if (fixel_list_model->items[i]->show)
              dynamic_cast<BaseFixel*>(fixel_list_model->items[i].get())->request_render_colourbar(*this);
          }
        }


        size_t Fixel::visible_number_colourbars () {
           size_t total_visible(0);

           if(!hide_all_button->isChecked()) {
             for (size_t i = 0, N = fixel_list_model->rowCount(); i < N; ++i) {
               BaseFixel* fixel = dynamic_cast<BaseFixel*>(fixel_list_model->items[i].get());
               if (fixel && fixel->show && !ColourMap::maps[fixel->colourmap].special)
                 total_visible += 1;
             }
           }

           return total_visible;
        }



        void Fixel::render_fixel_colourbar (const Tool::BaseFixel& fixel)
        {
          GL::assert_context_is_current();
          float min_value = fixel.use_discard_lower() ?
                      fixel.scaling_min_thresholded() :
                      fixel.scaling_min();

          float max_value = fixel.use_discard_upper() ?
                      fixel.scaling_max_thresholded() :
                      fixel.scaling_max();

          window().colourbar_renderer.render (fixel.colourmap, fixel.scale_inverted(),
                                              min_value, max_value,
                                              fixel.scaling_min(), fixel.display_range,
                                              Eigen::Array3f { fixel.colour[0] / 255.0f, fixel.colour[1] / 255.0f, fixel.colour[2] / 255.0f });
          GL::assert_context_is_current();
        }


        void Fixel::fixel_open_slot ()
        {
          vector<std::string> list = Dialog::File::get_files (this, "Select fixel images to open",
              GUI::Dialog::File::image_filter_string, &current_folder);
          add_images (list);
        }


        void Fixel::add_images (vector<std::string> &list)
        {
          if (list.empty())
            return;
          size_t previous_size = fixel_list_model->rowCount();
          fixel_list_model->add_items (list, *this);

          // Some of the images may be invalid, so it could be the case that no images were added
          size_t new_size = fixel_list_model->rowCount();
          if(previous_size < new_size) {
            QModelIndex first = fixel_list_model->index (previous_size, 0, QModelIndex());
            QModelIndex last = fixel_list_model->index (new_size -1, 0, QModelIndex());
            fixel_list_view->selectionModel()->select (QItemSelection (first, last), QItemSelectionModel::Select);
            update_gui_controls ();
          }

          window().updateGL ();
        }


        void Fixel::dropEvent (QDropEvent* event)
        {
          static constexpr int max_files = 32;

          const QMimeData* mimeData = event->mimeData();
          if (mimeData->hasUrls()) {
            vector<std::string> list;
            QList<QUrl> urlList = mimeData->urls();
            for (int i = 0; i < urlList.size() && i < max_files; ++i) {
                list.push_back (urlList.at (i).path().toUtf8().constData());
            }
            try {
              add_images (list);
            }
            catch (Exception& e) {
              e.display();
            }
            event->acceptProposedAction();
          }
        }


        void Fixel::fixel_close_slot ()
        {
          QModelIndexList indexes = fixel_list_view->selectionModel()->selectedIndexes();
          while (indexes.size()) {
            fixel_list_model->remove_item (indexes.first());
            indexes = fixel_list_view->selectionModel()->selectedIndexes();
          }
          window().updateGL();
        }


        void Fixel::toggle_shown_slot (const QModelIndex& index, const QModelIndex& index2)
        {
          if (index.row() == index2.row()) {
            fixel_list_view->setCurrentIndex(index);
          } else {
            for (size_t i = 0; i < fixel_list_model->items.size(); ++i) {
              if (fixel_list_model->items[i]->show) {
                fixel_list_view->setCurrentIndex (fixel_list_model->index (i, 0));
                break;
              }
            }
          }
          window().updateGL();
        }


        void Fixel::hide_all_slot ()
        {
          window().updateGL();
        }


        void Fixel::update_gui_controls ()
        {
          update_gui_scaling_controls ();
          update_gui_threshold_controls ();
          update_gui_colour_controls ();
          update_gui_tracking_controls ();
        }


        void Fixel::update_gui_colour_controls (bool reload_colour_types)
        {
          QModelIndexList indices = fixel_list_view->selectionModel ()->selectedIndexes ();
          size_t n_images (indices.size ());

          colour_combobox->setEnabled (n_images == 1);
          colourmap_button->setEnabled (n_images);

          max_value->setEnabled (n_images);
          min_value->setEnabled (n_images);

          if (!n_images) {
            max_value->setValue (NAN);
            min_value->setValue (NAN);
            length_multiplier->setValue (NAN);
            return;
          }


          if (!n_images)
            return;

          int colourmap_index = -2;
          for (size_t i = 0; i < n_images; ++i) {
            BaseFixel* fixel = dynamic_cast<BaseFixel*> (fixel_list_model->get_fixel_image (indices[i]));
            if (colourmap_index != int (fixel->colourmap)) {
              if (colourmap_index == -2)
                colourmap_index = fixel->colourmap;
              else
                colourmap_index = -1;
            }
          }

          // Not all colourmaps are added to this list; therefore need to find out
          // how many menu elements were actually created by ColourMap::create_menu()
          static size_t colourmap_count = 0;
          if (!colourmap_count) {
            for (size_t i = 0; ColourMap::maps[i].name; ++i) {
              if (!ColourMap::maps[i].special)
                ++colourmap_count;
            }
          }

          if (colourmap_index < 0) {
            for (size_t i = 0; i != colourmap_count; ++i )
              colourmap_button->colourmap_actions[i]->setChecked (false);
          } else {
            colourmap_button->colourmap_actions[colourmap_index]->setChecked (true);
          }

          BaseFixel* first_fixel = dynamic_cast<BaseFixel*> (fixel_list_model->get_fixel_image (indices[0]));

          if (n_images == 1 && reload_colour_types)
            first_fixel->load_colourby_combobox_options (*colour_combobox);

          const FixelColourType colour_type = first_fixel->get_colour_type ();

          colour_combobox->setCurrentIndex (first_fixel->get_colour_type_index ());
          colourmap_option_group->setEnabled (colour_type == CValue);

          max_value->setEnabled (colour_type == CValue);
          min_value->setEnabled (colour_type == CValue);

          if (colour_type == CValue) {
            min_value->setRate (first_fixel->scaling_rate ());
            max_value->setRate (first_fixel->scaling_rate ());
            min_value->setValue (first_fixel->scaling_min ());
            max_value->setValue (first_fixel->scaling_max ());
          }
        }


        void Fixel::update_gui_scaling_controls (bool reload_scaling_types)
        {
          QModelIndexList indices = fixel_list_view->selectionModel ()->selectedIndexes ();
          size_t n_images (indices.size ());

          length_multiplier->setEnabled (n_images);
          length_combobox->setEnabled (n_images == 1);

          if (!n_images) {
            length_multiplier->setValue (NAN);
            return;
          }

          BaseFixel* first_fixel = dynamic_cast<BaseFixel*> (fixel_list_model->get_fixel_image (indices[0]));

          if (n_images == 1 && reload_scaling_types)
            first_fixel->load_scaleby_combobox_options (*length_combobox);

          length_multiplier->setValue (first_fixel->get_line_length_multiplier ());
          line_thickness_slider->setValue (static_cast<int>(first_fixel->get_line_thickenss () * 1.0e5f));

          length_combobox->setCurrentIndex (first_fixel->get_scale_type_index ());
        }


        void Fixel::update_gui_threshold_controls (bool reload_threshold_types)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          size_t n_images (indices.size ());

          threshold_lower->setEnabled (n_images);
          threshold_upper->setEnabled (n_images);
          threshold_upper_box->setEnabled (n_images);
          threshold_lower_box->setEnabled (n_images);

          threshold_combobox->setEnabled (n_images == 1);

          if (!n_images) {
            threshold_lower->setValue (NAN);
            threshold_upper->setValue (NAN);
            return;
          }

          BaseFixel* first_fixel = dynamic_cast<BaseFixel*> (fixel_list_model->get_fixel_image (indices[0]));

          bool has_val = first_fixel->has_values ();

          if (n_images == 1 && reload_threshold_types && has_val)
            first_fixel->load_threshold_combobox_options (*threshold_combobox);

          threshold_lower->setEnabled (has_val);
          threshold_upper->setEnabled (has_val);
          threshold_lower_box->setEnabled (has_val);
          threshold_upper_box->setEnabled (has_val);
          threshold_combobox->setEnabled (has_val);

          if (!has_val) {
            threshold_lower_box->setChecked (false);
            threshold_upper_box->setChecked (false);
            return;
          }

          if (!std::isfinite (first_fixel->get_unscaled_threshold_lower ()))
            first_fixel->lessthan = first_fixel->intensity_min ();
          if (!std::isfinite (first_fixel->get_unscaled_threshold_upper ()))
            first_fixel->greaterthan = first_fixel->intensity_max ();

          threshold_lower->setValue (first_fixel->get_unscaled_threshold_lower ());
          threshold_lower->setRate (first_fixel->get_unscaled_threshold_rate ());
          threshold_lower->setEnabled (first_fixel->use_discard_lower ());
          threshold_lower_box->setChecked (first_fixel->use_discard_lower ());

          threshold_upper->setValue (first_fixel->get_unscaled_threshold_upper ());
          threshold_upper->setRate (first_fixel->get_unscaled_threshold_rate ());
          threshold_upper->setEnabled (first_fixel->use_discard_upper ());
          threshold_upper_box->setChecked (first_fixel->use_discard_upper ());

          threshold_combobox->setCurrentIndex (first_fixel->get_threshold_type_index ());
        }





        void Fixel::update_gui_tracking_controls ()
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          size_t n_images (indices.size ());

          if (!n_images) {
            track_main_volume->setEnabled (false);
            return;
          }

          size_t num_checked = 0;
          for (size_t i = 0; i < n_images; ++i) {
            Image4D* fixel = dynamic_cast<Image4D*> (fixel_list_model->get_fixel_image (indices[i]));
            if (!fixel) {
              track_main_volume->setEnabled (false);
              return;
            }
            if (!fixel->trackable()) {
              track_main_volume->setEnabled (false);
              return;
            }
            if (fixel->tracking)
              ++num_checked;
          }

          track_main_volume->setEnabled (true);
          track_main_volume->setCheckState ( num_checked ? ( num_checked == n_images ? Qt::Checked : Qt::PartiallyChecked ) : Qt::Unchecked );
        }





        void Fixel::opacity_slot (int opacity)
        {
          line_opacity = Math::pow2 (static_cast<float>(opacity)) / 1.0e6f;
          window().updateGL();
        }


        void Fixel::line_thickness_slot (int thickness)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i)
            fixel_list_model->get_fixel_image (indices[i])->set_line_thickness (static_cast<float>(thickness) / 1.0e5f);
          window().updateGL();
        }


        void Fixel::length_multiplier_slot ()
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i)
            fixel_list_model->get_fixel_image (indices[i])->set_line_length_multiplier (length_multiplier->value());
          window().updateGL();
        }


        void Fixel::length_type_slot (int selection)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();

          for (int i = 0; i < indices.size(); ++i) {
            const auto fixel = fixel_list_model->get_fixel_image (indices[i]);
            fixel->set_scale_type_index (selection);
            update_gui_scaling_controls (false);
            break;
          }

          window ().updateGL ();
        }


        void Fixel::threshold_type_slot (int selection)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();

          for (int i = 0; i < indices.size(); ++i) {
            const auto fixel = fixel_list_model->get_fixel_image (indices[i]);
            fixel->set_threshold_type_index (selection);
            update_gui_threshold_controls (false);
            break;
          }

          window ().updateGL ();
        }


        void Fixel::selection_changed_slot (const QItemSelection &, const QItemSelection &)
        {
          update_gui_controls ();
        }


        void Fixel::on_checkbox_slot(bool)
        {
          window().updateGL();
        }



        void Fixel::toggle_show_colour_bar (bool visible, const ColourMapButton&)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i)
            fixel_list_model->get_fixel_image (indices[i])->show_colour_bar = visible;
          window().updateGL();
        }


        void Fixel::selected_colourmap (size_t index, const ColourMapButton&)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i) {
            fixel_list_model->get_fixel_image (indices[i])->colourmap = index;
          }
          window().updateGL();
        }

        void Fixel::selected_custom_colour(const QColor& colour, const ColourMapButton&)
        {
          if (colour.isValid()) {
            QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
            std::array<GLubyte, 3> c_colour{{GLubyte(colour.red()), GLubyte(colour.green()), GLubyte(colour.blue())}};
            for (int i = 0; i < indices.size(); ++i) {
              fixel_list_model->get_fixel_image (indices[i])->set_colour (c_colour);
            }
            window().updateGL();
          }
        }

        void Fixel::reset_colourmap (const ColourMapButton&)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i)
            fixel_list_model->get_fixel_image (indices[i])->reset_windowing ();
          update_gui_controls ();
          window().updateGL();
        }


        void Fixel::toggle_invert_colourmap (bool inverted, const ColourMapButton&)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i)
            fixel_list_model->get_fixel_image (indices[i])->set_invert_scale (inverted);
          window().updateGL();
        }


        void Fixel::colour_changed_slot (int selection)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();

          colourmap_option_group->setEnabled (selection == 0);
          for (int i = 0; i < indices.size(); ++i) {
            fixel_list_model->get_fixel_image (indices[i])->set_colour_type_index (selection);
            update_gui_colour_controls (false);
            break;
          }

          window().updateGL();
        }



        void Fixel::on_set_tracking_slot (bool is_checked)
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i) {
            Image4D* fixel = dynamic_cast<Image4D*> (fixel_list_model->get_fixel_image (indices[i]));
            assert (fixel != nullptr);
            if (fixel)
              fixel->tracking = is_checked;
          }
          window().updateGL();
        }




        void Fixel::on_set_scaling_slot ()
        {
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i)
            fixel_list_model->get_fixel_image (indices[i])->set_windowing (min_value->value(), max_value->value());
          window().updateGL();
        }


        void Fixel::threshold_lower_changed (int)
        {
          if (threshold_lower_box->checkState() == Qt::PartiallyChecked) return;
          threshold_lower->setEnabled (threshold_lower_box->isChecked());
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i) {
            auto& fixel_image = *fixel_list_model->get_fixel_image (indices[i]);
            fixel_image.set_use_discard_lower (threshold_lower_box->isChecked() && fixel_image.has_values ());
          }
          window().updateGL();
        }


        void Fixel::threshold_upper_changed (int)
        {
          if (threshold_upper_box->checkState() == Qt::PartiallyChecked) return;
          threshold_upper->setEnabled (threshold_upper_box->isChecked());
          QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
          for (int i = 0; i < indices.size(); ++i) {
            auto& fixel_image = *fixel_list_model->get_fixel_image (indices[i]);
            fixel_image.set_use_discard_upper (threshold_upper_box->isChecked() && fixel_image.has_values ());
          }
          window().updateGL();
        }


        void Fixel::threshold_lower_value_changed ()
        {
          if (threshold_lower_box->checkState() == Qt::PartiallyChecked) return;
          if (threshold_lower_box->isChecked()) {
            QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
            for (int i = 0; i < indices.size(); ++i) {
              auto& fixel_image = *fixel_list_model->get_fixel_image (indices[i]);
              if (fixel_image.has_values ()) {
                fixel_image.set_threshold_lower (threshold_lower->value());
                fixel_image.set_use_discard_lower (threshold_lower_box->isChecked());
              }
            }
            window().updateGL();
          }
        }


        void Fixel::threshold_upper_value_changed ()
        {
          if (threshold_upper_box->checkState() == Qt::PartiallyChecked) return;
          if (threshold_upper_box->isChecked()) {
            QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes();
            for (int i = 0; i < indices.size(); ++i) {
              auto& fixel_image = *fixel_list_model->get_fixel_image (indices[i]);
              if (fixel_image.has_values ()) {
                fixel_image.set_threshold_upper (threshold_upper->value());
                fixel_image.set_use_discard_upper (threshold_upper_box->isChecked());
              }
            }
            window().updateGL();
          }
        }


        void Fixel::add_commandline_options (MR::App::OptionList& options)
        {
          using namespace MR::App;
          options
            + OptionGroup ("Fixel plot tool options")

            + Option ("fixel.load", "Load a fixel file (any file inside a fixel directory, or an old .msf / .msh legacy format file) into the fixel tool.").allow_multiple()
            +   Argument ("image").type_image_in();
        }

        bool Fixel::process_commandline_option (const MR::App::ParsedOption& opt)
        {
          if (opt.opt->is ("fixel.load")) {
            vector<std::string> list (1, std::string(opt[0]));
            try { fixel_list_model->add_items (list , *this); }
            catch (Exception& E) { E.display(); }
            return true;
          }

          return false;
        }





      }
    }
  }
}





