Chris@378: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@378: 
Chris@378: /*
Chris@378:     Sonic Visualiser
Chris@378:     An audio file viewer and annotation editor.
Chris@378:     Centre for Digital Music, Queen Mary, University of London.
Chris@378:     This file copyright 2006 Chris Cannam.
Chris@378:     
Chris@378:     This program is free software; you can redistribute it and/or
Chris@378:     modify it under the terms of the GNU General Public License as
Chris@378:     published by the Free Software Foundation; either version 2 of the
Chris@378:     License, or (at your option) any later version.  See the file
Chris@378:     COPYING included with this distribution for more information.
Chris@378: */
Chris@378: 
Chris@378: #include "CSVFormatDialog.h"
Chris@378: 
Chris@561: #include "layer/LayerFactory.h"
Chris@561: 
Chris@674: #include "TextAbbrev.h"
Chris@674: 
Chris@378: #include <QFrame>
Chris@378: #include <QGridLayout>
Chris@378: #include <QPushButton>
Chris@378: #include <QHBoxLayout>
Chris@378: #include <QVBoxLayout>
Chris@378: #include <QTableWidget>
Chris@378: #include <QComboBox>
Chris@378: #include <QLabel>
Chris@512: #include <QDialogButtonBox>
Chris@378: 
Chris@560: #include <iostream>
Chris@378: 
Chris@682: #include "base/Debug.h"
Chris@682: 
Chris@581: CSVFormatDialog::CSVFormatDialog(QWidget *parent, CSVFormat format,
Chris@581:                                  int maxDisplayCols) :
Chris@378:     QDialog(parent),
Chris@581:     m_format(format),
Chris@581:     m_maxDisplayCols(maxDisplayCols),
Chris@581:     m_fuzzyColumn(-1)
Chris@378: {
Chris@378:     setModal(true);
Chris@378:     setWindowTitle(tr("Select Data Format"));
Chris@378: 
Chris@378:     QGridLayout *layout = new QGridLayout;
Chris@378: 
Chris@560:     int row = 0;
Chris@378: 
Chris@560:     layout->addWidget(new QLabel(tr("Please select the correct data format for this file.")),
Chris@560: 		      row++, 0, 1, 4);
Chris@560: 
Chris@560:     QFrame *exampleFrame = new QFrame;
Chris@560:     exampleFrame->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
Chris@560:     exampleFrame->setLineWidth(2);
Chris@560:     QGridLayout *exampleLayout = new QGridLayout;
Chris@561:     exampleLayout->setSpacing(4);
Chris@560:     exampleFrame->setLayout(exampleLayout);
Chris@560: 
Chris@560:     QPalette palette = exampleFrame->palette();
Chris@560:     palette.setColor(QPalette::Window, palette.color(QPalette::Base));
Chris@560:     exampleFrame->setPalette(palette);
Chris@560: 
Chris@560:     QFont fp;
Chris@561:     fp.setPointSize(fp.pointSize() * 0.9);
Chris@561: //    fp.setFixedPitch(true);
Chris@561: //    fp.setStyleHint(QFont::TypeWriter);
Chris@561: //    fp.setFamily("Monospaced");
Chris@560:     
Chris@560:     int columns = format.getColumnCount();
Chris@560:     QList<QStringList> example = m_format.getExample();
Chris@560: 
Chris@560:     for (int i = 0; i < columns; ++i) {
Chris@581: 
Chris@560:         QComboBox *cpc = new QComboBox;
Chris@560:         m_columnPurposeCombos.push_back(cpc);
Chris@560:         exampleLayout->addWidget(cpc, 0, i);
Chris@581:         connect(cpc, SIGNAL(activated(int)), this, SLOT(columnPurposeChanged(int)));
Chris@581: 
Chris@581:         if (i == m_maxDisplayCols && columns > i + 2) {
Chris@581:             m_fuzzyColumn = i;
Chris@581:             cpc->addItem(tr("<ignore>"));
Chris@581:             cpc->addItem(tr("Values"));
Chris@581:             cpc->setCurrentIndex
Chris@581:                 (m_format.getColumnPurpose(i-1) == CSVFormat::ColumnUnknown ? 0 : 1);
Chris@581:             exampleLayout->addWidget(new QLabel(tr("(%1 more)").arg(columns - i)),
Chris@581:                                      1, i);
Chris@581:             break;
Chris@581:         }
Chris@560: 
Chris@560:         // NB must be in the same order as the CSVFormat::ColumnPurpose enum
Chris@560:         cpc->addItem(tr("<ignore>")); // ColumnUnknown
Chris@560:         cpc->addItem(tr("Time"));     // ColumnStartTime
Chris@560:         cpc->addItem(tr("End time")); // ColumnEndTime
Chris@560:         cpc->addItem(tr("Duration")); // ColumnDuration
Chris@560:         cpc->addItem(tr("Value"));    // ColumnValue
Chris@763:         cpc->addItem(tr("Pitch"));    // ColumnPitch
Chris@560:         cpc->addItem(tr("Label"));    // ColumnLabel
Chris@560:         cpc->setCurrentIndex(int(m_format.getColumnPurpose(i)));
Chris@560: 
Chris@581:         for (int j = 0; j < example.size() && j < 6; ++j) {
Chris@581:             QLabel *label = new QLabel;
Chris@581:             label->setTextFormat(Qt::PlainText);
Chris@674:             QString text = TextAbbrev::abbreviate(example[j][i], 35);
Chris@674:             label->setText(text);
Chris@581:             label->setFont(fp);
Chris@581:             label->setPalette(palette);
Chris@581:             label->setIndent(8);
Chris@581:             exampleLayout->addWidget(label, j+1, i);
Chris@560:         }
Chris@560:     }
Chris@560: 
Chris@560:     layout->addWidget(exampleFrame, row, 0, 1, 4);
Chris@560:     layout->setColumnStretch(3, 10);
Chris@560:     layout->setRowStretch(row++, 10);
Chris@560: 
Chris@560:     layout->addWidget(new QLabel(tr("Timing is specified:")), row, 0);
Chris@378:     
Chris@378:     m_timingTypeCombo = new QComboBox;
Chris@560:     m_timingTypeCombo->addItem(tr("Explicitly, in seconds"));
Chris@560:     m_timingTypeCombo->addItem(tr("Explicitly, in audio sample frames"));
Chris@560:     m_timingTypeCombo->addItem(tr("Implicitly: rows are equally spaced in time"));
Chris@560:     layout->addWidget(m_timingTypeCombo, row++, 1, 1, 2);
Chris@378:     connect(m_timingTypeCombo, SIGNAL(activated(int)),
Chris@378: 	    this, SLOT(timingTypeChanged(int)));
Chris@560:     m_timingTypeCombo->setCurrentIndex
Chris@560:         (m_format.getTimingType() == CSVFormat::ExplicitTiming ?
Chris@560:          m_format.getTimeUnits() == CSVFormat::TimeSeconds ? 0 : 1 : 2);
Chris@559: 
Chris@378:     m_sampleRateLabel = new QLabel(tr("Audio sample rate (Hz):"));
Chris@560:     layout->addWidget(m_sampleRateLabel, row, 0);
Chris@378:     
Chris@378:     size_t sampleRates[] = {
Chris@378: 	8000, 11025, 12000, 22050, 24000, 32000,
Chris@378: 	44100, 48000, 88200, 96000, 176400, 192000
Chris@378:     };
Chris@378: 
Chris@378:     m_sampleRateCombo = new QComboBox;
Chris@378:     for (size_t i = 0; i < sizeof(sampleRates) / sizeof(sampleRates[0]); ++i) {
Chris@378: 	m_sampleRateCombo->addItem(QString("%1").arg(sampleRates[i]));
Chris@560: 	if (sampleRates[i] == m_format.getSampleRate()) {
Chris@560:             m_sampleRateCombo->setCurrentIndex(i);
Chris@560:         }
Chris@378:     }
Chris@378:     m_sampleRateCombo->setEditable(true);
Chris@378: 
Chris@560:     layout->addWidget(m_sampleRateCombo, row++, 1);
Chris@378:     connect(m_sampleRateCombo, SIGNAL(activated(QString)),
Chris@378: 	    this, SLOT(sampleRateChanged(QString)));
Chris@378:     connect(m_sampleRateCombo, SIGNAL(editTextChanged(QString)),
Chris@378: 	    this, SLOT(sampleRateChanged(QString)));
Chris@378: 
Chris@378:     m_windowSizeLabel = new QLabel(tr("Frame increment between rows:"));
Chris@560:     layout->addWidget(m_windowSizeLabel, row, 0);
Chris@378: 
Chris@378:     m_windowSizeCombo = new QComboBox;
Chris@378:     for (int i = 0; i <= 16; ++i) {
Chris@378: 	int value = 1 << i;
Chris@378: 	m_windowSizeCombo->addItem(QString("%1").arg(value));
Chris@560: 	if (value == int(m_format.getWindowSize())) {
Chris@560:             m_windowSizeCombo->setCurrentIndex(i);
Chris@560:         }
Chris@378:     }
Chris@378:     m_windowSizeCombo->setEditable(true);
Chris@378: 
Chris@560:     layout->addWidget(m_windowSizeCombo, row++, 1);
Chris@378:     connect(m_windowSizeCombo, SIGNAL(activated(QString)),
Chris@378: 	    this, SLOT(windowSizeChanged(QString)));
Chris@378:     connect(m_windowSizeCombo, SIGNAL(editTextChanged(QString)),
Chris@378: 	    this, SLOT(windowSizeChanged(QString)));
Chris@378: 
Chris@561:     m_modelLabel = new QLabel;
Chris@561:     QFont f(m_modelLabel->font());
Chris@561:     f.setItalic(true);
Chris@561:     m_modelLabel->setFont(f);
Chris@561:     layout->addWidget(m_modelLabel, row++, 0, 1, 4);
Chris@561: 
Chris@512:     QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok |
Chris@512:                                                 QDialogButtonBox::Cancel);
Chris@560:     layout->addWidget(bb, row++, 0, 1, 4);
Chris@512:     connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
Chris@512:     connect(bb, SIGNAL(rejected()), this, SLOT(reject()));
Chris@378: 
Chris@512:     setLayout(layout);
Chris@378: 
Chris@378:     timingTypeChanged(m_timingTypeCombo->currentIndex());
Chris@561:     updateModelLabel();
Chris@378: }
Chris@378: 
Chris@378: CSVFormatDialog::~CSVFormatDialog()
Chris@378: {
Chris@378: }
Chris@378: 
Chris@378: CSVFormat
Chris@378: CSVFormatDialog::getFormat() const
Chris@378: {
Chris@560:     return m_format;
Chris@378: }
Chris@378: 
Chris@378: void
Chris@561: CSVFormatDialog::updateModelLabel()
Chris@378: {
Chris@561:     LayerFactory *f = LayerFactory::getInstance();
Chris@561: 
Chris@561:     QString s;
Chris@561:     switch (m_format.getModelType()) {
Chris@561:     case CSVFormat::OneDimensionalModel:
Chris@561:         s = f->getLayerPresentationName(LayerFactory::TimeInstants);
Chris@561:         break;
Chris@561:     case CSVFormat::TwoDimensionalModel:
Chris@561:         s = f->getLayerPresentationName(LayerFactory::TimeValues);
Chris@561:         break; 
Chris@561:     case CSVFormat::TwoDimensionalModelWithDuration:
Chris@561:         s = f->getLayerPresentationName(LayerFactory::Regions);
Chris@561:         break;
Chris@763:     case CSVFormat::TwoDimensionalModelWithDurationAndPitch:
Chris@763:         s = f->getLayerPresentationName(LayerFactory::Notes);
Chris@763:         break;
Chris@561:     case CSVFormat::ThreeDimensionalModel:
Chris@561:         s = f->getLayerPresentationName(LayerFactory::Colour3DPlot);
Chris@561:         break;
Chris@561:     }   
Chris@561: 
Chris@578:     m_modelLabel->setText("\n" + tr("Data will be displayed in a %1 layer.").arg(s));
Chris@378: }
Chris@378: 
Chris@378: void
Chris@378: CSVFormatDialog::timingTypeChanged(int type)
Chris@378: {
Chris@378:     switch (type) {
Chris@378: 
Chris@378:     case 0:
Chris@560: 	m_format.setTimingType(CSVFormat::ExplicitTiming);
Chris@560: 	m_format.setTimeUnits(CSVFormat::TimeSeconds);
Chris@378: 	m_sampleRateCombo->setEnabled(false);
Chris@378: 	m_sampleRateLabel->setEnabled(false);
Chris@378: 	m_windowSizeCombo->setEnabled(false);
Chris@378: 	m_windowSizeLabel->setEnabled(false);
Chris@378: 	break;
Chris@378: 
Chris@378:     case 1:
Chris@560: 	m_format.setTimingType(CSVFormat::ExplicitTiming);
Chris@560: 	m_format.setTimeUnits(CSVFormat::TimeAudioFrames);
Chris@378: 	m_sampleRateCombo->setEnabled(true);
Chris@378: 	m_sampleRateLabel->setEnabled(true);
Chris@378: 	m_windowSizeCombo->setEnabled(false);
Chris@378: 	m_windowSizeLabel->setEnabled(false);
Chris@378: 	break;
Chris@378: 
Chris@378:     case 2:
Chris@560: 	m_format.setTimingType(CSVFormat::ImplicitTiming);
Chris@560: 	m_format.setTimeUnits(CSVFormat::TimeWindows);
Chris@378: 	m_sampleRateCombo->setEnabled(true);
Chris@378: 	m_sampleRateLabel->setEnabled(true);
Chris@378: 	m_windowSizeCombo->setEnabled(true);
Chris@378: 	m_windowSizeLabel->setEnabled(true);
Chris@378: 	break;
Chris@378:     }
Chris@559: }
Chris@559: 
Chris@559: void
Chris@378: CSVFormatDialog::sampleRateChanged(QString rateString)
Chris@378: {
Chris@378:     bool ok = false;
Chris@378:     int sampleRate = rateString.toInt(&ok);
Chris@560:     if (ok) m_format.setSampleRate(sampleRate);
Chris@378: }
Chris@378: 
Chris@378: void
Chris@378: CSVFormatDialog::windowSizeChanged(QString sizeString)
Chris@378: {
Chris@378:     bool ok = false;
Chris@378:     int size = sizeString.toInt(&ok);
Chris@560:     if (ok) m_format.setWindowSize(size);
Chris@378: }
Chris@560: 
Chris@560: void
Chris@561: CSVFormatDialog::columnPurposeChanged(int p)
Chris@560: {
Chris@560:     QObject *o = sender();
Chris@561: 
Chris@560:     QComboBox *cb = qobject_cast<QComboBox *>(o);
Chris@560:     if (!cb) return;
Chris@561: 
Chris@561:     CSVFormat::ColumnPurpose purpose = (CSVFormat::ColumnPurpose)p;
Chris@561: 
Chris@561:     bool haveStartTime = false;
Chris@561:     bool haveDuration = false;
Chris@763:     bool havePitch = false;
Chris@561:     int valueCount = 0;
Chris@561: 
Chris@560:     for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
Chris@561: 
Chris@561:         CSVFormat::ColumnPurpose cp = m_format.getColumnPurpose(i);
Chris@561: 
Chris@561:         bool thisChanged = (cb == m_columnPurposeCombos[i]);
Chris@561:         
Chris@561:         if (thisChanged) {
Chris@561: 
Chris@682:             cerr << "i == " << i << ", fuzzy == " << m_fuzzyColumn
Chris@682:                       << ", p == " << p << endl;
Chris@581: 
Chris@581:             if (i == m_fuzzyColumn) {
Chris@581:                 for (int j = i; j < m_format.getColumnCount(); ++j) {
Chris@581:                     if (p == 0) { // Ignore
Chris@581:                         m_format.setColumnPurpose(j, CSVFormat::ColumnUnknown);
Chris@581:                     } else { // Value
Chris@581:                         m_format.setColumnPurpose(j, CSVFormat::ColumnValue);
Chris@581:                         ++valueCount;
Chris@581:                     }
Chris@581:                 }
Chris@581:                 continue;
Chris@581:             }
Chris@581: 
Chris@561:             cp = purpose;
Chris@561: 
Chris@561:         } else {
Chris@561: 
Chris@581:             if (i == m_fuzzyColumn) continue;
Chris@581: 
Chris@561:             // We can only have one ColumnStartTime column, and only
Chris@561:             // one of either ColumnDuration or ColumnEndTime
Chris@561: 
Chris@561:             if (purpose == CSVFormat::ColumnStartTime) {
Chris@561:                 if (cp == purpose) {
Chris@561:                     cp = CSVFormat::ColumnValue;
Chris@561:                 }
Chris@561:             } else if (purpose == CSVFormat::ColumnDuration ||
Chris@561:                        purpose == CSVFormat::ColumnEndTime) {
Chris@561:                 if (cp == CSVFormat::ColumnDuration ||
Chris@561:                     cp == CSVFormat::ColumnEndTime) {
Chris@561:                     cp = CSVFormat::ColumnValue;
Chris@561:                 }
Chris@561:             }
Chris@561: 
Chris@561:             // And we can only have one label
Chris@561:             if (purpose == CSVFormat::ColumnLabel) {
Chris@561:                 if (cp == purpose) {
Chris@561:                     cp = CSVFormat::ColumnUnknown;
Chris@561:                 }
Chris@561:             }
Chris@561:         }
Chris@561: 
Chris@561:         if (cp == CSVFormat::ColumnStartTime) {
Chris@561:             haveStartTime = true;
Chris@561:         }
Chris@561:         if (cp == CSVFormat::ColumnEndTime ||
Chris@561:             cp == CSVFormat::ColumnDuration) {
Chris@561:             haveDuration = true;
Chris@561:         }
Chris@763:         if (cp == CSVFormat::ColumnPitch) {
Chris@763:             havePitch = true;
Chris@763:         }
Chris@561:         if (cp == CSVFormat::ColumnValue) {
Chris@561:             ++valueCount;
Chris@561:         }
Chris@561: 
Chris@561:         m_columnPurposeCombos[i]->setCurrentIndex(int(cp));
Chris@561:         m_format.setColumnPurpose(i, cp);
Chris@561:     }
Chris@561: 
Chris@561:     if (!haveStartTime) {
Chris@561:         m_timingTypeCombo->setCurrentIndex(2);
Chris@561:         timingTypeChanged(2);
Chris@561:     }
Chris@561: 
Chris@561:     if (haveStartTime && haveDuration) {
Chris@763:         if (havePitch) {
Chris@763:             m_format.setModelType(CSVFormat::TwoDimensionalModelWithDurationAndPitch);
Chris@763:         } else {
Chris@763:             m_format.setModelType(CSVFormat::TwoDimensionalModelWithDuration);
Chris@763:         }
Chris@561:     } else {
Chris@561:         if (valueCount > 1) {
Chris@561:             m_format.setModelType(CSVFormat::ThreeDimensionalModel);
Chris@561:         } else if (valueCount > 0) {
Chris@561:             m_format.setModelType(CSVFormat::TwoDimensionalModel);
Chris@561:         } else {
Chris@561:             m_format.setModelType(CSVFormat::OneDimensionalModel);
Chris@560:         }
Chris@560:     }
Chris@561: 
Chris@561:     updateModelLabel();
Chris@560: }
Chris@560: 
Chris@560: