# HG changeset patch # User Chris Cannam # Date 1579016477 0 # Node ID 3943553b95b0703456a72d7897ee7580dae4b47e # Parent 1f80a514ce296c7d89f3faa9806a28a4fbee1097 Add CSV export dialog, + associated supporting changes diff -r 1f80a514ce29 -r 3943553b95b0 files.pri --- a/files.pri Fri Jan 10 14:54:27 2020 +0000 +++ b/files.pri Tue Jan 14 15:41:17 2020 +0000 @@ -53,6 +53,7 @@ widgets/ColourNameDialog.h \ widgets/CommandHistory.h \ widgets/CSVAudioFormatDialog.h \ + widgets/CSVExportDialog.h \ widgets/CSVFormatDialog.h \ widgets/Fader.h \ widgets/InteractiveFileFinder.h \ @@ -141,6 +142,7 @@ widgets/ColourNameDialog.cpp \ widgets/CommandHistory.cpp \ widgets/CSVAudioFormatDialog.cpp \ + widgets/CSVExportDialog.cpp \ widgets/CSVFormatDialog.cpp \ widgets/Fader.cpp \ widgets/InteractiveFileFinder.cpp \ diff -r 1f80a514ce29 -r 3943553b95b0 layer/Colour3DPlotExporter.cpp --- a/layer/Colour3DPlotExporter.cpp Fri Jan 10 14:54:27 2020 +0000 +++ b/layer/Colour3DPlotExporter.cpp Tue Jan 14 15:41:17 2020 +0000 @@ -47,7 +47,7 @@ QString Colour3DPlotExporter::getDelimitedDataHeaderLine(QString delimiter, - DataExportOptions) const + DataExportOptions opts) const { auto model = ModelById::getAs(m_sources.source); @@ -76,15 +76,12 @@ QStringList list; - switch (m_params.timestampFormat) { - case TimestampFormat::None: - break; - case TimestampFormat::Frames: - list << "FRAME"; - break; - case TimestampFormat::Seconds: - list << "TIME"; - break; + if (opts & DataExportAlwaysIncludeTimestamp) { + if (opts & DataExportWriteTimeInFrames) { + list << "FRAME"; + } else { + list << "TIME"; + } } if (m_params.binDisplay == BinDisplay::PeakFrequencies) { @@ -123,7 +120,7 @@ QString Colour3DPlotExporter::toDelimitedDataString(QString delimiter, - DataExportOptions, + DataExportOptions opts, sv_frame_t startFrame, sv_frame_t duration) const { @@ -184,16 +181,13 @@ QStringList list; - switch (m_params.timestampFormat) { - case TimestampFormat::None: - break; - case TimestampFormat::Frames: - list << QString("%1").arg(fr); - break; - case TimestampFormat::Seconds: - list << RealTime::frame2RealTime(fr, model->getSampleRate()) - .toString().c_str(); - break; + if (opts & DataExportAlwaysIncludeTimestamp) { + if (opts & DataExportWriteTimeInFrames) { + list << QString("%1").arg(fr); + } else { + list << RealTime::frame2RealTime(fr, model->getSampleRate()) + .toString().c_str(); + } } if (binDisplay == BinDisplay::PeakFrequencies) { diff -r 1f80a514ce29 -r 3943553b95b0 layer/Colour3DPlotExporter.h --- a/layer/Colour3DPlotExporter.h Fri Jan 10 14:54:27 2020 +0000 +++ b/layer/Colour3DPlotExporter.h Tue Jan 14 15:41:17 2020 +0000 @@ -31,20 +31,13 @@ const LayerGeometryProvider *provider; // optionally }; - enum class TimestampFormat { - None, - Seconds, - Frames - }; - struct Parameters { Parameters() : binDisplay(BinDisplay::AllBins), scaleFactor(1.0), threshold(0.0), gain(1.0), - normalization(ColumnNormalization::None), - timestampFormat(TimestampFormat::None) { } + normalization(ColumnNormalization::None) { } /** Selection of bins to include in the export. */ BinDisplay binDisplay; @@ -70,10 +63,6 @@ * calculate thresholding level. The exported values are * un-normalized. */ ColumnNormalization normalization; - - /** Format to use for the timestamp column. If None, no - * timestamp column will be included. */ - TimestampFormat timestampFormat; }; Colour3DPlotExporter(Sources sources, Parameters parameters); diff -r 1f80a514ce29 -r 3943553b95b0 widgets/CSVExportDialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/CSVExportDialog.cpp Tue Jan 14 15:41:17 2020 +0000 @@ -0,0 +1,233 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "CSVExportDialog.h" + +#include "view/ViewManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +//!!! todo: remember & re-apply last set of options chosen for this layer type + +CSVExportDialog::CSVExportDialog(Configuration config, QWidget *parent) : + QDialog(parent), + m_config(config) +{ + setWindowTitle(tr("Export Layer")); + + QString intro = tr("Exporting layer \"%1\" to %2 file.") + .arg(config.layerName) + .arg(config.fileExtension.toUpper()); + + QVBoxLayout *vbox = new QVBoxLayout; + + QLabel *label = new QLabel(intro); + vbox->addWidget(label); + + int space = ViewManager::scalePixelSize(2); + + vbox->addSpacing(space); + + QGroupBox *rowColGroup = new QGroupBox(tr("Row and column options:")); + QGridLayout *rowColLayout = new QGridLayout; + rowColGroup->setLayout(rowColLayout); + + vector> separators { + { tr("Comma"), ',' }, + { tr("Tab"), '\t' }, + { tr("Space"), ' ' }, + { tr("Pipe"), '|' }, + { tr("Slash"), '/' }, + { tr("Colon"), ':' } + }; + + QChar defaultSeparator = ','; + if (m_config.fileExtension != "csv") { + defaultSeparator = '\t'; + } + + rowColLayout->addWidget(new QLabel(tr("Column separator:"), 0, 0)); + m_separatorCombo = new QComboBox; + for (auto p: separators) { + if (p.second == '\t' || p.second == ' ') { + m_separatorCombo->addItem(p.first, p.second); + } else { + m_separatorCombo->addItem(tr("%1 '%2'").arg(p.first).arg(p.second), + p.second); + } + if (p.second == defaultSeparator) { + m_separatorCombo->setCurrentIndex(m_separatorCombo->count()-1); + } + } + m_separatorCombo->setEditable(false); + rowColLayout->addWidget(m_separatorCombo, 0, 1); + rowColLayout->setColumnStretch(2, 10); + + m_header = new QCheckBox + (tr("Include a header row before the data rows")); + m_timestamps = new QCheckBox + (tr("Include a timestamp column before the data columns")); + rowColLayout->addWidget(m_header, 1, 0, 1, 3); + rowColLayout->addWidget(m_timestamps, 2, 0, 1, 3); + + if (!m_config.isDense) { + m_timestamps->setChecked(true); + m_timestamps->setEnabled(false); + } + + vbox->addWidget(rowColGroup); + + vbox->addSpacing(space); + + QGroupBox *framesGroup = new QGroupBox + (tr("Timing format:")); + + m_seconds = new QRadioButton + (tr("Write times in seconds")); + m_frames = new QRadioButton + (tr("Write times in audio sample frames")); + m_seconds->setChecked(true); + + QVBoxLayout *framesLayout = new QVBoxLayout; + framesLayout->addWidget(m_seconds); + framesLayout->addWidget(m_frames); + framesGroup->setLayout(framesLayout); + vbox->addWidget(framesGroup); + + vbox->addSpacing(space); + + if (m_config.isDense) { + m_seconds->setEnabled(false); + m_frames->setEnabled(false); + } + + QGroupBox *rangeGroup = new QGroupBox + (tr("Range to export:")); + + QButtonGroup *selectionGroup = new QButtonGroup(rangeGroup); + QButtonGroup *viewGroup = new QButtonGroup(rangeGroup); + + m_selectionOnly = new QRadioButton + (tr("Export only the current selection")); + QRadioButton *fullDuration = new QRadioButton + (tr("Export the full duration of the model")); + + selectionGroup->addButton(m_selectionOnly); + selectionGroup->addButton(fullDuration); + + if (m_config.haveSelection) { + m_selectionOnly->setChecked(true); + } else { + m_selectionOnly->setEnabled(false); + fullDuration->setEnabled(false); + fullDuration->setChecked(true); + } + + QVBoxLayout *rangeLayout = new QVBoxLayout; + rangeLayout->addWidget(m_selectionOnly); + rangeLayout->addWidget(fullDuration); + + if (m_config.haveView && m_config.isDense) { + + m_viewOnly = new QRadioButton + (tr("Export only the height of the visible view")); + QRadioButton *fullHeight = new QRadioButton + (tr("Export the full height of the model")); + + viewGroup->addButton(m_viewOnly); + viewGroup->addButton(fullHeight); + + m_viewOnly->setChecked(true); + + rangeLayout->addSpacing(space); + + rangeLayout->addWidget(m_viewOnly); + rangeLayout->addWidget(fullHeight); + + } else { + m_viewOnly = nullptr; + } + + rangeGroup->setLayout(rangeLayout); + vbox->addWidget(rangeGroup); + + vbox->addSpacing(space); + + QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel); + vbox->addWidget(bb); + connect(bb, SIGNAL(accepted()), this, SLOT(accept())); + connect(bb, SIGNAL(rejected()), this, SLOT(reject())); + + connect(m_timestamps, SIGNAL(toggled(bool)), + this, SLOT(timestampsToggled(bool))); + + setLayout(vbox); +} + +void +CSVExportDialog::timestampsToggled(bool on) +{ + m_seconds->setEnabled(on); + m_frames->setEnabled(on); +} + +QString +CSVExportDialog::getDelimiter() const +{ + return m_separatorCombo->currentData().toChar(); +} + +bool +CSVExportDialog::shouldIncludeHeader() const +{ + return m_header && m_header->isChecked(); +} + +bool +CSVExportDialog::shouldIncludeTimestamps() const +{ + return m_timestamps && m_timestamps->isChecked(); +} + +bool +CSVExportDialog::shouldWriteTimeInFrames() const +{ + return shouldIncludeTimestamps() && m_frames && m_frames->isChecked(); +} + +bool +CSVExportDialog::shouldConstrainToViewHeight() const +{ + return m_viewOnly && m_viewOnly->isChecked(); +} + +bool +CSVExportDialog::shouldConstrainToSelection() const +{ + return m_selectionOnly && m_selectionOnly->isChecked(); +} + diff -r 1f80a514ce29 -r 3943553b95b0 widgets/CSVExportDialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/CSVExportDialog.h Tue Jan 14 15:41:17 2020 +0000 @@ -0,0 +1,125 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_CSV_EXPORT_DIALOG_H +#define SV_CSV_EXPORT_DIALOG_H + +#include +#include + +class QComboBox; +class QCheckBox; +class QRadioButton; + +class CSVExportDialog : public QDialog +{ + Q_OBJECT + +public: + struct Configuration { + Configuration() : + layerName(""), + fileExtension("csv"), + isDense(false), + haveView(false), + haveSelection(false) { } + + /** + * Presentation name of the layer being exported. + */ + QString layerName; + + /** + * Extension of file being exported into. + */ + QString fileExtension; + + /** + * True if the model is a dense type for which timestamps are + * not written by default. + */ + bool isDense; + + /** + * True if we have a view that provides a vertical scale + * range, so we may want to offer a choice between exporting + * only the visible range or exporting full height. This + * choice happens to be offered only if isDense is also true. + */ + bool haveView; + + /** + * True if there is a selection current that the user may want + * to constrain export to. + */ + bool haveSelection; + }; + + CSVExportDialog(Configuration config, QWidget *parent); + + /** + * Return the column delimiter to use in the exported file. Either + * the default for the supplied file extension, or some other + * option chosen by the user. + */ + QString getDelimiter() const; + + /** + * Return true if we should include a header row at the top of the + * exported file. + */ + bool shouldIncludeHeader() const; + + /** + * Return true if we should write a timestamp column. This is + * always true for non-dense models, but is a user option for + * dense ones. + */ + bool shouldIncludeTimestamps() const; + + /** + * Return true if we should use sample frames rather than seconds + * for the timestamp column (and duration where present). + */ + bool shouldWriteTimeInFrames() const; + + /** + * Return true if we should constrain the vertical range to the + * visible area only. Otherwise we should export the full vertical + * range of the model. + */ + bool shouldConstrainToViewHeight() const; + + /** + * Return true if we should export the selected time range(s) + * only. Otherwise we should export the full length of the model. + */ + bool shouldConstrainToSelection() const; + +private: + Configuration m_config; + + QComboBox *m_separatorCombo; + QCheckBox *m_header; + QCheckBox *m_timestamps; + QRadioButton *m_seconds; + QRadioButton *m_frames; + QRadioButton *m_selectionOnly; + QRadioButton *m_viewOnly; + +private slots: + void timestampsToggled(bool); +}; + +#endif