/// QasTools: Desktop toolset for the Linux sound system ALSA.
/// \copyright See COPYING file.

#include "slider.hpp"
#include "dpe/image_allocator.hpp"
#include "dpe/image_request.hpp"
#include "dpe/image_set.hpp"
#include "wdg/color_methods.hpp"
#include "wdg/ds/widget_types.hpp"
#include "wdg/style_db.hpp"
#include <QEvent>
#include <QMouseEvent>
#include <QPainter>
#include <cassert>
#include <climits>
#include <cmath>
#include <iostream>

namespace Wdg::DS
{

Slider::Slider ( const Wdg::Style_Db * style_db_n,
                 dpe::Image_Allocator * image_alloc_n,
                 QWidget * parent_n )
: QWidget ( parent_n )
, _dsi ( 4, image_alloc_n )
, _current_index ( 0 )
, _maximum_index ( 0 )
, _wheel_degrees ( 720 )
, _step_single ( 0 )
, _step_page ( 0 )
, _handle_pos ( 0 )
, _handle_pos_index ( 0 )
, _handle_pos_hint ( 0 )
, _handle_pos_max ( 0 )
, _slider_down ( false )
, _update_pixmaps_pending ( false )
, _cursor_select_allowed ( false )
, _cursor_over_handle ( false )
, _cursor_value_hinting ( false )
, _anim_run_snap ( false )
, _meta_bg ( num_images_bg, Wdg::DS::WT_SLIDER, 0 )
, _meta_marker ( num_images_marker, Wdg::DS::WT_SLIDER, 1 )
, _meta_frame ( num_images_frame, Wdg::DS::WT_SLIDER, 2 )
, _meta_handle ( num_images_handle, Wdg::DS::WT_SLIDER, 3 )
, _style_db ( style_db_n )
{
  _dsi.image_request ()->meta[ 0 ] = &_meta_bg;
  _dsi.image_request ()->meta[ 1 ] = &_meta_marker;
  _dsi.image_request ()->meta[ 2 ] = &_meta_frame;
  _dsi.image_request ()->meta[ 3 ] = &_meta_handle;

  {
    QSizePolicy policy ( sizePolicy () );
    policy.setHorizontalPolicy ( QSizePolicy::Expanding );
    policy.setVerticalPolicy ( QSizePolicy::Expanding );
    setSizePolicy ( policy );
  }
  setFocusPolicy ( Qt::StrongFocus );
  setMouseTracking ( true );

  // Animation timer
  {
    const unsigned int fps = 60;
    _anim_timer.setInterval ( 1000 / fps );
    connect ( &_anim_timer, &QTimer::timeout, this, &Slider::anim_tick );
  }

  connect ( _style_db,
            &Wdg::Style_Db::sig_palette_changed,
            this,
            &Slider::style_palette_changed );

  update_index_mappers ();
  update_palette_and_colors ();
}

Slider::~Slider ()
{
  _dsi.set_image_alloc ( 0 );
}

QSize
Slider::sizeHint () const
{
  const int def_len ( fontMetrics ().height () );
  return QSize ( def_len, def_len * 10 );
}

QSize
Slider::minimumSizeHint () const
{
  const int def_len ( fontMetrics ().height () );
  return QSize ( def_len, def_len * 4 );
}

void
Slider::set_style_id ( unsigned int id_n )
{
  if ( style_id () != id_n ) {
    _dsi.set_images_meta_style_id ( id_n );
    update_palette_and_colors ();
  }
}

void
Slider::set_wheel_degrees ( unsigned int degrees_n )
{
  _wheel_degrees = ( degrees_n > 0 ) ? degrees_n : 1;
}

void
Slider::set_current_index ( unsigned long idx_n )
{
  if ( idx_n > maximum_index () ) {
    idx_n = maximum_index ();
  }
  if ( current_index () != idx_n ) {
    _current_index = idx_n;
    this->current_index_changed ();
    Q_EMIT sig_current_index_changed ( idx_n );
  }
}

void
Slider::adjust_current_index ( long idx_delta_n )
{
  unsigned long idx ( current_index () );
  if ( idx_delta_n >= 0 ) {
    unsigned long dplus ( idx_delta_n );
    if ( dplus >= maximum_index () ) {
      idx = maximum_index ();
    } else {
      if ( ( maximum_index () - dplus ) <= idx ) {
        idx = maximum_index ();
      } else {
        idx += dplus;
      }
    }
  } else {
    unsigned long dplus ( -idx_delta_n );
    if ( dplus > idx ) {
      idx = 0;
    } else {
      idx -= dplus;
    }
  }
  set_current_index ( idx );
}

void
Slider::set_maximum_index ( unsigned long idx_n )
{
  if ( maximum_index () != idx_n ) {
    _maximum_index = idx_n;
    this->maximum_index_changed ();
    Q_EMIT sig_maximum_index_changed ( idx_n );
  }
}

void
Slider::set_slider_down ( bool down_n )
{
  _slider_down = down_n;
}

void
Slider::current_index_changed ()
{
  _handle_pos_index = map_index_to_handle_pos ( current_index () );
  if ( !slider_down () ) {
    anim_snap_start ();
  }
  update ();
}

void
Slider::maximum_index_changed ()
{
  update_index_mappers ();

  if ( current_index () > maximum_index () ) {
    set_current_index ( maximum_index () );
  }

  unsigned long delta ( maximum_index () );
  unsigned long div;
  unsigned long val;
  unsigned long mod;

  // Single step
  div = 100;
  val = delta / div;
  mod = delta % div;
  if ( mod > ( div / 2 ) ) {
    ++val;
  }
  if ( val == 0 ) {
    ++val;
  }
  _step_single = val;

  // Page step
  div = 12;
  val = delta / div;
  mod = delta % div;
  if ( mod > ( div / 2 ) ) {
    ++val;
  }
  if ( val == 0 ) {
    ++val;
  }
  _step_page = val;

  // Update pixmap config
  _meta_bg.ticks_max_idx = delta;

  update_pixmaps ();
}

unsigned long
Slider::map_handle_pos_to_index ( int handle_pos_n )
{
  unsigned long res ( 0 );

  if ( handle_pos_n <= 0 ) {
    res = maximum_index ();
  } else {
    unsigned int hpos ( handle_pos_n );
    if ( hpos <= _handle_pos_max ) {
      hpos = _handle_pos_max - hpos;
      res = _map_hp_idx->map ( hpos );
    }
  }

  return res;
}

unsigned int
Slider::map_index_to_handle_pos ( unsigned long idx_n )
{
  unsigned int res ( _handle_pos_max );

  if ( idx_n <= maximum_index () ) {
    res = _map_idx_hp->map ( idx_n );
    if ( res > _handle_pos_max ) {
      res = 0;
    } else {
      res = _handle_pos_max - res;
    }
  }

  return res;
}

unsigned int
Slider::map_tick_pos_to_handle_pos ( int tick_pos_n )
{
  unsigned int res ( 0 );
  const int delta ( _handle_rect.height () / 2 );
  if ( tick_pos_n > delta ) {
    res = ( tick_pos_n - delta );
    if ( res > _handle_pos_max ) {
      res = _handle_pos_max;
    }
  }
  return res;
}

unsigned int
Slider::map_handle_pos_to_tick_pos ( int handle_pos_n )
{
  unsigned int res ( 0 );
  if ( handle_pos_n > 0 ) {
    res = handle_pos_n;
    if ( res > _handle_pos_max ) {
      res = _handle_pos_max;
    }
  }
  res += ( _handle_rect.height () / 2 );
  return res;
}

unsigned int
Slider::map_index_to_tick_pos ( unsigned long idx_n )
{
  unsigned int res = map_index_to_handle_pos ( idx_n );
  res = map_handle_pos_to_tick_pos ( res );
  return res;
}

unsigned long
Slider::map_tick_pos_to_index ( int tick_pos_n )
{
  unsigned long res = map_tick_pos_to_handle_pos ( tick_pos_n );
  res = map_handle_pos_to_index ( res );
  return res;
}

void
Slider::update_index_mappers ()
{
  if ( maximum_index () > _handle_pos_max ) {
    _map_idx_hp.reset (
        new UInt_Mapper_Down ( maximum_index (), _handle_pos_max ) );
    _map_hp_idx.reset (
        new UInt_Mapper_Up ( _handle_pos_max, maximum_index () ) );
  } else {
    _map_idx_hp.reset (
        new UInt_Mapper_Up ( maximum_index (), _handle_pos_max ) );
    _map_hp_idx.reset (
        new UInt_Mapper_Down ( _handle_pos_max, maximum_index () ) );
  }
}

bool
Slider::update_cursor_mode ()
{
  // std::cout << "Slider::update_cursor_mode " << "\n";

  bool res ( false ); // Indicates a cursor mode change

  const QPoint cur_pos ( mapFromGlobal ( QCursor::pos () ) );

  { // Check if the cursor is over the handle
    const bool over_handle ( _handle_rect.contains ( cur_pos ) );
    if ( over_handle != _cursor_over_handle ) {
      _cursor_over_handle = over_handle;
      res = true;
    }
  }

  { // Update cursor value hinting
    const bool show_hint ( _cursor_select_allowed &&
                           rect ().contains ( cur_pos ) &&
                           !_cursor_over_handle && !slider_down () );

    if ( show_hint ) {
      unsigned long hpos;
      hpos = map_tick_pos_to_index ( cur_pos.y () );
      hpos = map_index_to_handle_pos ( hpos );

      if ( _handle_pos_hint != hpos ) {
        _handle_pos_hint = hpos;
        res = true;
      }
    }

    if ( _cursor_value_hinting != show_hint ) {
      _cursor_value_hinting = show_hint;
      res = true;
    }
  }

  { // Update cursor shape
    Qt::CursorShape cshape ( Qt::ArrowCursor );
    if ( slider_down () ) {
      cshape = Qt::ClosedHandCursor;
    } else if ( _cursor_over_handle ) {
      cshape = Qt::OpenHandCursor;
    } else if ( _cursor_value_hinting ) {
      cshape = Qt::CrossCursor;
    }

    QCursor curs ( cursor () );
    if ( curs.shape () != cshape ) {
      curs.setShape ( cshape );
      setCursor ( curs );
      res = true;
    }
  }

  return res;
}

bool
Slider::set_handle_pos ( unsigned int pos_n )
{
  // std::cout << "Slider::set_handle_pos " << pos_n << "\n";

  if ( pos_n > _handle_pos_max ) {
    pos_n = _handle_pos_max;
  }

  if ( _handle_pos != pos_n ) {
    _handle_pos = pos_n;
    _handle_rect.moveTop ( _handle_pos );
    return true;
  }

  return false;
}

void
Slider::update_value_from_handle_pos ()
{
  unsigned long idx = map_handle_pos_to_index ( _handle_pos );
  set_current_index ( idx );
}

void
Slider::finish_handle_manipulation ()
{
  set_slider_down ( false );

  _handle_pos_index = map_index_to_handle_pos ( current_index () );
  anim_snap_start ();

  update_cursor_mode ();
}

void
Slider::changeEvent ( QEvent * event_n )
{
  QWidget::changeEvent ( event_n );

  switch ( event_n->type () ) {
  case QEvent::ActivationChange:
  case QEvent::EnabledChange:
  case QEvent::StyleChange:
  case QEvent::PaletteChange:
  case QEvent::LayoutDirectionChange:
    break;
  default:
    break;
  }

  update_palette_and_colors ();
  update_pixmaps ();
  update ();
}

// Focus events
void
Slider::focusInEvent ( QFocusEvent * event_n )
{
  QWidget::focusInEvent ( event_n );

  switch ( event_n->reason () ) {
  case Qt::MouseFocusReason:
  case Qt::ActiveWindowFocusReason:
    _cursor_select_allowed = false;
    break;

  default:
    _cursor_select_allowed = true;
    break;
  }

  update_cursor_mode ();
  update ();
}

void
Slider::focusOutEvent ( QFocusEvent * event_n )
{
  QWidget::focusOutEvent ( event_n );

  _cursor_select_allowed = false;
  _cursor_value_hinting = false;
  finish_handle_manipulation ();
  update ();
}

void
Slider::enterEvent ( QEnterEvent * event_n )
{
  QWidget::enterEvent ( event_n );

  update_cursor_mode ();
  update ();
}

void
Slider::leaveEvent ( QEvent * event_n )
{
  QWidget::leaveEvent ( event_n );

  update_cursor_mode ();
  update ();
}

// Mouse events
void
Slider::mousePressEvent ( QMouseEvent * event_n )
{
  QWidget::mousePressEvent ( event_n );

  if ( event_n->button () == Qt::LeftButton ) {

    if ( _cursor_over_handle ) {
      // Start slider drag
      set_slider_down ( true );
    } else {
      // Select a value on the slider range
      if ( _cursor_select_allowed ) {
        set_slider_down ( true );
        const unsigned int hpos (
            map_tick_pos_to_handle_pos ( event_n->pos ().y () ) );
        if ( set_handle_pos ( hpos ) ) {
          update_value_from_handle_pos ();
        }
      }
    }

    if ( slider_down () ) {
      anim_stop ();
    }

    // After a click selection should be allowed
    if ( hasFocus () ) {
      _cursor_select_allowed = true;
    }
    update_cursor_mode ();
    update ();
  }
}

void
Slider::mouseReleaseEvent ( QMouseEvent * event_n )
{
  QWidget::mouseReleaseEvent ( event_n );

  if ( event_n->button () == Qt::LeftButton ) {
    if ( slider_down () ) {
      finish_handle_manipulation ();
      update ();
    }
  }
}

void
Slider::mouseMoveEvent ( QMouseEvent * event_n )
{
  QWidget::mouseMoveEvent ( event_n );

  bool do_update ( false );

  if ( slider_down () ) {
    int delta ( 0 );

    {
      int p_now ( event_n->pos ().y () );
      int p_prev ( _mouse_last.y () );

      if ( p_now != p_prev ) {
        int p_min;
        int p_max;
        if ( p_now > p_prev ) {
          // For moving downwards the cursor positions must be in this range
          p_min = _tick_min;
          p_max = height () - 1;
        } else {
          // For moving upwards the cursor positions must be in this range
          p_min = 0;
          p_max = _tick_max;
        }
        p_now = qBound ( p_min, p_now, p_max );
        p_prev = qBound ( p_min, p_prev, p_max );

        delta = ( p_now - p_prev );
      }
    }

    // std::cout << "Slider::mouseMoveEvent: Delta " << delta << "\n";
    unsigned int hpos ( _handle_pos );
    if ( delta < 0 ) {
      delta = -delta;
      if ( (unsigned int)delta < hpos ) {
        hpos -= delta;
      } else {
        hpos = 0;
      }
    } else {
      hpos += delta;
    }
    if ( set_handle_pos ( hpos ) ) {
      update_value_from_handle_pos ();
      do_update = true;
    }
  }

  _mouse_last = event_n->pos ();

  // Test if the cursor is over the handle
  if ( update_cursor_mode () ) {
    do_update = true;
  }

  if ( do_update ) {
    update ();
  }
}

void
Slider::wheelEvent ( QWheelEvent * event_n )
{
  QWidget::wheelEvent ( event_n );

  if ( rect ().contains ( event_n->position ().toPoint () ) ) {
    event_n->accept ();
    setFocus ();

    long delta;
    {
      double amount ( maximum_index () / double ( _wheel_degrees ) );
      amount *= ( event_n->angleDelta ().y () / 8.0 );
      if ( amount > 0 ) {
        delta = std::ceil ( amount );
      } else {
        delta = std::floor ( amount );
      }
    }
    if ( delta == 0 ) {
      if ( event_n->angleDelta ().y () > 0 ) {
        delta = 1;
      } else {
        delta = -1;
      }
    }

    adjust_current_index ( delta );

  } else {
    event_n->ignore ();
  }
}

void
Slider::keyPressEvent ( QKeyEvent * event_n )
{
  QWidget::keyPressEvent ( event_n );

  long idx_adjust ( 0 );
  switch ( event_n->key () ) {
  case Qt::Key_Left:
    idx_adjust = -_step_single;
    break;
  case Qt::Key_Up:
    idx_adjust = _step_single;
    break;
  case Qt::Key_Right:
    idx_adjust = _step_single;
    break;
  case Qt::Key_Down:
    idx_adjust = -_step_single;
    break;
  case Qt::Key_VolumeDown:
    idx_adjust = -_step_single;
    break;
  case Qt::Key_VolumeUp:
    idx_adjust = _step_single;
    break;
  case Qt::Key_PageUp:
    idx_adjust = _step_page;
    break;
  case Qt::Key_PageDown:
    idx_adjust = -_step_page;
    break;
  default:
    break;
  }
  if ( idx_adjust != 0 ) {
    adjust_current_index ( idx_adjust );
  }
}

void
Slider::keyReleaseEvent ( QKeyEvent * event_n )
{
  QWidget::keyReleaseEvent ( event_n );
}

// Window events

void
Slider::resizeEvent ( QResizeEvent * event_n )
{
  // std::cout << "Slider::resizeEvent " << this << "\n";
  QWidget::resizeEvent ( event_n );

  anim_stop ();

  if ( ( width () <= 0 ) || ( height () <= 0 ) ) {
    return;
  }

  // Handle rect
  unsigned int handle_height ( width () );
  handle_height += ( handle_height + 1 ) % 2; // Make odd

  _handle_rect.setRect ( 0, _handle_pos, width (), handle_height );

  // Handle range
  _handle_pos_max = 0;
  if ( (unsigned int)height () > handle_height ) {
    _handle_pos_max = ( height () - handle_height );
  }
  update_index_mappers ();

  _tick_min = handle_height / 2;
  _tick_max = _tick_min + ( _handle_pos_max + 1 );

  // Update pixmap config
  _meta_bg.ticks_range_max_idx = _handle_pos_max;
  _meta_bg.ticks_range_start = handle_height / 2;

  // Marker size
  {
    int mww ( _handle_rect.height () / 5 );
    int mhh ( _handle_rect.width () / 7 );
    mww = qMin ( mww, mhh );

    if ( mww < 6 ) {
      mww = 6;
    }
    if ( ( ( _handle_rect.width () - mww ) % 2 ) != 0 ) {
      --mww;
    }

    mhh = mww;
    if ( ( mhh % 2 ) == 0 ) {
      --mhh; // height should be odd
    }
    _marker_size.setWidth ( mww );
    _marker_size.setHeight ( mhh );
  }

  // Update value marker painting position offset
  {
    int off_x ( _handle_rect.width () );
    int off_y ( _handle_rect.height () / 2 );
    off_x -= _marker_size.width ();
    off_y -= _marker_size.height () / 2;
    off_x /= 2;
    _marker_offset[ 0 ] = off_x;
    _marker_offset[ 1 ] = off_y;
  }

  // Update handle position
  _handle_pos_index = map_index_to_handle_pos ( current_index () );
  set_handle_pos ( _handle_pos_index );
  update_cursor_mode ();

  update_pixmaps ();
}

void
Slider::paintEvent ( QPaintEvent * )
{
  if ( _update_pixmaps_pending ) {
    update_pixmaps ();
  }
  _dsi.wait_for_request ();
  if ( !_dsi.images ().ready () ) {
    return;
  }

  QPixmap * pixmap_handle = nullptr;
  QPixmap * pixmap_frame = nullptr;
  QPixmap * pixmap_marker_2 = nullptr;
  QPixmap * pixmap_marker_1 = nullptr;
  bool draw_snap_line = false;
  QPixmap * pixmap_bg_2 = nullptr;
  QPixmap * pixmap_bg_1 = nullptr;

  // Items setup
  {
    // Local state buffer
    const bool loc_under_mouse = underMouse ();
    const bool loc_is_enabled = isEnabled ();
    const bool loc_has_focus = hasFocus ();
    const bool loc_slider_down = slider_down ();

    // Slider background setup
    {
      // Top half of the background
      dpe::Image & img = _dsi.images ().img_sets[ 0 ]->image ( 0 );
      pixmap_bg_1 = img.convert_to_pixmap ();
    }
    {
      // Bottom half of the background
      dpe::Image & img = _dsi.images ().img_sets[ 0 ]->image ( 1 );
      pixmap_bg_2 = img.convert_to_pixmap ();
    }

    // Value marker and snap line setup
    if ( loc_slider_down || _anim_run_snap ) {
      draw_snap_line = true;

      dpe::Image & img = _dsi.images ().img_sets[ 1 ]->image ( 0 );
      pixmap_marker_1 = img.convert_to_pixmap ();
    }

    // Value hint marker setup
    if ( _cursor_value_hinting ) {
      dpe::Image & img = _dsi.images ().img_sets[ 1 ]->image ( 1 );
      pixmap_marker_2 = img.convert_to_pixmap ();
    }

    // Frame setup
    {
      int idx = -1;
      if ( loc_has_focus ) {
        idx = 0;
      } else if ( loc_under_mouse && loc_is_enabled ) {
        idx = 1;
      }
      if ( idx >= 0 ) {
        dpe::Image & img ( _dsi.images ().img_sets[ 2 ]->image ( idx ) );
        pixmap_frame = img.convert_to_pixmap ();
      }
    }

    // Handle setup
    {
      unsigned int idx;
      if ( loc_slider_down ) {
        idx = 4;
      } else {
        idx = loc_has_focus ? 1 : 0;
        if ( loc_under_mouse && loc_is_enabled ) {
          idx += 2;
        }
      }
      dpe::Image & img ( _dsi.images ().img_sets[ 3 ]->image ( idx ) );
      pixmap_handle = img.convert_to_pixmap ();
    }
  }

  // Painting
  QPainter painter ( this );
  painter.setRenderHint ( QPainter::Antialiasing );

  // Draw background
  if ( ( pixmap_bg_1 != nullptr ) && ( pixmap_bg_2 != nullptr ) ) {
    const int mid_pos = _handle_pos_index + _handle_rect.height () / 2;
    {
      const QRect re_img ( 0, 0, width (), mid_pos );
      painter.drawPixmap ( re_img, *pixmap_bg_1, re_img );
    }
    {
      const QRect re_img ( 0, mid_pos, width (), height () - mid_pos );
      painter.drawPixmap ( re_img, *pixmap_bg_2, re_img );
    }
  }

  // Draw snap line
  if ( draw_snap_line ) {
    unsigned int pos_diff;
    if ( _handle_pos > _handle_pos_index ) {
      pos_diff = _handle_pos - _handle_pos_index;
    } else {
      pos_diff = _handle_pos_index - _handle_pos;
    }
    if ( pos_diff > 5 ) {
      const double x_mid = width () / 2.0;
      double dx1 = _marker_size.width () / 5.0;
      dx1 = qMax ( dx1, 1.5 );
      double yy1 = map_handle_pos_to_tick_pos ( _handle_pos );
      double yy2 = map_handle_pos_to_tick_pos ( _handle_pos_index );
      yy1 += 0.5;
      yy2 += 0.5;
      const double yym = ( yy1 + yy2 ) / 2.0;

      const int num_pts = 6;
      QPointF pts[ num_pts ] = { QPointF ( x_mid + dx1, yy1 ),
                                 QPointF ( x_mid, yym ),
                                 QPointF ( x_mid + dx1, yy2 ),
                                 QPointF ( x_mid - dx1, yy2 ),
                                 QPointF ( x_mid, yym ),
                                 QPointF ( x_mid - dx1, yy1 ) };
      painter.setPen ( _snap_pen );
      painter.setBrush ( _snap_brush );
      painter.drawPolygon ( pts, num_pts );
    }
  }

  // Draw actual level marker
  if ( pixmap_marker_1 != nullptr ) {
    const QPointF pp ( _marker_offset[ 0 ],
                       _marker_offset[ 1 ] + _handle_pos_index );
    painter.drawPixmap ( pp, *pixmap_marker_1 );
  }

  // Draw hint marker
  if ( pixmap_marker_2 != nullptr ) {
    const QPointF pp ( _marker_offset[ 0 ],
                       _marker_offset[ 1 ] + _handle_pos_hint );
    painter.drawPixmap ( pp, *pixmap_marker_2 );
  }

  // Draw frame
  if ( pixmap_frame != nullptr ) {
    const QPointF pp ( 0, 0 );
    painter.drawPixmap ( pp, *pixmap_frame );
  }

  // Draw handle
  if ( pixmap_handle != nullptr ) {
    painter.drawPixmap ( _handle_rect.topLeft (), *pixmap_handle );
  }
}

void
Slider::update_pixmaps ()
{
  if ( _dsi.image_alloc () != nullptr ) {
    if ( isVisible () ) {
      // std::cout << "Slider::update_pixmaps " << this << "\n";
      _update_pixmaps_pending = false;
      _dsi.wait_for_request ();

      _meta_bg.size = size ();
      _meta_marker.size = _marker_size;
      _meta_frame.size = size ();
      _meta_handle.size = _handle_rect.size ();
      _dsi.set_images_meta_style_sub_id ( _dsi.style_sub_id ( this ) );
      _dsi.set_images_meta_palette ( palette () );

      _dsi.send_request ();

      update ();
    } else {
      _update_pixmaps_pending = true;
    }
  }
}

void
Slider::update_palette_and_colors ()
{
  QColor col;
  {
    if ( style_db () != nullptr ) {
      QPalette pal = style_db ()->palette ( style_id () );
      if ( palette () != pal ) {
        setPalette ( pal );
      }
    }
    QColor col_bg ( palette ().color ( QPalette::Window ) );
    QColor col_fg ( palette ().color ( QPalette::WindowText ) );
    col = Wdg::col_mix ( col_bg, col_fg, 1, 2 );
  }
  col.setAlpha ( 70 );

  _snap_pen.setStyle ( Qt::NoPen );
  _snap_pen.setColor ( col );

  _snap_brush.setStyle ( Qt::SolidPattern );
  _snap_brush.setColor ( col );
}

void
Slider::style_palette_changed ( unsigned int style_id_n )
{
  if ( style_id_n == style_id () ) {
    update_palette_and_colors ();
    update_pixmaps ();
    update ();
  }
}

void
Slider::anim_stop ()
{
  _anim_run_snap = false;
  _anim_timer.stop ();
}

void
Slider::anim_tick ()
{
  bool do_update ( false );

  if ( _anim_run_snap ) {
    // Passed seconds since the animation start
    unsigned int msec = std::abs ( _anim_snap_time.elapsed () );
    if ( msec >= _anim_snap_msec_max ) {
      msec = _anim_snap_msec_max;
      _anim_run_snap = false;
    }
    if ( anim_snap_tick ( msec ) ) {
      do_update = true;
    }
    if ( _handle_pos == _handle_pos_index ) {
      _anim_run_snap = false;
      do_update = true;
    }
  }

  if ( !_anim_run_snap ) {
    anim_stop ();
  }
  if ( do_update ) {
    update_cursor_mode ();
    update ();
  }
}

void
Slider::anim_snap_start ()
{
  if ( ( _handle_pos != _handle_pos_index ) ) {

    // Current speed
    double v_x0 = 0.0;

    if ( _anim_run_snap ) {
      // A snapping annimation is already running.
      unsigned int msec = std::abs ( _anim_snap_time.elapsed () );
      if ( msec < _anim_snap_msec_max ) {
        // Acquire the current speed
        v_x0 = double ( msec ) / 1000.0;
        v_x0 = _cubic_curve.eval_speed ( v_x0 );
      } else {
        msec = _anim_snap_msec_max;
      }
      // Do a anim tick
      anim_snap_tick ( msec );
    } else {
      _anim_run_snap = true;
    }

    _anim_snap_time.start ();
    if ( !_anim_timer.isActive () ) {
      _anim_timer.start ();
    }

    if ( _handle_pos_max > 0 ) {
      unsigned int pos_diff;
      if ( _handle_pos > _handle_pos_index ) {
        pos_diff = _handle_pos - _handle_pos_index;
      } else {
        pos_diff = _handle_pos_index - _handle_pos;
      }

      _anim_snap_msec_max = ( 800 * pos_diff ) / _handle_pos_max;
    } else {
      _anim_snap_msec_max = 500;
    }
    _anim_snap_msec_max = qBound ( 100, (int)_anim_snap_msec_max, 600 );

    _cubic_curve.set_params (
        _handle_pos, v_x0, _handle_pos_index, _anim_snap_msec_max / 1000.0 );
  }
}

bool
Slider::anim_snap_tick ( unsigned int msec_n )
{
  unsigned int hpos = _handle_pos_index;
  if ( msec_n <= _anim_snap_msec_max ) {
    // Calculate the position from the easing curve
    double dpos = double ( msec_n ) / 1000.0;
    dpos = _cubic_curve.eval ( dpos );
    dpos = std::floor ( dpos + 0.5 ); // Round
    if ( dpos > 0.0 ) {
      hpos = static_cast< unsigned int > ( dpos );
    } else {
      hpos = 0;
    }
  }

  return set_handle_pos ( hpos );
}

} // namespace Wdg::DS
