スライダーとスピンボックスを連動させる

こんばんわ、[Qt Advent Calendar 2021](https://qiita.com/advent-calendar/2021/qt)の 19日目の記事です。

パラメータ入力にスライダーを使用する際、スピンボックスも同時に使用することが多いですよね。
今回はスライダーとスピンボックスを合わせたウィジェットを作ってみたいと思います。

# QSpinBoxとQSliderの連動
お互いの値変更シグナルを掴みます。

```
    connect(mSlider, SIGNAL(valueChanged(int)),this, SLOT(setValue_SpinBox(int)));
    connect(mSpinBox, SIGNAL(valueChanged(int)),this, SLOT(setValue_Slider(int)));
```

相手の値を変更します。

```
void SpinSlider::setValue_SpinBox(int value)
{
    mSpinBox->setValue(value);
}

void SpinSlider::setValue_Slider(int value)
{
    mSlider->setValue(value);
}
```
簡単でしたね。以下が全コードです。

```c++:spinslider.h
#include <QSlider>
#include <QSpinBox>

class SpinSlider : public QWidget
{
    Q_OBJECT
public:
    explicit SpinSlider(QWidget *parent = nullptr);

public Q_SLOTS:
    void setOrientation(Qt::Orientation value);
    void setValue(int value);
    void setRange(int min, int max);

private Q_SLOTS:
    void setValue_SpinBox(int value);
    void setValue_Slider(int value);

private:
    QSlider *mSlider = nullptr;
    QSpinBox *mSpinBox = nullptr;
};
```

```c++:spinslider.cpp
#include "spinslider.h"
#include <QLayout>

SpinSlider::SpinSlider(QWidget *parent)
    : QWidget(parent)
{
    mSlider = new QSlider();
    mSpinBox = new QSpinBox();
    mSpinBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    connect(mSlider, SIGNAL(valueChanged(int)),this, SLOT(setValue_SpinBox(int)));
    connect(mSpinBox, SIGNAL(valueChanged(int)),this, SLOT(setValue_Slider(int)));

    setOrientation(mSlider->orientation());
    setRange(mSpinBox->minimum(), mSpinBox->maximum());
    setValue(mSpinBox->value());
}

void SpinSlider::setOrientation(Qt::Orientation value)
{
    mSlider->setOrientation(value);
    QLayout *layout = this->layout();
    if (layout != nullptr)  delete layout;

    switch (value) {
    case Qt::Horizontal:
        layout = new QHBoxLayout(this);
        break;
    case Qt::Vertical:
        layout = new QVBoxLayout(this);
        break;
    }
    layout->addWidget(mSlider);
    layout->addWidget(mSpinBox);
}

void SpinSlider::setValue(int value)
{
     mSpinBox->setValue(value);
}

void SpinSlider::setRange(int min, int max)
{
    mSpinBox->setRange(min, max);
    mSlider->setRange(min, max);
}

void SpinSlider::setValue_SpinBox(int value)
{
    mSpinBox->setValue(value);
}

void SpinSlider::setValue_Slider(int value)
{
    mSlider->setValue(value);
}
```

# QDoubleSpinBoxとQSliderの連動
doubleの場合はQDoubleSliderを…と、そんなものはありませんね。
まずはdoubleに対応したスライダーを作成します。
今回sliderの刻み幅を表示桁の合わせました。
使用する側は、最初にdecimalsをセットする以外はあまり意識せず使えます。

```c++:doubleslider.h
#include <QSlider>
#include <qmath.h>

class DoubleSlider : public QSlider
{
    Q_OBJECT
public:
    DoubleSlider(QWidget *parent = 0) : QSlider(parent) {
        connect(this, SIGNAL(valueChanged(int)), this, SLOT(notifyValueChanged(int)));
    }
    void setValue(double value) { QSlider::setValue(value * (qPow(10, mDecimals)));}
    void setDecicals(int value) { mDecimals = value; }
    void setRange(double min, double max) {QSlider::setRange(min * (qPow(10, mDecimals)), max * (qPow(10, mDecimals))); }

signals:
    void valueChanged(double value);

private Q_SLOTS:
    void notifyValueChanged(int value) {
        double doubleValue = value / (qPow(10, mDecimals));
        emit valueChanged(doubleValue);
    }
private:
    int mDecimals = 0;
};

```

これさえ作ってしまえば、あとはQSpinBoxの時と同様にお互いの値変更シグナルを掴んで相手の値を変更すればほぼ完成です。

```c++:doublespinslider.h
#include "doubleslider.h"
#include <QDoubleSpinBox>

class DoubleSpinSlider : public QWidget
{
    Q_OBJECT
public:
    explicit DoubleSpinSlider(QWidget *parent = nullptr);

public Q_SLOTS:
    void setOrientation(Qt::Orientation value);
    void setValue(double value);
    void setRange(double min, double max);

private Q_SLOTS:
    void setValue_SpinBox(double value) { mSpinBox->setValue(value); }
    void setValue_Slider(double value) { mSlider->setValue(value); }

private:
    DoubleSlider *mSlider = nullptr;
    QDoubleSpinBox *mSpinBox = nullptr;
}
```


```c++:doublespinslider.cpp
#include "doublespinslider.h"
#include<QLayout>

DoubleSpinSlider::DoubleSpinSlider(QWidget *parent)
    : QWidget(parent)
{
    mSlider = new DoubleSlider();
    mSpinBox = new QDoubleSpinBox();
    mSlider->setDecicals(mSpinBox->decimals());
    mSpinBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    connect(mSlider, SIGNAL(valueChanged(double)),this, SLOT(setValue_SpinBox(double)));
    connect(mSpinBox, SIGNAL(valueChanged(double)),this, SLOT(setValue_Slider(double)));

    setOrientation(mSlider->orientation());
    setRange(mSpinBox->minimum(), mSpinBox->maximum());
    setValue(mSpinBox->value());
}

void DoubleSpinSlider::setOrientation(Qt::Orientation value)
{
    mSlider->setOrientation(value);
    QLayout *layout = this->layout();
    if (layout != nullptr)  delete layout;

    switch (value) {
    case Qt::Horizontal:
        layout = new QHBoxLayout(this);
        break;
    case Qt::Vertical:
        layout = new QVBoxLayout(this);
        break;
    }
    layout->addWidget(mSlider);
    layout->addWidget(mSpinBox);
}

void DoubleSpinSlider::setValue(double value)
{
    mSpinBox->setValue(value);
}

void DoubleSpinSlider::setRange(double min, double max)
{
    mSpinBox->setRange(min, max);
    mSlider->setRange(min, max);
}
```

# 終わりに
スライダーとスピンボックスを合わせたウィジェットを作ってみました。

今回は等間隔で値が変化するスライダーを作りましたが、そうでないものもたくさんありますよね。
そういう場合はルックアップテーブルを使用するのがいいんでしょうかね？

みなさんも作ってみてください。