/************************************************************************
 *
 * Copyright 2010-2012 Jakob Leben (jakob.leben@gmail.com)
 *
 * This file is part of SuperCollider Qt GUI.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 ************************************************************************/

#include "QcSlider.h"
#include "../QcWidgetFactory.h"

#include <QMouseEvent>
#include <QKeyEvent>
#include <QWheelEvent>

QC_DECLARE_QWIDGET_FACTORY(QcSlider);

QcSlider::QcSlider(): QtCollider::Style::Client(this), _value(0.0), _step(0.0), _hndLen(20) {
    setFocusPolicy(Qt::StrongFocus);
    setOrientation(Qt::Vertical);
    setAttribute(Qt::WA_AcceptTouchEvents);
}

double QcSlider::pixelStep() {
    using namespace QtCollider::Style;

    QRect contRect(sunkenContentsRect(rect()));
    int range = (_ort == Qt::Horizontal) ? contRect.width() : contRect.height();
    range -= _hndLen;

    if (range > 0)
        return 1.0 / range;
    else
        return 0.0;
}

void QcSlider::setValue(double val) {
    double step = _step;
    modifyStep(&step);
    if (step)
        val = qRound(val / step) * step;

    _value = qBound(0.0, val, 1.0);

    update();
}

void QcSlider::increment(double factor) {
    double step = qMax(_step, pixelStep());
    setValue(step * factor + _value);
    update();
}

void QcSlider::decrement(double factor) {
    double step = qMax(_step, pixelStep());
    setValue(-step * factor + _value);
    update();
}

void QcSlider::setOrientation(int i) {
    _ort = (Qt::Orientation)i;

    if (_ort == Qt::Horizontal)
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    else
        setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);

    updateGeometry();
    update();
}

QSize QcSlider::sizeHint() const {
    if (orientation() == Qt::Horizontal)
        return QSize(qMax(_hndLen + 10, 150), 20);
    else
        return QSize(20, qMax(_hndLen + 10, 150));
}

QSize QcSlider::minimumSizeHint() const {
    if (orientation() == Qt::Horizontal)
        return QSize(_hndLen + 10, 20);
    else
        return QSize(20, _hndLen + 10);
}

void QcSlider::mousePressEvent(QMouseEvent* e) {
    setValue(valueAt(e->pos()));
    update();
    Q_EMIT(action());
}

void QcSlider::mouseMoveEvent(QMouseEvent* e) {
    if (!e->buttons())
        return;

    setValue(valueAt(e->pos()));
    update();
    Q_EMIT(action());
}


void QcSlider::wheelEvent(QWheelEvent* e) {
    double step = qMax(_step, pixelStep());
    double numSteps = getScrollSteps(e).y();
    modifyStep(&step);
    // accumulate fractional numSteps to help scrolling through big steps
    scrollRemainder = std::modf(numSteps + scrollRemainder, &numSteps);
    setValue(_value + numSteps * step);
    Q_EMIT(action());
}

void QcSlider::paintEvent(QPaintEvent* e) {
    using namespace QtCollider::Style;
    using QtCollider::Style::RoundRect;

    QPainter p(this);
    const QPalette& plt = palette();

    p.save();
    p.setRenderHint(QPainter::Antialiasing, true);

    QRect rGroove = rect();

    // draw groove
    RoundRect shGroove(rGroove, 2);
    drawSunken(&p, plt, shGroove, grooveColor(), hasFocus() ? focusColor() : QColor());

    // geometry
    QRect rHandle(thumbRect());

    // draw handle
    RoundRect shHandle(rHandle, 2);
    drawRaised(&p, plt, shHandle, plt.color(QPalette::Button).lighter(105));

    p.restore();

    // draw marker
    QPen pen(knobColor());
    pen.setWidth(2);
    p.setPen(pen);
    if (_ort == Qt::Horizontal) {
        qreal center = rHandle.center().x() + 1;
        QLine line(center, rHandle.top() + 3, center, rHandle.bottom() - 2);
        p.drawLine(line);
        pen.setColor(plt.color(QPalette::Light));
    } else {
        qreal center = rHandle.center().y() + 1;
        QLine line(rHandle.left() + 3, center, rHandle.right() - 2, center);
        p.drawLine(line);
        pen.setColor(plt.color(QPalette::Light));
    }
}

QRect QcSlider::thumbRect() {
    using namespace QtCollider::Style;

    QRect contRect(sunkenContentsRect(rect()));

    QRect r;
    if (_ort == Qt::Horizontal) {
        int pos = _value * (contRect.width() - _hndLen);
        r.setX(pos + contRect.left());
        r.setY(contRect.top());
        r.setSize(QSize(_hndLen, contRect.height()));
    } else {
        int pos = _value * (contRect.height() - _hndLen);
        r.setX(contRect.left());
        r.setY(contRect.bottom() - pos - _hndLen + 1);
        r.setSize(QSize(contRect.width(), _hndLen));
    }
    return r;
}

double QcSlider::valueAt(const QPoint& pos) {
    using namespace QtCollider::Style;

    QRect contRect(sunkenContentsRect(rect()));
    if (_ort == Qt::Horizontal)
        return xValue(pos.x(), contRect, QSize(_hndLen, 0));
    else
        return yValue(pos.y(), contRect, QSize(0, _hndLen));
}
