/*
 *
 *    soniK digital audio editor
 *    Copyright (C) 2003-2006  Robert Walker <rob@tenfoot.org.uk>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program 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 General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include "slider.h"

#include <klocale.h>
#include <knuminput.h>
#include <kglobalsettings.h>
#include <kdebug.h>

#include <qvariant.h>
#include <qlabel.h>
#include <qpushbutton.h>
#include <qslider.h>
#include <qlayout.h>
#include <qtooltip.h>
#include <qwhatsthis.h>

#include <cmath>

using Sonik::Slider;


namespace
{
  const int kSliderRes = 100;
  const int kSliderStep = 5;

  const int kSliderMinSize = 300;
}


Slider::Slider(const QString& labelText, double min, double max, int precision,
               double step, bool logScale, Qt::Orientation orientation,
               QWidget* parent, const char* name)
  : QWidget(parent, name),
    mOrientation(orientation),
    mLogScale(logScale),
    mSMin(logScale ? std::log((min > 0.001) ? min : 0.001) : min),
    mSRange(logScale ? (std::log(max)-mSMin) : (max-mSMin))
{
  kdDebug(60606) << "Slider::Slider: "
                 << "labelText = " << labelText << ", "
                 << "min = " << min << ", "
                 << "max = " << max << ", "
                 << "precision = " << precision << ", "
                 << "step = " << step << ", "
                 << "logScale = " << logScale << ", "
                 << "mSMin = " << mSMin << ", "
                 << "mSRange = " << mSRange
                 << "\n";

  QSizePolicy labelPolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);

  //
  // Main controls
  //

  // Slider
  mSlider = new QSlider(orientation, this, "slider");
  mSlider->setMinValue(0);
  mSlider->setMaxValue(kSliderRes);
  mSlider->setValue(valToSlider(min));
  mSlider->setPageStep(kSliderStep);
  mSlider->setTickInterval(kSliderStep);
  mSlider->installEventFilter(this);

  // Spinbox
  if (precision > 0)
  {
    mSpinBox = new KDoubleSpinBox(min, max, step, min, precision,
                                  this, "spinbox");
    connect(mSpinBox, SIGNAL(valueChanged(double)),
            this, SLOT(spinValueChanged(double)));
  }
  else
  {
    mSpinBox = new QSpinBox((int)min, (int)max, step < 1.0f ? 1 : (int)step,
                            this, "spinbox");
    connect(mSpinBox, SIGNAL(valueChanged(int)),
            this, SLOT(spinValueChanged(int)));
  }
  mSpinBox->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));

  // Control Label
  mLabel = new QLabel(this, "control_label");
  mLabel->setText(labelText);
  mLabel->setSizePolicy(labelPolicy);

  // Scale labels
  QFont labelFont(KGlobalSettings::generalFont());
  labelFont.setPointSize((labelFont.pointSize() > 8) ?
                         labelFont.pointSize() - 2 : 6);
  QLabel *maxLabel = new QLabel(this, "max_label");
  maxLabel->setText(QString::number(max, 'f', 2));
  maxLabel->setFont(labelFont);
  maxLabel->setSizePolicy(labelPolicy);
  QLabel *midLabel = new QLabel(this, "mid_label");
  midLabel->setText(QString::number(sliderToVal(kSliderRes/2), 'f', 2));
  midLabel->setFont(labelFont);
  midLabel->setSizePolicy(labelPolicy);
  QLabel *minLabel = new QLabel(this, "min_label");
  minLabel->setText(QString::number(min, 'f', 2));
  minLabel->setFont(labelFont);
  minLabel->setSizePolicy(labelPolicy);

  // Layout
  if (orientation == Qt::Horizontal)
    layoutHorizontalSlider(minLabel, midLabel, maxLabel);
  else
    layoutVerticalSlider(minLabel, midLabel, maxLabel);

  connect(mSlider, SIGNAL(valueChanged(int)),
          this, SLOT(sliderValueChanged(int)));
}

Slider::~Slider()
{
}

double Slider::value() const
{
  if (mSpinBox->isA("KDoubleSpinBox"))
    return static_cast<KDoubleSpinBox*>(mSpinBox)->value();
  else
    return mSpinBox->value();
}

int Slider::precision() const
{
  if (mSpinBox->isA("KDoubleSpinBox"))
    return static_cast<KDoubleSpinBox*>(mSpinBox)->precision();
  else
    return 0;
}

Qt::Orientation Slider::orientation() const
{
  return mOrientation;
}

void Slider::setValue(double value)
{
  mSpinBox->blockSignals(true);
  if (mSpinBox->isA("KDoubleSpinBox"))
    static_cast<KDoubleSpinBox*>(mSpinBox)->setValue((double)value);
  else
    mSpinBox->setValue((int)value);
  mSpinBox->blockSignals(false);

  mSlider->blockSignals(true);
  mSlider->setValue(valToSlider(value));
  mSlider->blockSignals(false);

  emit valueChanged(value);
}

void Slider::setPrecision(int precision)
{
  if (!mSpinBox->isA("KDoubleSpinBox"))
    return;

  KDoubleSpinBox *dblSpinBox = static_cast<KDoubleSpinBox*>(mSpinBox);

  double oldValue = dblSpinBox->value();

  dblSpinBox->blockSignals(true);
  dblSpinBox->setPrecision(precision);
  dblSpinBox->blockSignals(false);

  double newValue = dblSpinBox->value();

  mSlider->blockSignals(true);
  mSlider->setValue(valToSlider(newValue));
  mSlider->blockSignals(false);

  if (oldValue != newValue)
    emit valueChanged(newValue);
}

int Slider::labelWidth() const
{
  return mLabel->sizeHint().width();
}

int Slider::spinBoxWidth() const
{
  return mSpinBox->sizeHint().width();
}

void Slider::setLabelWidth(int width)
{
  mLabel->setMinimumWidth(width);
}

void Slider::setSpinBoxWidth(int width)
{
  mSpinBox->setMinimumWidth(width);
}

void Slider::spinValueChanged(double value)
{
  if (!mSpinBox->isA("KDoubleSpinBox"))
    return;

  mSlider->blockSignals(true);
  mSlider->setValue(valToSlider(value));
  mSlider->blockSignals(false);

  emit valueChanged(value);
}

void Slider::spinValueChanged(int value)
{
  if (mSpinBox->isA("KDoubleSpinBox"))
    return;

  mSlider->blockSignals(true);
  mSlider->setValue(valToSlider(value));
  mSlider->blockSignals(false);

  emit valueChanged(value);
}

void Slider::sliderValueChanged(int s)
{
  double v = sliderToVal(s);

  mSpinBox->blockSignals(true);
  if (mSpinBox->isA("KDoubleSpinBox"))
    static_cast<KDoubleSpinBox*>(mSpinBox)->setValue(v);
  else
    mSpinBox->setValue((int)v);
  mSpinBox->blockSignals(false);

  emit valueChanged(v);
}

double Slider::sliderToVal(int s) const
{
  double v;
  if (mSlider->orientation() == Qt::Vertical)
    v = mSMin + (kSliderRes - (double)s)/kSliderRes * mSRange;
  else
    v = mSMin + ((double)s)/kSliderRes * mSRange;

  return mLogScale ? std::exp(v) : v;
}

int Slider::valToSlider(double v) const
{
  if (mLogScale)
    v = std::log((v > 0.001) ? v : 0.001);

  if (mSlider->orientation() == Qt::Vertical)
    return kSliderRes - (int)((v - mSMin) * kSliderRes / mSRange);
  else
    return (int)((v - mSMin) * kSliderRes / mSRange);
}

bool Slider::eventFilter(QObject *o, QEvent *e)
{
  if (o == mSlider &&
      (e->type() == QEvent::MouseButtonPress ||
       e->type() == QEvent::Wheel))
  {
    mSpinBox->setFocus();
  }

  return false;
}

void Slider::layoutHorizontalSlider(QLabel *minLabel,
                                    QLabel *midLabel,
                                    QLabel *maxLabel)
{
    QHBoxLayout *mainLayout = new QHBoxLayout(this, 1, 2, "main_layout");
    QSpacerItem *spacer;

    mLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
    mainLayout->addWidget(mLabel);

    QVBoxLayout *sliderLayout = new QVBoxLayout(mainLayout, -1, "slider_layout");

    spacer = new QSpacerItem(5, 5, QSizePolicy::Minimum, QSizePolicy::Expanding);
    sliderLayout->addItem(spacer);

    mSlider->setMinimumWidth(kSliderMinSize);
    mSlider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding,
                                       QSizePolicy::Fixed));
    mSlider->setTickmarks(QSlider::Below);
    sliderLayout->addWidget(mSlider);

    QHBoxLayout *labelLayout = new QHBoxLayout(sliderLayout, -1, "label_layout");
    minLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    labelLayout->addWidget(minLabel);
    spacer = new QSpacerItem(5, 5, QSizePolicy::Minimum, QSizePolicy::Expanding);
    labelLayout->addItem(spacer);
    midLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
    labelLayout->addWidget(midLabel);
    spacer = new QSpacerItem(5, 5, QSizePolicy::Minimum, QSizePolicy::Expanding);
    labelLayout->addItem(spacer);
    maxLabel->setAlignment(Qt::AlignRight | Qt::AlignTop);
    labelLayout->addWidget(maxLabel);

    spacer = new QSpacerItem(5, 5, QSizePolicy::Minimum, QSizePolicy::Expanding);
    sliderLayout->addItem(spacer);

    mSpinBox->setMinimumWidth(50);
    mainLayout->addWidget(mSpinBox);

    setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
}

void Slider::layoutVerticalSlider(QLabel *minLabel,
                                  QLabel *midLabel,
                                  QLabel *maxLabel)
{
    QVBoxLayout* mainLayout = new QVBoxLayout(this, 0, 0, "main_layout");
    QHBoxLayout* rowLayout;
    QSpacerItem* spacer;

    // Text row
    rowLayout = new QHBoxLayout(mainLayout, -1, "row_layout");
    spacer = new QSpacerItem(5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum);
    rowLayout->addItem(spacer);

    rowLayout->addWidget(mLabel);
    spacer = new QSpacerItem(5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum);
    rowLayout->addItem(spacer);

    // Slider row
    rowLayout = new QHBoxLayout(mainLayout, -1, "row_layout");
    spacer = new QSpacerItem(5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum);
    rowLayout->addItem(spacer);

    mSlider->setMinimumHeight(kSliderMinSize);
    mSlider->setSizePolicy(QSizePolicy(QSizePolicy::Fixed,
                                       QSizePolicy::MinimumExpanding));
    mSlider->setTickmarks(QSlider::Right);
    rowLayout->addWidget(mSlider);

    spacer = new QSpacerItem(5, 5, QSizePolicy::Minimum, QSizePolicy::Minimum);
    rowLayout->addItem(spacer);

    QVBoxLayout* labelLayout = new QVBoxLayout(rowLayout, -1, "label_layout");
    maxLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    labelLayout->addWidget(maxLabel);
    spacer = new QSpacerItem(5, 5, QSizePolicy::Minimum, QSizePolicy::Expanding);
    labelLayout->addItem(spacer);
    midLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
    labelLayout->addWidget(midLabel);
    spacer = new QSpacerItem(5, 5, QSizePolicy::Minimum, QSizePolicy::Expanding);
    labelLayout->addItem(spacer);
    minLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
    labelLayout->addWidget(minLabel);

    spacer = new QSpacerItem(5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum);
    rowLayout->addItem(spacer);

    // Spin box row
    rowLayout = new QHBoxLayout(mainLayout, -1, "row_layout");
    spacer = new QSpacerItem(5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum);
    rowLayout->addItem(spacer);

    rowLayout->addWidget(mSpinBox);

    spacer = new QSpacerItem(5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum);
    rowLayout->addItem(spacer);

    setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding));
}

void Sonik::alignHorizontalSliders(SliderPtrVector& sliders)
{
  // find the max size
  int maxLabel = 0, maxSpin = 0;
  SliderPtrVector::iterator it = sliders.begin();
  for ( ; it != sliders.end(); ++it)
  {
    if ((*it)->orientation() == Qt::Horizontal)
    {
      maxLabel = QMAX((*it)->labelWidth(), maxLabel);
      maxSpin = QMAX((*it)->spinBoxWidth(), maxSpin);
    }
  }

  // resize all labels to match
  it = sliders.begin();
  for ( ; it != sliders.end(); ++it)
  {
    if ((*it)->orientation() == Qt::Horizontal)
    {
      (*it)->setLabelWidth(maxLabel);
      (*it)->setSpinBoxWidth(maxSpin);
    }
  }
}

// TODO: implement label alignment for vertical sliders (if necessary)
