#include "toonzqt/intfield.h"
#include "toonzqt/dvdialog.h"
#include "toonzqt/gutil.h"
#include <QIntValidator>
#include <QSlider>
#include <QHBoxLayout>
#include <QAction>
#include <QFocusEvent>
#include <QPainter>
#include <QPushButton>
namespace {
const int NonLinearSliderPrecision = 2;
}
using namespace DVGui;
//=============================================================================
// RollerField
//-----------------------------------------------------------------------------
RollerField::RollerField(QWidget *parent)
: QWidget(parent)
, m_value(0)
, m_minValue(-100000.0)
, m_maxValue(100000.0)
, m_xPos(0)
, m_step(1.0) {
setMinimumSize(43, 7);
}
//-----------------------------------------------------------------------------
void RollerField::setValue(double value) {
if (m_value == value) return;
if (value < m_minValue) m_value = m_minValue;
if (value > m_maxValue) m_value = m_maxValue;
m_value = value;
}
//-----------------------------------------------------------------------------
double RollerField::getValue() const { return m_value; }
//-----------------------------------------------------------------------------
void RollerField::setRange(double minValue, double maxValue) {
m_minValue = minValue;
m_maxValue = maxValue;
}
//-----------------------------------------------------------------------------
void RollerField::getRange(double &minValue, double &maxValue) {
minValue = m_minValue;
maxValue = m_maxValue;
}
//-----------------------------------------------------------------------------
void RollerField::paintEvent(QPaintEvent *e) {
QPainter p(this);
int w = width();
drawArrow(p, QPointF(3, 3), QPointF(5, 5), QPointF(5, 1), true, Qt::black,
Qt::black);
drawArrow(p, QPointF(w - 4, 3), QPointF(w - 6, 5), QPointF(w - 6, 1), true,
Qt::black, Qt::black);
p.drawLine(QPoint(3, 3), QPoint(w - 4, 3));
}
//-----------------------------------------------------------------------------
void RollerField::mousePressEvent(QMouseEvent *e) {
if (e->buttons() == Qt::LeftButton) {
m_xPos = e->pos().x();
e->accept();
}
}
//-----------------------------------------------------------------------------
void RollerField::mouseMoveEvent(QMouseEvent *e) {
if (e->buttons() == Qt::LeftButton) {
if (m_xPos < e->pos().x())
addValue(true);
else if (m_xPos > e->pos().x())
removeValue(true);
m_xPos = e->pos().x();
e->accept();
}
}
//-----------------------------------------------------------------------------
void RollerField::mouseReleaseEvent(QMouseEvent *e) {
e->accept();
emit valueChanged(false);
}
//-----------------------------------------------------------------------------
void RollerField::addValue(bool isDragging) {
double newValue = tcrop(m_value + m_step, m_minValue, m_maxValue);
if (newValue == m_value) return;
m_value = newValue;
emit valueChanged(isDragging);
}
//-----------------------------------------------------------------------------
void RollerField::removeValue(bool isDragging) {
double newValue = tcrop(m_value - m_step, m_minValue, m_maxValue);
if (newValue == m_value) return;
m_value = newValue;
emit valueChanged(isDragging);
}
//=============================================================================
// IntLineEdit
//-----------------------------------------------------------------------------
IntLineEdit::IntLineEdit(QWidget *parent, int value, int minValue, int maxValue,
int showedDigits)
: LineEdit(parent), m_showedDigits(showedDigits) {
setFixedWidth(40);
m_validator = new QIntValidator(this);
setValue(value);
setRange(minValue, maxValue);
setValidator(m_validator);
}
//-----------------------------------------------------------------------------
void IntLineEdit::setValue(int value) {
int minValue, maxValue;
getRange(minValue, maxValue);
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
QString str;
str.setNum(value);
if (m_showedDigits > 0) {
while (str.length() < m_showedDigits) str.push_front("0");
while (str.length() > m_showedDigits) str.remove(0, 1);
}
setText(str);
// Faccio in modo che il cursore sia sulla prima cifra, cosi' se la stringa da
// visualizzare
// e' piu' lunga del campo le cifre che vengono troncate sono le ultime e non
// le prime.
setCursorPosition(0);
}
//-----------------------------------------------------------------------------
int IntLineEdit::getValue() { return text().toInt(); }
//-----------------------------------------------------------------------------
void IntLineEdit::setRange(int minValue, int maxValue) {
m_validator->setRange(minValue, maxValue);
}
//-----------------------------------------------------------------------------
void IntLineEdit::getRange(int &minValue, int &maxValue) {
minValue = m_validator->bottom();
maxValue = m_validator->top();
}
//-----------------------------------------------------------------------------
void IntLineEdit::setBottomRange(int minValue) {
m_validator->setBottom(minValue);
}
//-----------------------------------------------------------------------------
void IntLineEdit::setTopRange(int maxValue) { m_validator->setTop(maxValue); }
//-----------------------------------------------------------------------------
void IntLineEdit::focusOutEvent(QFocusEvent *e) {
int value = getValue();
int minValue, maxValue;
getRange(minValue, maxValue);
if (e->lostFocus()) setValue(value);
QLineEdit::focusOutEvent(e);
m_isTyping = false;
}
//-----------------------------------------------------------------------------
// for fps edit in flip console
void IntLineEdit::setLineEditBackgroundColor(QColor color) {
// Set text color based on luminescence of bg color
int value = 0;
double luminescence = ((0.299 * color.red()) + (0.587 * color.green()) +
(0.114 * color.blue())) /
255;
if (luminescence > 0.5)
value = 0; // black
else
value = 255; // white
QString sheet =
QString("background-color: rgba(") + QString::number(color.red()) +
QString(",") + QString::number(color.green()) + QString(",") +
QString::number(color.blue()) + QString(",") +
QString::number(color.alpha()) +
QString(");" +
QString("color: rgb(" + QString::number(value) + QString(",") +
QString::number(value) + QString(",") +
QString::number(value) + QString(");")));
setStyleSheet(sheet);
}
//-----------------------------------------------------------------------------
void IntLineEdit::mousePressEvent(QMouseEvent *e) {
if (e->buttons() == Qt::MiddleButton) {
m_xMouse = e->x();
m_mouseDragEditing = true;
} else {
QLineEdit::mousePressEvent(e);
if (!m_isTyping) { // only the first click will select all
selectAll();
m_isTyping = true;
}
}
}
//-----------------------------------------------------------------------------
void IntLineEdit::mouseMoveEvent(QMouseEvent *e) {
if (e->buttons() == Qt::MiddleButton) {
setValue(getValue() + ((e->x() - m_xMouse) / 2));
m_xMouse = e->x();
} else
QLineEdit::mouseMoveEvent(e);
}
//-----------------------------------------------------------------------------
void IntLineEdit::mouseReleaseEvent(QMouseEvent *e) {
if ((e->buttons() == Qt::NoButton && m_mouseDragEditing)) {
m_mouseDragEditing = false;
clearFocus();
} else
QLineEdit::mouseReleaseEvent(e);
}
//=============================================================================
// IntField
//-----------------------------------------------------------------------------
IntField::IntField(QWidget *parent, bool isMaxRangeLimited, bool isRollerHide,
bool isSpinnerHide)
: QWidget(parent)
, m_roller(0)
, m_lineEdit(0)
, m_slider(0)
, m_inc(0)
, m_dec(0)
, m_isMaxRangeLimited(isMaxRangeLimited)
, m_isLinearSlider(true) {
setObjectName("IntField");
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setMargin(0);
layout->setSpacing(5);
QWidget *field = new QWidget(this);
field->setMaximumWidth(43);
QVBoxLayout *vLayout = new QVBoxLayout(field);
vLayout->setMargin(0);
vLayout->setSpacing(0);
m_lineEdit = new DVGui::IntLineEdit(field);
bool ret = connect(m_lineEdit, SIGNAL(editingFinished()), this,
SLOT(onEditingFinished()));
vLayout->addWidget(m_lineEdit);
m_roller = new RollerField(field);
ret = ret && connect(m_roller, SIGNAL(valueChanged(bool)), this,
SLOT(onRollerValueChanged(bool)));
vLayout->addWidget(m_roller);
if (isRollerHide) enableRoller(false);
layout->addWidget(field);
m_inc = new QPushButton(QString("+"));
m_dec = new QPushButton(QString("-"));
m_inc->setFixedSize(QSize(20, 20));
m_dec->setFixedSize(QSize(20, 20));
ret = ret
&& connect(m_inc, SIGNAL(clicked()), this, SLOT(onIncClicked()))
&& connect(m_dec, SIGNAL(clicked()), this, SLOT(onDecClicked()));
if (isSpinnerHide) enableSpinner(false);
// TODO:
// Commonly in OpenToonz spin-buttons has been placed in that order: [+][-]
// This seems unusual behavior.
// And in this particular case buttons has been placed in another order: [-][+]
// We need to know what is better
layout->addWidget(m_dec);
layout->addWidget(m_inc);
m_slider = new QSlider(Qt::Horizontal, this);
ret = ret && connect(m_slider, SIGNAL(valueChanged(int)), this,
SLOT(onSliderChanged(int)));
ret = ret && connect(m_slider, SIGNAL(sliderReleased()), this,
SLOT(onSliderReleased()));
ret = ret && connect(m_lineEdit, SIGNAL(editingFinished()), this,
SIGNAL(valueEditedByHand()));
ret = ret && connect(m_slider, SIGNAL(sliderReleased()), this,
SIGNAL(valueEditedByHand()));
layout->addWidget(m_slider);
setValues(0, 0, 100);
setLayout(layout);
assert(ret);
}
//-----------------------------------------------------------------------------
void IntField::getRange(int &minValue, int &maxValue) {
double min, max;
m_roller->getRange(min, max);
minValue = tround(min);
maxValue = tround(max);
}
//-----------------------------------------------------------------------------
void IntField::setRange(int minValue, int maxValue) {
m_lineEdit->setRange(minValue, m_isMaxRangeLimited
? maxValue
: (std::numeric_limits<int>::max)());
if (m_isLinearSlider)
m_slider->setRange(minValue, maxValue);
else
m_slider->setRange(minValue * pow(10., NonLinearSliderPrecision),
maxValue * pow(10., NonLinearSliderPrecision));
m_roller->setRange(minValue, maxValue);
}
//-----------------------------------------------------------------------------
void IntField::setValue(int value) {
if (m_lineEdit->getValue() == value) return;
m_lineEdit->setValue(value);
m_slider->setSliderPosition(value2pos(value));
m_roller->setValue((double)value);
}
//-----------------------------------------------------------------------------
int IntField::getValue() { return (m_lineEdit->getValue()); }
//-----------------------------------------------------------------------------
void IntField::setValues(int value, int minValue, int maxValue) {
setRange(minValue, maxValue);
setValue(value);
}
//-----------------------------------------------------------------------------
void IntField::enableSlider(bool enable) {
m_slider->setEnabled(enable);
if (enable)
m_slider->show();
else
m_slider->hide();
}
//-----------------------------------------------------------------------------
bool IntField::sliderIsEnabled() { return m_slider->isEnabled(); }
//-----------------------------------------------------------------------------
void IntField::enableRoller(bool enable) {
m_roller->setEnabled(enable);
if (enable)
m_roller->show();
else
m_roller->hide();
}
//-----------------------------------------------------------------------------
bool IntField::rollerIsEnabled() { return m_roller->isEnabled(); }
//-----------------------------------------------------------------------------
void IntField::enableSpinner(bool enable) {
m_inc->setEnabled(enable);
m_dec->setEnabled(enable);
if (enable) {
m_inc->show();
m_dec->show();
} else {
m_inc->hide();
m_dec->hide();
}
}
//-----------------------------------------------------------------------------
bool IntField::spinnerIsEnabled() { return m_inc->isEnabled(); }
//-----------------------------------------------------------------------------
void IntField::setLineEditBackgroundColor(QColor color) {
m_lineEdit->setLineEditBackgroundColor(color);
}
//-----------------------------------------------------------------------------
int IntField::pos2value(int x) const {
if (m_isLinearSlider) return x;
// nonlinear slider case
double rangeSize = (double)(m_slider->maximum() - m_slider->minimum());
double posRatio = (double)(x - m_slider->minimum()) / rangeSize;
double t;
if (posRatio <= 0.5)
t = 0.04 * posRatio;
else if (posRatio <= 0.75)
t = -0.02 + 0.08 * posRatio;
else if (posRatio <= 0.9)
t = -0.26 + 0.4 * posRatio;
else
t = -8.0 + 9.0 * posRatio;
double sliderVal = (double)m_slider->minimum() + rangeSize * t;
return (int)round(sliderVal * pow(0.1, NonLinearSliderPrecision));
}
//-----------------------------------------------------------------------------
int IntField::value2pos(int v) const {
if (m_isLinearSlider) return v;
// nonlinear slider case
double sliderVal = (double)v * pow(10., NonLinearSliderPrecision);
double rangeSize = (double)(m_slider->maximum() - m_slider->minimum());
double valueRatio = (double)(sliderVal - m_slider->minimum()) / rangeSize;
double t;
if (valueRatio <= 0.02)
t = valueRatio / 0.04;
else if (valueRatio <= 0.04)
t = (valueRatio + 0.02) / 0.08;
else if (valueRatio <= 0.1)
t = (valueRatio + 0.26) / 0.4;
else
t = (valueRatio + 8.0) / 9.0;
return m_slider->minimum() + (int)(t * rangeSize);
}
//-----------------------------------------------------------------------------
void IntField::onSliderChanged(int sliderPos) {
int value = pos2value(sliderPos);
// Controllo necessario per evitare che il segnale di cambiamento venga emesso
// piu' volte.
if (m_lineEdit->getValue() == value ||
((int)m_roller->getValue() == value && m_roller->isVisible()))
return;
m_lineEdit->setValue(value);
m_roller->setValue((double)value);
// Faccio in modo che il cursore sia sulla prima cifra, cosi' se la stringa
// da visualizzare e' piu' lunga del campo le cifre che vengono troncate sono
// le ultime e non le prime (dovrebbero essere quelle dopo la virgola).
m_lineEdit->setCursorPosition(0);
emit valueChanged(true);
}
//-----------------------------------------------------------------------------
void IntField::onIncClicked() {
int value = m_lineEdit->getValue() + 1;
m_lineEdit->setValue(value);
m_slider->setValue(value2pos(value));
m_roller->setValue((double)value);
m_lineEdit->setCursorPosition(0);
emit valueChanged(false);
}
//-----------------------------------------------------------------------------
void IntField::onDecClicked() {
int value = m_lineEdit->getValue() - 1;
m_lineEdit->setValue(value);
m_slider->setValue(value2pos(value));
m_roller->setValue((double)value);
m_lineEdit->setCursorPosition(0);
emit valueChanged(false);
}
//-----------------------------------------------------------------------------
void IntField::onEditingFinished() {
double value = m_lineEdit->getValue();
// Controllo necessario per evitare che il segnale di cambiamento venga emesso
// piu' volte.
if ((pos2value(m_slider->value()) == value && m_slider->isVisible()) ||
((int)m_roller->getValue() == value && m_roller->isVisible()))
return;
m_slider->setValue(value2pos(value));
m_roller->setValue((double)value);
emit valueChanged(false);
}
//-----------------------------------------------------------------------------
void IntField::onRollerValueChanged(bool isDragging) {
int value = m_roller->getValue();
if (value == m_lineEdit->getValue()) {
assert(pos2value(m_slider->value()) == value || !m_slider->isVisible());
// Se isDragging e' falso e' giusto che venga emessa la notifica di
// cambiamento.
if (!isDragging) emit valueChanged(isDragging);
return;
}
m_slider->setValue(value2pos(value));
m_lineEdit->setValue(value);
// Faccio in modo che il cursore sia sulla prima cifra, cosi' se la stringa
// da visualizzare e' piu' lunga del campo le cifre che vengono troncate sono
// le ultime e non le prime (dovrebbero essere quelle dopo la virgola).
m_lineEdit->setCursorPosition(0);
emit valueChanged(isDragging);
}