Files @ 561c68ae0a5e
Branch filter:

Location: tempo-plotter/src/plotcontrolpanel.cpp - annotation

Hasan Yavuz ÖZDERYA
download qwt archive from sourceforge instead of using subversion
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
1e14c645a35b
ed444750d8c4
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
ceb94a4c22ec
6c0c96841b44
6c0c96841b44
da766523eb64
ed444750d8c4
ceb94a4c22ec
ceb94a4c22ec
ceb94a4c22ec
1e14c645a35b
1e14c645a35b
8096ef7aa74f
1e14c645a35b
6c0c96841b44
e82a2aa579c9
da766523eb64
da766523eb64
6c0c96841b44
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
da766523eb64
1e14c645a35b
1e14c645a35b
601fe4ade58b
601fe4ade58b
601fe4ade58b
601fe4ade58b
601fe4ade58b
6acb6d6f037c
8edbdb6e43df
8edbdb6e43df
601fe4ade58b
1e14c645a35b
1e14c645a35b
1e14c645a35b
da766523eb64
da766523eb64
da766523eb64
6c0c96841b44
6c0c96841b44
6c0c96841b44
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
1e14c645a35b
1e14c645a35b
6c0c96841b44
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
4126c800559d
4126c800559d
4126c800559d
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
2c1e89113ca8
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
de31cf5f538c
de31cf5f538c
ed444750d8c4
ed444750d8c4
ed444750d8c4
601fe4ade58b
6acb6d6f037c
6acb6d6f037c
601fe4ade58b
601fe4ade58b
8edbdb6e43df
8edbdb6e43df
601fe4ade58b
601fe4ade58b
6acb6d6f037c
6acb6d6f037c
6acb6d6f037c
6acb6d6f037c
6acb6d6f037c
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
2391097cdad5
2391097cdad5
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
6c0c96841b44
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
ed444750d8c4
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
bfd45d598368
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
bfd45d598368
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
655c5c52b942
655c5c52b942
655c5c52b942
655c5c52b942
1e14c645a35b
1e14c645a35b
2b698ce67e65
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
2b698ce67e65
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
2b698ce67e65
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
2b698ce67e65
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
1e14c645a35b
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
4126c800559d
f102fddaeb9f
4126c800559d
4126c800559d
4126c800559d
4126c800559d
655c5c52b942
655c5c52b942
655c5c52b942
f102fddaeb9f
655c5c52b942
4126c800559d
4126c800559d
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
f102fddaeb9f
3be63f78737a
1e14c645a35b
3be63f78737a
3be63f78737a
3be63f78737a
c83e2c5aa6fe
c83e2c5aa6fe
c83e2c5aa6fe
de31cf5f538c
de31cf5f538c
de31cf5f538c
de31cf5f538c
de31cf5f538c
ed444750d8c4
de31cf5f538c
de31cf5f538c
de31cf5f538c
de31cf5f538c
de31cf5f538c
ed444750d8c4
de31cf5f538c
de31cf5f538c
ed444750d8c4
c83e2c5aa6fe
c83e2c5aa6fe
ff0a686c939f
ff0a686c939f
ff0a686c939f
ff0a686c939f
ff0a686c939f
ed444750d8c4
ed444750d8c4
ff0a686c939f
ff0a686c939f
ff0a686c939f
c83e2c5aa6fe
c83e2c5aa6fe
c83e2c5aa6fe
c83e2c5aa6fe
c83e2c5aa6fe
c83e2c5aa6fe
c83e2c5aa6fe
c83e2c5aa6fe
c83e2c5aa6fe
c83e2c5aa6fe
ed444750d8c4
c83e2c5aa6fe
601fe4ade58b
601fe4ade58b
601fe4ade58b
601fe4ade58b
601fe4ade58b
8edbdb6e43df
8edbdb6e43df
6acb6d6f037c
6acb6d6f037c
1e14c645a35b
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
746904c1f67b
8ab6e827bf3e
8ab6e827bf3e
8ab6e827bf3e
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
746904c1f67b
746904c1f67b
8ab6e827bf3e
8ab6e827bf3e
8ab6e827bf3e
8ab6e827bf3e
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
8096ef7aa74f
/*
  Copyright © 2019 Hasan Yavuz Özderya

  This file is part of serialplot.

  serialplot 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 3 of the License, or
  (at your option) any later version.

  serialplot 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 serialplot.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <QVariant>
#include <QMessageBox>
#include <QCheckBox>
#include <QStyledItemDelegate>
#include <QColorDialog>

#include <math.h>

#include "plotcontrolpanel.h"
#include "ui_plotcontrolpanel.h"
#include "setting_defines.h"

/// Confirm if #samples is being set to a value greater than this
const int NUMSAMPLES_CONFIRM_AT = 1000000;
/// Precision used for channel info table numbers
const int DOUBLESP_PRECISION = 6;

/// Used for scale range selection combobox
struct Range
{
    double rmin;
    double rmax;
};

Q_DECLARE_METATYPE(Range);

/// Used for customizing double precision in tables
class SpinBoxDelegate : public QStyledItemDelegate
{
public:
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const Q_DECL_OVERRIDE
        {
            auto w = QStyledItemDelegate::createEditor(
                parent, option, index);

            auto sp = qobject_cast<QDoubleSpinBox*>(w);
            if (sp)
            {
                sp->setDecimals(DOUBLESP_PRECISION);
            }
            return w;
        }
};

PlotControlPanel::PlotControlPanel(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::PlotControlPanel),
    resetAct(tr("Reset"), this),
    resetNamesAct(tr("Reset Names"), this),
    resetColorsAct(tr("Reset Colors"), this),
    showAllAct(tr("Show All"), this),
    hideAllAct(tr("Hide All"), this),
    resetGainsAct(tr("Reset All Gain"), this),
    resetOffsetsAct(tr("Reset All Offset"), this),
    resetMenu(tr("Reset Menu"), this)
{
    ui->setupUi(this);

    delegate = new SpinBoxDelegate();
    ui->tvChannelInfo->setItemDelegate(delegate);

    warnNumOfSamples = true;    // TODO: load from settings
    _numOfSamples = ui->spNumOfSamples->value();

    // set limits for axis limit boxes
    ui->spYmin->setRange((-1) * std::numeric_limits<double>::max(),
                         std::numeric_limits<double>::max());

    ui->spYmax->setRange((-1) * std::numeric_limits<double>::max(),
                         std::numeric_limits<double>::max());

    ui->spXmin->setRange((-1) * std::numeric_limits<double>::max(),
                         std::numeric_limits<double>::max());

    ui->spXmax->setRange((-1) * std::numeric_limits<double>::max(),
                         std::numeric_limits<double>::max());

    // connect signals
    connect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
            this, SLOT(onNumOfSamples(int)));

    connect(ui->cbAutoScale, &QCheckBox::toggled,
            this, &PlotControlPanel::onAutoScaleChecked);

    connect(ui->spYmax, SIGNAL(valueChanged(double)),
            this, SLOT(onYScaleChanged()));

    connect(ui->spYmin, SIGNAL(valueChanged(double)),
            this, SLOT(onYScaleChanged()));

    connect(ui->cbIndex, &QCheckBox::toggled,
            this, &PlotControlPanel::onIndexChecked);

    connect(ui->spXmax, SIGNAL(valueChanged(double)),
            this, SLOT(onXScaleChanged()));

    connect(ui->spXmax, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
            [this](double v)
            {
                // set limit just a little below
                double step = pow(10, -1 * ui->spXmin->decimals());
                ui->spXmin->setMaximum(v - step);
            });

    connect(ui->spXmin, SIGNAL(valueChanged(double)),
            this, SLOT(onXScaleChanged()));

    connect(ui->spXmin, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
            [this](double v)
            {
                // set limit just a little above
                double step = pow(10, -1 * ui->spXmax->decimals());
                ui->spXmax->setMinimum(v + step);
            });

    connect(ui->spPlotWidth, SIGNAL(valueChanged(int)),
            this, SLOT(onPlotWidthChanged()));

    // init scale range preset list
    for (int nbits = 8; nbits <= 24; nbits++) // signed binary formats
    {
        int rmax = pow(2, nbits-1)-1;
        int rmin = -rmax-1;
        Range r = {double(rmin),  double(rmax)};
        ui->cbRangePresets->addItem(
            QString().sprintf("Signed %d bits %d to +%d", nbits, rmin, rmax),
            QVariant::fromValue(r));
    }
    for (int nbits = 8; nbits <= 24; nbits++) // unsigned binary formats
    {
        int rmax = pow(2, nbits)-1;
        ui->cbRangePresets->addItem(
            QString().sprintf("Unsigned %d bits %d to +%d", nbits, 0, rmax),
            QVariant::fromValue(Range{0, double(rmax)}));
    }
    ui->cbRangePresets->addItem("-1 to +1", QVariant::fromValue(Range{-1, +1}));
    ui->cbRangePresets->addItem("0 to +1", QVariant::fromValue(Range{0, +1}));
    ui->cbRangePresets->addItem("-100 to +100", QVariant::fromValue(Range{-100, +100}));
    ui->cbRangePresets->addItem("0 to +100", QVariant::fromValue(Range{0, +100}));

    QObject::connect(ui->cbRangePresets, SIGNAL(activated(int)),
                     this, SLOT(onRangeSelected()));

    // color selector starts disabled until a channel is selected
    ui->pbColorSel->setDisabled(true);
    setSelectorColor(QColor(0,0,0,0));
    connect(ui->pbColorSel, &QPushButton::clicked, this, &PlotControlPanel::onColorSelect);

    // reset buttons
    resetAct.setToolTip(tr("Reset channel names and colors"));
    resetMenu.addAction(&resetNamesAct);
    resetMenu.addAction(&resetColorsAct);
    resetMenu.addAction(&resetGainsAct);
    resetMenu.addAction(&resetOffsetsAct);
    resetAct.setMenu(&resetMenu);
    ui->tbReset->setDefaultAction(&resetAct);

    showAllAct.setToolTip(tr("Show all channels"));
    hideAllAct.setToolTip(tr("Hide all channels"));
    ui->tbShowAll->setDefaultAction(&showAllAct);
    ui->tbHideAll->setDefaultAction(&hideAllAct);
}

PlotControlPanel::~PlotControlPanel()
{
    delete ui;
}

unsigned PlotControlPanel::numOfSamples()
{
    return ui->spNumOfSamples->value();
}

void PlotControlPanel::onNumOfSamples(int value)
{
    if (warnNumOfSamples && value > NUMSAMPLES_CONFIRM_AT)
    {
        // ask confirmation
        if (!askNSConfirmation(value))
        {
            // revert to old value
            disconnect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
                       this, SLOT(onNumOfSamples(int)));

            ui->spNumOfSamples->setValue(_numOfSamples);

            connect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
                    this, SLOT(onNumOfSamples(int)));

            return;
        }
    }

    _numOfSamples = value;
    emit numOfSamplesChanged(value);
}

bool PlotControlPanel::askNSConfirmation(int value)
{
    auto text = tr("Setting number of samples to a too big value "
                   "(>%1) can seriously impact the performance of "
                   "the application and cause freezes. Are you sure you "
                   "want to change the number of samples to %2?")
        .arg(QString::number(NUMSAMPLES_CONFIRM_AT), QString::number(value));

    // TODO: parent the mainwindow
    QMessageBox mb(QMessageBox::Warning,
                   tr("Confirm Number of Samples"),
                   text,
                   QMessageBox::Apply | QMessageBox::Cancel,
                   this);

    auto cb = new QCheckBox("Don't show this again.");
    connect(cb, &QCheckBox::stateChanged, [this](int state)
            {
                warnNumOfSamples = (state == Qt::Unchecked);
            });

    mb.setCheckBox(cb);

    return mb.exec() == QMessageBox::Apply;
}

void PlotControlPanel::setSelectorColor(QColor color)
{
    ui->pbColorSel->setStyleSheet(QString("background-color: %1;").arg(color.name()));
}

void PlotControlPanel::onColorSelect()
{
    auto selection = ui->tvChannelInfo->selectionModel()->currentIndex();
    // no selection
    if (!selection.isValid()) return;

    // current color
    auto model = ui->tvChannelInfo->model();
    QColor color = model->data(selection, Qt::ForegroundRole).value<QColor>();

    // show dialog
    color = QColorDialog::getColor(color, this);

    if (color.isValid())        // color is set to invalid if user cancels
    {
        ui->tvChannelInfo->model()->setData(selection, color, Qt::ForegroundRole);
    }
}

void PlotControlPanel::onAutoScaleChecked(bool checked)
{
    if (checked)
    {
        ui->lYmin->setEnabled(false);
        ui->lYmax->setEnabled(false);
        ui->spYmin->setEnabled(false);
        ui->spYmax->setEnabled(false);

        emit yScaleChanged(true); // autoscale
    }
    else
    {
        ui->lYmin->setEnabled(true);
        ui->lYmax->setEnabled(true);
        ui->spYmin->setEnabled(true);
        ui->spYmax->setEnabled(true);

        emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
    }
}

void PlotControlPanel::onYScaleChanged()
{
    if (!autoScale())
    {
        emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
    }
}

bool PlotControlPanel::autoScale() const
{
    return ui->cbAutoScale->isChecked();
}

double PlotControlPanel::yMax() const
{
    return ui->spYmax->value();
}

double PlotControlPanel::yMin() const
{
    return ui->spYmin->value();
}

bool PlotControlPanel::xAxisAsIndex() const
{
    return ui->cbIndex->isChecked();
}

double PlotControlPanel::xMax() const
{
    return ui->spXmax->value();
}

double PlotControlPanel::xMin() const
{
    return ui->spXmin->value();
}

void PlotControlPanel::onRangeSelected()
{
    Range r = ui->cbRangePresets->currentData().value<Range>();
    ui->spYmin->setValue(r.rmin);
    ui->spYmax->setValue(r.rmax);
    ui->cbAutoScale->setChecked(false);
}

void PlotControlPanel::onIndexChecked(bool checked)
{
    if (checked)
    {
        ui->lXmin->setEnabled(false);
        ui->lXmax->setEnabled(false);
        ui->spXmin->setEnabled(false);
        ui->spXmax->setEnabled(false);

        emit xScaleChanged(true); // use index
    }
    else
    {
        ui->lXmin->setEnabled(true);
        ui->lXmax->setEnabled(true);
        ui->spXmin->setEnabled(true);
        ui->spXmax->setEnabled(true);

        emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
    }
    emit plotWidthChanged(plotWidth());
}

void PlotControlPanel::onXScaleChanged()
{
    if (!xAxisAsIndex())
    {
        emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
        emit plotWidthChanged(plotWidth());
    }
}

double PlotControlPanel::plotWidth() const
{
    double value = ui->spPlotWidth->value();
    if (!xAxisAsIndex())
    {
        // scale by xmin and xmax
        auto xmax = ui->spXmax->value();
        auto xmin = ui->spXmin->value();
        double scale = (xmax - xmin) / _numOfSamples;
        value *= scale;
    }
    return value;
}

void PlotControlPanel::onPlotWidthChanged()
{
    emit plotWidthChanged(plotWidth());
}

void PlotControlPanel::setChannelInfoModel(ChannelInfoModel* model)
{
    ui->tvChannelInfo->setModel(model);

    // channel color selector
    connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::currentRowChanged,
            [this](const QModelIndex &current, const QModelIndex &previous)
            {
                // TODO: duplicate with below lambda
                QColor color(0,0,0,0); // transparent

                if (current.isValid())
                {
                    ui->pbColorSel->setEnabled(true);
                    auto model = ui->tvChannelInfo->model();
                    color = model->data(current, Qt::ForegroundRole).value<QColor>();
                }
                else
                {
                    ui->pbColorSel->setDisabled(true);
                }

                setSelectorColor(color);
            });

    connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::selectionChanged,
            [this](const QItemSelection & selected, const QItemSelection & deselected)
            {
                if (!selected.length())
                {
                    ui->pbColorSel->setDisabled(true);
                    setSelectorColor(QColor(0,0,0,0));
                }
            });

    connect(model, &QAbstractItemModel::dataChanged,
            [this](const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector<int> & roles = QVector<int> ())
            {
                auto current = ui->tvChannelInfo->selectionModel()->currentIndex();

                // no current selection
                if (!current.isValid()) return;

                auto mod = ui->tvChannelInfo->model();
                QColor color = mod->data(current, Qt::ForegroundRole).value<QColor>();
                setSelectorColor(color);
            });

    // reset actions
    connect(&resetAct, &QAction::triggered, model, &ChannelInfoModel::resetInfos);
    connect(&resetNamesAct, &QAction::triggered, model, &ChannelInfoModel::resetNames);
    connect(&resetColorsAct, &QAction::triggered, model, &ChannelInfoModel::resetColors);
    connect(&resetGainsAct, &QAction::triggered, model, &ChannelInfoModel::resetGains);
    connect(&resetOffsetsAct, &QAction::triggered, model, &ChannelInfoModel::resetOffsets);
    connect(&showAllAct, &QAction::triggered, [model]{model->resetVisibility(true);});
    connect(&hideAllAct, &QAction::triggered, [model]{model->resetVisibility(false);});
}

void PlotControlPanel::saveSettings(QSettings* settings)
{
    settings->beginGroup(SettingGroup_Plot);
    settings->setValue(SG_Plot_NumOfSamples, numOfSamples());
    settings->setValue(SG_Plot_PlotWidth, ui->spPlotWidth->value());
    settings->setValue(SG_Plot_IndexAsX, xAxisAsIndex());
    settings->setValue(SG_Plot_XMax, xMax());
    settings->setValue(SG_Plot_XMin, xMin());
    settings->setValue(SG_Plot_AutoScale, autoScale());
    settings->setValue(SG_Plot_YMax, yMax());
    settings->setValue(SG_Plot_YMin, yMin());
    settings->endGroup();
}

void PlotControlPanel::loadSettings(QSettings* settings)
{
    settings->beginGroup(SettingGroup_Plot);
    ui->spNumOfSamples->setValue(
        settings->value(SG_Plot_NumOfSamples, numOfSamples()).toInt());
    ui->spPlotWidth->setValue(
        settings->value(SG_Plot_PlotWidth, ui->spPlotWidth->value()).toInt());
    ui->cbIndex->setChecked(
        settings->value(SG_Plot_IndexAsX, xAxisAsIndex()).toBool());
    ui->spXmax->setValue(settings->value(SG_Plot_XMax, xMax()).toDouble());
    ui->spXmin->setValue(settings->value(SG_Plot_XMin, xMin()).toDouble());
    ui->cbAutoScale->setChecked(
        settings->value(SG_Plot_AutoScale, autoScale()).toBool());
    ui->spYmax->setValue(settings->value(SG_Plot_YMax, yMax()).toDouble());
    ui->spYmin->setValue(settings->value(SG_Plot_YMin, yMin()).toDouble());
    settings->endGroup();
}