comparison widgets/CSVFormatDialog.cpp @ 1319:fbda05431ce0 import-audio-data

Refactor; the two use-cases of CSVFormatDialog were turning out to be different enough to justify two separate classes
author Chris Cannam
date Thu, 06 Sep 2018 13:58:09 +0100
parents b149b53df365
children 0930a27ebea2
comparison
equal deleted inserted replaced
1318:b149b53df365 1319:fbda05431ce0
2 2
3 /* 3 /*
4 Sonic Visualiser 4 Sonic Visualiser
5 An audio file viewer and annotation editor. 5 An audio file viewer and annotation editor.
6 Centre for Digital Music, Queen Mary, University of London. 6 Centre for Digital Music, Queen Mary, University of London.
7 This file copyright 2006 Chris Cannam. 7 This file copyright 2006-2018 Chris Cannam and QMUL.
8 8
9 This program is free software; you can redistribute it and/or 9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as 10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the 11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file 12 License, or (at your option) any later version. See the file
35 #include "base/Debug.h" 35 #include "base/Debug.h"
36 36
37 CSVFormatDialog::CSVFormatDialog(QWidget *parent, CSVFormat format, 37 CSVFormatDialog::CSVFormatDialog(QWidget *parent, CSVFormat format,
38 int maxDisplayCols) : 38 int maxDisplayCols) :
39 QDialog(parent), 39 QDialog(parent),
40 m_dialogType(format.getModelType() == CSVFormat::WaveFileModel ?
41 AudioDataDialog :
42 AnnotationDataDialog),
43 m_format(format), 40 m_format(format),
44 m_maxDisplayCols(maxDisplayCols), 41 m_maxDisplayCols(maxDisplayCols),
45 m_fuzzyColumn(-1) 42 m_fuzzyColumn(-1)
46 { 43 {
47 setModal(true); 44 setModal(true);
80 connect(cpc, SIGNAL(activated(int)), this, SLOT(columnPurposeChanged(int))); 77 connect(cpc, SIGNAL(activated(int)), this, SLOT(columnPurposeChanged(int)));
81 78
82 if (i == m_maxDisplayCols && columns > i + 2) { 79 if (i == m_maxDisplayCols && columns > i + 2) {
83 m_fuzzyColumn = i; 80 m_fuzzyColumn = i;
84 81
85 if (m_dialogType == AnnotationDataDialog) { 82 cpc->addItem(tr("<ignore>"));
86 cpc->addItem(tr("<ignore>")); 83 cpc->addItem(tr("Values"));
87 cpc->addItem(tr("Values")); 84 cpc->setCurrentIndex
88 cpc->setCurrentIndex 85 (m_format.getColumnPurpose(i-1) ==
89 (m_format.getColumnPurpose(i-1) == 86 CSVFormat::ColumnUnknown ? 0 : 1);
90 CSVFormat::ColumnUnknown ? 0 : 1); 87
91 } else {
92 cpc->addItem(tr("<ignore>"));
93 cpc->addItem(tr("Audio channels"));
94 cpc->setCurrentIndex
95 (m_format.isColumnNumeric(i-1) ? 1 : 0);
96 }
97 exampleLayout->addWidget 88 exampleLayout->addWidget
98 (new QLabel(tr("(%1 more)").arg(columns - i)), 1, i); 89 (new QLabel(tr("(%1 more)").arg(columns - i)), 1, i);
99 break; 90 break;
100 } 91 }
101 92
102 if (m_dialogType == AnnotationDataDialog) { 93 // NB must be in the same order as the CSVFormat::ColumnPurpose enum
103 94 cpc->addItem(tr("<ignore>")); // ColumnUnknown
104 // NB must be in the same order as the CSVFormat::ColumnPurpose enum 95 cpc->addItem(tr("Time")); // ColumnStartTime
105 cpc->addItem(tr("<ignore>")); // ColumnUnknown 96 cpc->addItem(tr("End time")); // ColumnEndTime
106 cpc->addItem(tr("Time")); // ColumnStartTime 97 cpc->addItem(tr("Duration")); // ColumnDuration
107 cpc->addItem(tr("End time")); // ColumnEndTime 98 cpc->addItem(tr("Value")); // ColumnValue
108 cpc->addItem(tr("Duration")); // ColumnDuration 99 cpc->addItem(tr("Pitch")); // ColumnPitch
109 cpc->addItem(tr("Value")); // ColumnValue 100 cpc->addItem(tr("Label")); // ColumnLabel
110 cpc->addItem(tr("Pitch")); // ColumnPitch 101 cpc->setCurrentIndex(int(m_format.getColumnPurpose(i)));
111 cpc->addItem(tr("Label")); // ColumnLabel
112 cpc->setCurrentIndex(int(m_format.getColumnPurpose(i)));
113
114 } else {
115 cpc->addItem(tr("<ignore>"));
116 cpc->addItem(tr("Audio channel"));
117 if (m_format.isColumnNumeric(i)) {
118 cpc->setCurrentIndex(1);
119 } else {
120 cpc->setCurrentIndex(0);
121 }
122 }
123 102
124 for (int j = 0; j < example.size() && j < 6; ++j) { 103 for (int j = 0; j < example.size() && j < 6; ++j) {
125 if (i >= example[j].size()) { 104 if (i >= example[j].size()) {
126 continue; 105 continue;
127 } 106 }
138 117
139 layout->addWidget(exampleFrame, row, 0, 1, 4); 118 layout->addWidget(exampleFrame, row, 0, 1, 4);
140 layout->setColumnStretch(3, 10); 119 layout->setColumnStretch(3, 10);
141 layout->setRowStretch(row++, 10); 120 layout->setRowStretch(row++, 10);
142 121
143 if (m_dialogType == AnnotationDataDialog) { 122 layout->addWidget(new QLabel(tr("Timing is specified:")), row, 0);
144 123
145 layout->addWidget(new QLabel(tr("Timing is specified:")), row, 0); 124 m_timingTypeCombo = new QComboBox;
146 125
147 m_timingTypeCombo = new QComboBox; 126 m_timingLabels = {
148 127 { TimingExplicitSeconds, tr("Explicitly, in seconds") },
149 m_timingLabels = { 128 { TimingExplicitMsec, tr("Explicitly, in milliseconds") },
150 { TimingExplicitSeconds, tr("Explicitly, in seconds") }, 129 { TimingExplicitSamples, tr("Explicitly, in audio sample frames") },
151 { TimingExplicitMsec, tr("Explicitly, in milliseconds") }, 130 { TimingImplicit, tr("Implicitly: rows are equally spaced in time") }
152 { TimingExplicitSamples, tr("Explicitly, in audio sample frames") }, 131 };
153 { TimingImplicit, tr("Implicitly: rows are equally spaced in time") } 132
154 }; 133 for (auto &l: m_timingLabels) {
155 134 m_timingTypeCombo->addItem(l.second);
156 for (auto &l: m_timingLabels) { 135 }
157 m_timingTypeCombo->addItem(l.second); 136
158 } 137 layout->addWidget(m_timingTypeCombo, row++, 1, 1, 2);
159 138
160 layout->addWidget(m_timingTypeCombo, row++, 1, 1, 2); 139 connect(m_timingTypeCombo, SIGNAL(activated(int)),
161 140 this, SLOT(timingTypeChanged(int)));
162 connect(m_timingTypeCombo, SIGNAL(activated(int)), 141
163 this, SLOT(timingTypeChanged(int))); 142 m_initialTimingOption = TimingImplicit;
164 143 if (m_format.getTimingType() == CSVFormat::ExplicitTiming) {
165 m_initialTimingOption = TimingImplicit; 144 switch (m_format.getTimeUnits()) {
166 if (m_format.getTimingType() == CSVFormat::ExplicitTiming) { 145 case CSVFormat::TimeSeconds:
167 switch (m_format.getTimeUnits()) { 146 m_initialTimingOption = TimingExplicitSeconds; break;
168 case CSVFormat::TimeSeconds: 147 case CSVFormat::TimeMilliseconds:
169 m_initialTimingOption = TimingExplicitSeconds; break; 148 m_initialTimingOption = TimingExplicitMsec; break;
170 case CSVFormat::TimeMilliseconds: 149 case CSVFormat::TimeAudioFrames:
171 m_initialTimingOption = TimingExplicitMsec; break; 150 m_initialTimingOption = TimingExplicitSamples; break;
172 case CSVFormat::TimeAudioFrames: 151 case CSVFormat::TimeWindows:
173 m_initialTimingOption = TimingExplicitSamples; break; 152 m_initialTimingOption = TimingImplicit; break;
174 case CSVFormat::TimeWindows: 153 }
175 m_initialTimingOption = TimingImplicit; break; 154 }
176 } 155 m_timingTypeCombo->setCurrentIndex(int(m_initialTimingOption));
177 }
178 m_timingTypeCombo->setCurrentIndex(int(m_initialTimingOption));
179
180 } else {
181
182 m_timingTypeCombo = 0;
183 }
184 156
185 m_sampleRateLabel = new QLabel(tr("Audio sample rate (Hz):")); 157 m_sampleRateLabel = new QLabel(tr("Audio sample rate (Hz):"));
186 layout->addWidget(m_sampleRateLabel, row, 0); 158 layout->addWidget(m_sampleRateLabel, row, 0);
187 159
188 int sampleRates[] = { 160 int sampleRates[] = {
203 connect(m_sampleRateCombo, SIGNAL(activated(QString)), 175 connect(m_sampleRateCombo, SIGNAL(activated(QString)),
204 this, SLOT(sampleRateChanged(QString))); 176 this, SLOT(sampleRateChanged(QString)));
205 connect(m_sampleRateCombo, SIGNAL(editTextChanged(QString)), 177 connect(m_sampleRateCombo, SIGNAL(editTextChanged(QString)),
206 this, SLOT(sampleRateChanged(QString))); 178 this, SLOT(sampleRateChanged(QString)));
207 179
208 if (m_dialogType == AnnotationDataDialog) { 180 m_windowSizeLabel = new QLabel(tr("Frame increment between rows:"));
209 181 layout->addWidget(m_windowSizeLabel, row, 0);
210 m_windowSizeLabel = new QLabel(tr("Frame increment between rows:")); 182
211 layout->addWidget(m_windowSizeLabel, row, 0); 183 m_windowSizeCombo = new QComboBox;
212 184 for (int i = 0; i <= 16; ++i) {
213 m_windowSizeCombo = new QComboBox; 185 int value = 1 << i;
214 for (int i = 0; i <= 16; ++i) { 186 m_windowSizeCombo->addItem(QString("%1").arg(value));
215 int value = 1 << i; 187 if (value == int(m_format.getWindowSize())) {
216 m_windowSizeCombo->addItem(QString("%1").arg(value)); 188 m_windowSizeCombo->setCurrentIndex(i);
217 if (value == int(m_format.getWindowSize())) { 189 }
218 m_windowSizeCombo->setCurrentIndex(i); 190 }
219 } 191 m_windowSizeCombo->setEditable(true);
220 } 192
221 m_windowSizeCombo->setEditable(true); 193 layout->addWidget(m_windowSizeCombo, row++, 1);
222 194 connect(m_windowSizeCombo, SIGNAL(activated(QString)),
223 layout->addWidget(m_windowSizeCombo, row++, 1); 195 this, SLOT(windowSizeChanged(QString)));
224 connect(m_windowSizeCombo, SIGNAL(activated(QString)), 196 connect(m_windowSizeCombo, SIGNAL(editTextChanged(QString)),
225 this, SLOT(windowSizeChanged(QString))); 197 this, SLOT(windowSizeChanged(QString)));
226 connect(m_windowSizeCombo, SIGNAL(editTextChanged(QString)), 198
227 this, SLOT(windowSizeChanged(QString))); 199 m_modelLabel = new QLabel;
228 200 QFont f(m_modelLabel->font());
229 m_modelLabel = new QLabel; 201 f.setItalic(true);
230 QFont f(m_modelLabel->font()); 202 m_modelLabel->setFont(f);
231 f.setItalic(true); 203 layout->addWidget(m_modelLabel, row++, 0, 1, 4);
232 m_modelLabel->setFont(f);
233 layout->addWidget(m_modelLabel, row++, 0, 1, 4);
234
235 } else {
236 m_windowSizeLabel = 0;
237 m_windowSizeCombo = 0;
238 m_modelLabel = 0;
239 }
240 204
241 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok | 205 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok |
242 QDialogButtonBox::Cancel); 206 QDialogButtonBox::Cancel);
243 layout->addWidget(bb, row++, 0, 1, 4); 207 layout->addWidget(bb, row++, 0, 1, 4);
244 connect(bb, SIGNAL(accepted()), this, SLOT(accept())); 208 connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
245 connect(bb, SIGNAL(rejected()), this, SLOT(reject())); 209 connect(bb, SIGNAL(rejected()), this, SLOT(reject()));
246 210
247 setLayout(layout); 211 setLayout(layout);
248 212
249 if (m_timingTypeCombo) { 213 timingTypeChanged(m_timingTypeCombo->currentIndex());
250 timingTypeChanged(m_timingTypeCombo->currentIndex());
251 } else {
252 updateFormatFromDialog();
253 updateComboVisibility();
254 }
255 } 214 }
256 215
257 CSVFormatDialog::~CSVFormatDialog() 216 CSVFormatDialog::~CSVFormatDialog()
258 { 217 {
259 } 218 }
300 } 259 }
301 260
302 void 261 void
303 CSVFormatDialog::applyStartTimePurpose() 262 CSVFormatDialog::applyStartTimePurpose()
304 { 263 {
305 if (m_dialogType == AudioDataDialog) {
306 return;
307 }
308
309 // First check if we already have any. NB there may be fewer than 264 // First check if we already have any. NB there may be fewer than
310 // m_format.getColumnCount() elements in m_columnPurposeCombos 265 // m_format.getColumnCount() elements in m_columnPurposeCombos
311 // (because of the fuzzy column behaviour). Note also that the 266 // (because of the fuzzy column behaviour). Note also that the
312 // fuzzy column (which is the one just showing how many more 267 // fuzzy column (which is the one just showing how many more
313 // columns there are) has a different combo with only two items 268 // columns there are) has a different combo with only two items
331 } 286 }
332 287
333 void 288 void
334 CSVFormatDialog::removeStartTimePurpose() 289 CSVFormatDialog::removeStartTimePurpose()
335 { 290 {
336 if (m_dialogType == AudioDataDialog) {
337 return;
338 }
339
340 // NB there may be fewer than m_format.getColumnCount() elements 291 // NB there may be fewer than m_format.getColumnCount() elements
341 // in m_columnPurposeCombos (because of the fuzzy column 292 // in m_columnPurposeCombos (because of the fuzzy column
342 // behaviour) 293 // behaviour)
343 for (int i = 0; i < m_columnPurposeCombos.size(); ++i) { 294 for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
344 if (i == m_fuzzyColumn) continue; 295 if (i == m_fuzzyColumn) continue;
350 } 301 }
351 302
352 void 303 void
353 CSVFormatDialog::updateComboVisibility() 304 CSVFormatDialog::updateComboVisibility()
354 { 305 {
355 bool wantRate = (m_dialogType == AudioDataDialog || 306 bool wantRate = (m_format.getTimingType() == CSVFormat::ImplicitTiming ||
356 m_format.getTimingType() == CSVFormat::ImplicitTiming ||
357 m_format.getTimeUnits() == CSVFormat::TimeAudioFrames); 307 m_format.getTimeUnits() == CSVFormat::TimeAudioFrames);
358 bool wantWindow = (m_format.getTimingType() == CSVFormat::ImplicitTiming); 308 bool wantWindow = (m_format.getTimingType() == CSVFormat::ImplicitTiming);
359 309
360 m_sampleRateCombo->setEnabled(wantRate); 310 m_sampleRateCombo->setEnabled(wantRate);
361 m_sampleRateLabel->setEnabled(wantRate); 311 m_sampleRateLabel->setEnabled(wantRate);
362 312
363 if (m_windowSizeCombo) { 313 m_windowSizeCombo->setEnabled(wantWindow);
364 m_windowSizeCombo->setEnabled(wantWindow); 314 m_windowSizeLabel->setEnabled(wantWindow);
365 m_windowSizeLabel->setEnabled(wantWindow);
366 }
367 } 315 }
368 316
369 void 317 void
370 CSVFormatDialog::timingTypeChanged(int type) 318 CSVFormatDialog::timingTypeChanged(int type)
371 { 319 {
400 { 348 {
401 QObject *o = sender(); 349 QObject *o = sender();
402 QComboBox *cb = qobject_cast<QComboBox *>(o); 350 QComboBox *cb = qobject_cast<QComboBox *>(o);
403 if (!cb) return; 351 if (!cb) return;
404 352
405 if (m_dialogType == AnnotationDataDialog) { 353 // Ensure a consistent set of column purposes, in case of a
406 columnPurposeChangedForAnnotationType(cb, p); 354 // situation where some combinations are contradictory. Only
407 } 355 // updates the UI, does not update the stored format record from
408 356 // the UI - that's the job of updateFormatFromDialog
409 updateFormatFromDialog();
410 updateComboVisibility();
411 }
412
413 void
414 CSVFormatDialog::columnPurposeChangedForAnnotationType(QComboBox *cb, int p)
415 {
416 // Ensure a consistent set of column purposes, in a situation
417 // where some combinations are contradictory. This is only
418 // relevant to annotation type formats. Only updates the UI, does
419 // not update the stored format record from the UI - that's the
420 // job of updateFormatFromDialog
421 357
422 CSVFormat::ColumnPurpose purpose = (CSVFormat::ColumnPurpose)p; 358 CSVFormat::ColumnPurpose purpose = (CSVFormat::ColumnPurpose)p;
423 359
424 bool haveStartTime = false; // so as to update timing type combo appropriately 360 bool haveStartTime = false; // so as to update timing type combo appropriately
425 361
475 haveStartTime = true; 411 haveStartTime = true;
476 } 412 }
477 } 413 }
478 } 414 }
479 415
480 if (m_timingTypeCombo) { 416 if (!haveStartTime) {
481 if (!haveStartTime) { 417 m_timingTypeCombo->setCurrentIndex(int(TimingImplicit));
482 m_timingTypeCombo->setCurrentIndex(int(TimingImplicit)); 418 } else if (m_timingTypeCombo->currentIndex() == int(TimingImplicit)) {
483 } else if (m_timingTypeCombo->currentIndex() == int(TimingImplicit)) { 419 if (m_initialTimingOption == TimingImplicit) {
484 if (m_initialTimingOption == TimingImplicit) { 420 m_timingTypeCombo->setCurrentIndex(TimingExplicitSeconds);
485 m_timingTypeCombo->setCurrentIndex(TimingExplicitSeconds); 421 } else {
486 } else { 422 m_timingTypeCombo->setCurrentIndex(m_initialTimingOption);
487 m_timingTypeCombo->setCurrentIndex(m_initialTimingOption); 423 }
488 } 424 }
489 } 425
490 } 426 updateFormatFromDialog();
427 updateComboVisibility();
491 } 428 }
492 429
493 void 430 void
494 CSVFormatDialog::updateFormatFromDialog() 431 CSVFormatDialog::updateFormatFromDialog()
495 { 432 {
496 if (m_timingTypeCombo) { 433 switch (TimingOption(m_timingTypeCombo->currentIndex())) {
497 switch (TimingOption(m_timingTypeCombo->currentIndex())) { 434
498 435 case TimingExplicitSeconds:
499 case TimingExplicitSeconds: 436 m_format.setTimingType(CSVFormat::ExplicitTiming);
500 m_format.setTimingType(CSVFormat::ExplicitTiming); 437 m_format.setTimeUnits(CSVFormat::TimeSeconds);
501 m_format.setTimeUnits(CSVFormat::TimeSeconds); 438 break;
502 break; 439
503 440 case TimingExplicitMsec:
504 case TimingExplicitMsec: 441 m_format.setTimingType(CSVFormat::ExplicitTiming);
505 m_format.setTimingType(CSVFormat::ExplicitTiming); 442 m_format.setTimeUnits(CSVFormat::TimeMilliseconds);
506 m_format.setTimeUnits(CSVFormat::TimeMilliseconds); 443 break;
507 break; 444
508 445 case TimingExplicitSamples:
509 case TimingExplicitSamples: 446 m_format.setTimingType(CSVFormat::ExplicitTiming);
510 m_format.setTimingType(CSVFormat::ExplicitTiming); 447 m_format.setTimeUnits(CSVFormat::TimeAudioFrames);
511 m_format.setTimeUnits(CSVFormat::TimeAudioFrames); 448 break;
512 break; 449
513 450 case TimingImplicit:
514 case TimingImplicit:
515 m_format.setTimingType(CSVFormat::ImplicitTiming);
516 m_format.setTimeUnits(CSVFormat::TimeWindows);
517 break;
518 }
519 } else if (m_dialogType == AudioDataDialog) {
520 m_format.setTimingType(CSVFormat::ImplicitTiming); 451 m_format.setTimingType(CSVFormat::ImplicitTiming);
521 m_format.setTimeUnits(CSVFormat::TimeAudioFrames); 452 m_format.setTimeUnits(CSVFormat::TimeWindows);
453 break;
522 } 454 }
523 455
524 bool haveStartTime = false; 456 bool haveStartTime = false;
525 bool haveDuration = false; 457 bool haveDuration = false;
526 bool havePitch = false; 458 bool havePitch = false;
528 460
529 for (int i = 0; i < m_columnPurposeCombos.size(); ++i) { 461 for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
530 462
531 QComboBox *thisCombo = m_columnPurposeCombos[i]; 463 QComboBox *thisCombo = m_columnPurposeCombos[i];
532 464
533 CSVFormat::ColumnPurpose purpose; 465 CSVFormat::ColumnPurpose purpose =
534 466 (CSVFormat::ColumnPurpose) (thisCombo->currentIndex());
535 if (m_dialogType == AnnotationDataDialog) { 467
536 purpose = (CSVFormat::ColumnPurpose) (thisCombo->currentIndex());
537 } else {
538 purpose = (thisCombo->currentIndex() == 1 ?
539 CSVFormat::ColumnValue :
540 CSVFormat::ColumnUnknown);
541 }
542
543 if (i == m_fuzzyColumn) { 468 if (i == m_fuzzyColumn) {
544 for (int j = i; j < m_format.getColumnCount(); ++j) { 469 for (int j = i; j < m_format.getColumnCount(); ++j) {
545 if (purpose == CSVFormat::ColumnUnknown) { 470 if (purpose == CSVFormat::ColumnUnknown) {
546 m_format.setColumnPurpose(j, CSVFormat::ColumnUnknown); 471 m_format.setColumnPurpose(j, CSVFormat::ColumnUnknown);
547 } else { // Value 472 } else { // Value
567 492
568 m_format.setColumnPurpose(i, purpose); 493 m_format.setColumnPurpose(i, purpose);
569 } 494 }
570 } 495 }
571 496
572 if (m_dialogType == AudioDataDialog) { 497 if (haveStartTime && haveDuration) {
573 m_format.setModelType(CSVFormat::WaveFileModel);
574 } else if (haveStartTime && haveDuration) {
575 if (havePitch) { 498 if (havePitch) {
576 m_format.setModelType(CSVFormat::TwoDimensionalModelWithDurationAndPitch); 499 m_format.setModelType(CSVFormat::TwoDimensionalModelWithDurationAndPitch);
577 } else { 500 } else {
578 m_format.setModelType(CSVFormat::TwoDimensionalModelWithDuration); 501 m_format.setModelType(CSVFormat::TwoDimensionalModelWithDuration);
579 } 502 }