changeset 1569:b33b0b06133e

Merge from branch csv-export-dialog
author Chris Cannam
date Tue, 14 Jan 2020 15:48:44 +0000
parents 77ffd5421627 (current diff) 3943553b95b0 (diff)
children 9095fbec4e52
files layer/Colour3DPlotExporter.cpp
diffstat 5 files changed, 376 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/files.pri	Tue Jan 14 13:19:18 2020 +0000
+++ b/files.pri	Tue Jan 14 15:48:44 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 \
--- a/layer/Colour3DPlotExporter.cpp	Tue Jan 14 13:19:18 2020 +0000
+++ b/layer/Colour3DPlotExporter.cpp	Tue Jan 14 15:48:44 2020 +0000
@@ -47,7 +47,7 @@
 
 QString
 Colour3DPlotExporter::getDelimitedDataHeaderLine(QString delimiter,
-                                                 DataExportOptions) const
+                                                 DataExportOptions opts) const
 {
     auto model =
         ModelById::getAs<DenseThreeDimensionalModel>(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) {
--- a/layer/Colour3DPlotExporter.h	Tue Jan 14 13:19:18 2020 +0000
+++ b/layer/Colour3DPlotExporter.h	Tue Jan 14 15:48:44 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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/CSVExportDialog.cpp	Tue Jan 14 15:48:44 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 <QVBoxLayout>
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QLabel>
+#include <QDialogButtonBox>
+#include <QRadioButton>
+#include <QButtonGroup>
+#include <QCheckBox>
+#include <QComboBox>
+
+#include <vector>
+
+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<pair<QString, QChar>> 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();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/CSVExportDialog.h	Tue Jan 14 15:48:44 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 <QDialog>
+#include <QString>
+
+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