changeset 378:22b72f0f6a4e

* More work to abstract out interactive components used in the data library, so that it does not need to depend on QtGui.
author Chris Cannam
date Fri, 14 Mar 2008 17:14:21 +0000
parents 0bcb449d15f4
children 036b75ddcd3f
files layer/ImageLayer.cpp widgets/CSVFormatDialog.cpp widgets/CSVFormatDialog.h widgets/FileFinder.cpp widgets/FileFinder.h widgets/ImageDialog.cpp widgets/MIDIFileImportDialog.cpp widgets/MIDIFileImportDialog.h widgets/ProgressDialog.cpp widgets/ProgressDialog.h widgets/widgets.pro
diffstat 11 files changed, 1172 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/layer/ImageLayer.cpp	Thu Mar 13 14:06:03 2008 +0000
+++ b/layer/ImageLayer.cpp	Fri Mar 14 17:14:21 2008 +0000
@@ -24,6 +24,7 @@
 #include "data/fileio/FileSource.h"
 
 #include "widgets/ImageDialog.h"
+#include "widgets/ProgressDialog.h"
 
 #include <QPainter>
 #include <QMouseEvent>
@@ -912,7 +913,8 @@
             return;
         }
 
-        FileSource *rf = new FileSource(img, FileSource::ProgressDialog);
+        ProgressDialog dialog(tr("Opening image URL..."), true, 2000);
+        FileSource *rf = new FileSource(img, &dialog);
         if (rf->isOK()) {
             std::cerr << "ok, adding it (local filename = " << rf->getLocalFilename().toStdString() << ")" << std::endl;
             m_remoteFiles[img] = rf;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/CSVFormatDialog.cpp	Fri Mar 14 17:14:21 2008 +0000
@@ -0,0 +1,270 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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 "CSVFormatDialog.h"
+
+#include <QFrame>
+#include <QGridLayout>
+#include <QPushButton>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QTableWidget>
+#include <QComboBox>
+#include <QLabel>
+
+
+CSVFormatDialog::CSVFormatDialog(QWidget *parent, CSVFormat format,
+				 size_t defaultSampleRate) :
+    QDialog(parent),
+    m_modelType(CSVFormat::OneDimensionalModel),
+    m_timingType(CSVFormat::ExplicitTiming),
+    m_timeUnits(CSVFormat::TimeAudioFrames),
+    m_separator(""),
+    m_behaviour(QString::KeepEmptyParts)
+{
+    setModal(true);
+    setWindowTitle(tr("Select Data Format"));
+
+    m_modelType = format.getModelType();
+    m_timingType = format.getTimingType();
+    m_timeUnits = format.getTimeUnits();
+    m_separator = format.getSeparator();
+    m_sampleRate = format.getSampleRate();
+    m_windowSize = format.getWindowSize();
+    m_behaviour = format.getSplitBehaviour();
+    m_example = format.getExample();
+    m_maxExampleCols = format.getMaxExampleCols();
+
+    QGridLayout *layout = new QGridLayout;
+
+    layout->addWidget(new QLabel(tr("<b>Select Data Format</b><p>Please select the correct data format for this file.")),
+		      0, 0, 1, 4);
+
+    layout->addWidget(new QLabel(tr("Each row specifies:")), 1, 0);
+
+    m_modelTypeCombo = new QComboBox;
+    m_modelTypeCombo->addItem(tr("A point in time"));
+    m_modelTypeCombo->addItem(tr("A value at a time"));
+    m_modelTypeCombo->addItem(tr("A set of values"));
+    layout->addWidget(m_modelTypeCombo, 1, 1, 1, 2);
+    connect(m_modelTypeCombo, SIGNAL(activated(int)),
+	    this, SLOT(modelTypeChanged(int)));
+    m_modelTypeCombo->setCurrentIndex(int(m_modelType));
+
+    layout->addWidget(new QLabel(tr("The first column contains:")), 2, 0);
+    
+    m_timingTypeCombo = new QComboBox;
+    m_timingTypeCombo->addItem(tr("Time, in seconds"));
+    m_timingTypeCombo->addItem(tr("Time, in audio sample frames"));
+    m_timingTypeCombo->addItem(tr("Data (rows are consecutive in time)"));
+    layout->addWidget(m_timingTypeCombo, 2, 1, 1, 2);
+    connect(m_timingTypeCombo, SIGNAL(activated(int)),
+	    this, SLOT(timingTypeChanged(int)));
+    m_timingTypeCombo->setCurrentIndex(m_timingType == CSVFormat::ExplicitTiming ?
+                                       m_timeUnits == CSVFormat::TimeSeconds ? 0 : 1 : 2);
+
+    m_sampleRateLabel = new QLabel(tr("Audio sample rate (Hz):"));
+    layout->addWidget(m_sampleRateLabel, 3, 0);
+    
+    size_t sampleRates[] = {
+	8000, 11025, 12000, 22050, 24000, 32000,
+	44100, 48000, 88200, 96000, 176400, 192000
+    };
+
+    m_sampleRateCombo = new QComboBox;
+    m_sampleRate = defaultSampleRate;
+    for (size_t i = 0; i < sizeof(sampleRates) / sizeof(sampleRates[0]); ++i) {
+	m_sampleRateCombo->addItem(QString("%1").arg(sampleRates[i]));
+	if (sampleRates[i] == m_sampleRate) m_sampleRateCombo->setCurrentIndex(i);
+    }
+    m_sampleRateCombo->setEditable(true);
+
+    layout->addWidget(m_sampleRateCombo, 3, 1);
+    connect(m_sampleRateCombo, SIGNAL(activated(QString)),
+	    this, SLOT(sampleRateChanged(QString)));
+    connect(m_sampleRateCombo, SIGNAL(editTextChanged(QString)),
+	    this, SLOT(sampleRateChanged(QString)));
+
+    m_windowSizeLabel = new QLabel(tr("Frame increment between rows:"));
+    layout->addWidget(m_windowSizeLabel, 4, 0);
+
+    m_windowSizeCombo = new QComboBox;
+    m_windowSize = 1024;
+    for (int i = 0; i <= 16; ++i) {
+	int value = 1 << i;
+	m_windowSizeCombo->addItem(QString("%1").arg(value));
+	if (value == int(m_windowSize)) m_windowSizeCombo->setCurrentIndex(i);
+    }
+    m_windowSizeCombo->setEditable(true);
+
+    layout->addWidget(m_windowSizeCombo, 4, 1);
+    connect(m_windowSizeCombo, SIGNAL(activated(QString)),
+	    this, SLOT(windowSizeChanged(QString)));
+    connect(m_windowSizeCombo, SIGNAL(editTextChanged(QString)),
+	    this, SLOT(windowSizeChanged(QString)));
+
+    layout->addWidget(new QLabel(tr("\nExample data from file:")), 5, 0, 1, 4);
+
+    m_exampleWidget = new QTableWidget
+	(std::min(10, m_example.size()), m_maxExampleCols);
+
+    layout->addWidget(m_exampleWidget, 6, 0, 1, 4);
+    layout->setColumnStretch(3, 10);
+    layout->setRowStretch(4, 10);
+
+    QPushButton *ok = new QPushButton(tr("OK"));
+    connect(ok, SIGNAL(clicked()), this, SLOT(accept()));
+    ok->setDefault(true);
+
+    QPushButton *cancel = new QPushButton(tr("Cancel"));
+    connect(cancel, SIGNAL(clicked()), this, SLOT(reject()));
+
+    QHBoxLayout *buttonLayout = new QHBoxLayout;
+    buttonLayout->addStretch(1);
+    buttonLayout->addWidget(ok);
+    buttonLayout->addWidget(cancel);
+
+    QVBoxLayout *mainLayout = new QVBoxLayout;
+    mainLayout->addLayout(layout);
+    mainLayout->addLayout(buttonLayout);
+
+    setLayout(mainLayout);
+    
+    timingTypeChanged(m_timingTypeCombo->currentIndex());
+}
+
+CSVFormatDialog::~CSVFormatDialog()
+{
+}
+
+CSVFormat
+CSVFormatDialog::getFormat() const
+{
+    CSVFormat format;
+    format.setModelType(m_modelType);
+    format.setTimingType(m_timingType);
+    format.setTimeUnits(m_timeUnits);
+    format.setSeparator(m_separator);
+    format.setSampleRate(m_sampleRate);
+    format.setWindowSize(m_windowSize);
+    format.setSplitBehaviour(m_behaviour);
+    return format;
+}
+
+void
+CSVFormatDialog::populateExample()
+{
+    m_exampleWidget->setColumnCount
+        (m_timingType == CSVFormat::ExplicitTiming ?
+	 m_maxExampleCols - 1 : m_maxExampleCols);
+
+    m_exampleWidget->setHorizontalHeaderLabels(QStringList());
+
+    for (int i = 0; i < m_example.size(); ++i) {
+	for (int j = 0; j < m_example[i].size(); ++j) {
+
+	    QTableWidgetItem *item = new QTableWidgetItem(m_example[i][j]);
+
+	    if (j == 0) {
+		if (m_timingType == CSVFormat::ExplicitTiming) {
+		    m_exampleWidget->setVerticalHeaderItem(i, item);
+		    continue;
+		} else {
+		    QTableWidgetItem *header =
+			new QTableWidgetItem(QString("%1").arg(i));
+		    header->setFlags(Qt::ItemIsEnabled);
+		    m_exampleWidget->setVerticalHeaderItem(i, header);
+		}
+	    }
+	    int index = j;
+	    if (m_timingType == CSVFormat::ExplicitTiming) --index;
+	    item->setFlags(Qt::ItemIsEnabled);
+	    m_exampleWidget->setItem(i, index, item);
+	}
+    }
+}
+
+void
+CSVFormatDialog::modelTypeChanged(int type)
+{
+    m_modelType = (CSVFormat::ModelType)type;
+
+    if (m_modelType == CSVFormat::ThreeDimensionalModel) {
+        // We can't load 3d models with explicit timing, because the 3d
+        // model is dense so we need a fixed sample increment
+        m_timingTypeCombo->setCurrentIndex(2);
+        timingTypeChanged(2);
+    }
+}
+
+void
+CSVFormatDialog::timingTypeChanged(int type)
+{
+    switch (type) {
+
+    case 0:
+	m_timingType = CSVFormat::ExplicitTiming;
+	m_timeUnits = CSVFormat::TimeSeconds;
+	m_sampleRateCombo->setEnabled(false);
+	m_sampleRateLabel->setEnabled(false);
+	m_windowSizeCombo->setEnabled(false);
+	m_windowSizeLabel->setEnabled(false);
+        if (m_modelType == CSVFormat::ThreeDimensionalModel) {
+            m_modelTypeCombo->setCurrentIndex(1);
+            modelTypeChanged(1);
+        }
+	break;
+
+    case 1:
+	m_timingType = CSVFormat::ExplicitTiming;
+	m_timeUnits = CSVFormat::TimeAudioFrames;
+	m_sampleRateCombo->setEnabled(true);
+	m_sampleRateLabel->setEnabled(true);
+	m_windowSizeCombo->setEnabled(false);
+	m_windowSizeLabel->setEnabled(false);
+        if (m_modelType == CSVFormat::ThreeDimensionalModel) {
+            m_modelTypeCombo->setCurrentIndex(1);
+            modelTypeChanged(1);
+        }
+	break;
+
+    case 2:
+	m_timingType = CSVFormat::ImplicitTiming;
+	m_timeUnits = CSVFormat::TimeWindows;
+	m_sampleRateCombo->setEnabled(true);
+	m_sampleRateLabel->setEnabled(true);
+	m_windowSizeCombo->setEnabled(true);
+	m_windowSizeLabel->setEnabled(true);
+	break;
+    }
+
+    populateExample();
+}
+
+void
+CSVFormatDialog::sampleRateChanged(QString rateString)
+{
+    bool ok = false;
+    int sampleRate = rateString.toInt(&ok);
+    if (ok) m_sampleRate = sampleRate;
+}
+
+void
+CSVFormatDialog::windowSizeChanged(QString sizeString)
+{
+    bool ok = false;
+    int size = sizeString.toInt(&ok);
+    if (ok) m_windowSize = size;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/CSVFormatDialog.h	Fri Mar 14 17:14:21 2008 +0000
@@ -0,0 +1,69 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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 _CSV_FORMAT_DIALOG_H_
+#define _CSV_FORMAT_DIALOG_H_
+
+#include "data/fileio/CSVFormat.h"
+
+class QTableWidget;
+class QComboBox;
+class QLabel;
+    
+#include <QDialog>
+
+class CSVFormatDialog : public QDialog
+{
+    Q_OBJECT
+    
+public:
+    CSVFormatDialog(QWidget *parent, CSVFormat initialFormat,
+                    size_t defaultSampleRate);
+    ~CSVFormatDialog();
+
+    CSVFormat getFormat() const;
+    
+protected slots:
+    void modelTypeChanged(int type);
+    void timingTypeChanged(int type);
+    void sampleRateChanged(QString);
+    void windowSizeChanged(QString);
+
+protected:
+    CSVFormat::ModelType  m_modelType;
+    CSVFormat::TimingType m_timingType;
+    CSVFormat::TimeUnits  m_timeUnits;
+
+    QString    m_separator;
+    size_t     m_sampleRate;
+    size_t     m_windowSize;
+
+    QString::SplitBehavior m_behaviour;
+    
+    QList<QStringList> m_example;
+    int m_maxExampleCols;
+    QTableWidget *m_exampleWidget;
+    
+    QComboBox *m_modelTypeCombo;
+    QComboBox *m_timingTypeCombo;
+    QLabel *m_sampleRateLabel;
+    QComboBox *m_sampleRateCombo;
+    QLabel *m_windowSizeLabel;
+    QComboBox *m_windowSizeCombo;
+
+    void populateExample();
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/FileFinder.cpp	Fri Mar 14 17:14:21 2008 +0000
@@ -0,0 +1,511 @@
+/* -*- 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 file copyright 2007 QMUL.
+    
+    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 "FileFinder.h"
+#include "data/fileio/FileSource.h"
+#include "data/fileio/AudioFileReaderFactory.h"
+#include "data/fileio/DataFileReaderFactory.h"
+
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QFileDialog>
+#include <QInputDialog>
+#include <QImageReader>
+#include <QSettings>
+
+#include <iostream>
+
+FileFinder *
+FileFinder::m_instance = 0;
+
+FileFinder::FileFinder() :
+    m_lastLocatedLocation("")
+{
+}
+
+FileFinder::~FileFinder()
+{
+}
+
+FileFinder *
+FileFinder::getInstance()
+{
+    if (m_instance == 0) {
+        m_instance = new FileFinder();
+    }
+    return m_instance;
+}
+
+QString
+FileFinder::getOpenFileName(FileType type, QString fallbackLocation)
+{
+    QString settingsKey;
+    QString lastPath = fallbackLocation;
+    
+    QString title = tr("Select file");
+    QString filter = tr("All files (*.*)");
+
+    switch (type) {
+
+    case SessionFile:
+        settingsKey = "sessionpath";
+        title = tr("Select a session file");
+        filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
+        break;
+
+    case AudioFile:
+        settingsKey = "audiopath";
+        title = "Select an audio file";
+        filter = tr("Audio files (%1)\nAll files (*.*)")
+            .arg(AudioFileReaderFactory::getKnownExtensions());
+        break;
+
+    case LayerFile:
+        settingsKey = "layerpath";
+        filter = tr("All supported files (%1)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions());
+        break;
+
+    case LayerFileNoMidi:
+        settingsKey = "layerpath";
+        filter = tr("All supported files (%1)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions());
+        break;
+
+    case SessionOrAudioFile:
+        settingsKey = "lastpath";
+        filter = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)")
+            .arg(AudioFileReaderFactory::getKnownExtensions());
+        break;
+
+    case ImageFile:
+        settingsKey = "imagepath";
+        {
+            QStringList fmts;
+            QList<QByteArray> formats = QImageReader::supportedImageFormats();
+            for (QList<QByteArray>::iterator i = formats.begin();
+                 i != formats.end(); ++i) {
+                fmts.push_back(QString("*.%1")
+                               .arg(QString::fromLocal8Bit(*i).toLower()));
+            }
+            filter = tr("Image files (%1)\nAll files (*.*)").arg(fmts.join(" "));
+        }
+        break;
+
+    case AnyFile:
+        settingsKey = "lastpath";
+        filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)")
+            .arg(AudioFileReaderFactory::getKnownExtensions())
+            .arg(DataFileReaderFactory::getKnownExtensions());
+        break;
+    };
+
+    if (lastPath == "") {
+        char *home = getenv("HOME");
+        if (home) lastPath = home;
+        else lastPath = ".";
+    } else if (QFileInfo(lastPath).isDir()) {
+        lastPath = QFileInfo(lastPath).canonicalPath();
+    } else {
+        lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
+    }
+
+    QSettings settings;
+    settings.beginGroup("FileFinder");
+    lastPath = settings.value(settingsKey, lastPath).toString();
+
+    QString path = "";
+
+    // Use our own QFileDialog just for symmetry with getSaveFileName below
+
+    QFileDialog dialog;
+    dialog.setFilters(filter.split('\n'));
+    dialog.setWindowTitle(title);
+    dialog.setDirectory(lastPath);
+
+    dialog.setAcceptMode(QFileDialog::AcceptOpen);
+    dialog.setFileMode(QFileDialog::ExistingFile);
+    
+    if (dialog.exec()) {
+        QStringList files = dialog.selectedFiles();
+        if (!files.empty()) path = *files.begin();
+        
+        QFileInfo fi(path);
+        
+        if (!fi.exists()) {
+            
+            QMessageBox::critical(0, tr("File does not exist"),
+                                  tr("File \"%1\" does not exist").arg(path));
+            path = "";
+            
+        } else if (!fi.isReadable()) {
+            
+            QMessageBox::critical(0, tr("File is not readable"),
+                                  tr("File \"%1\" can not be read").arg(path));
+            path = "";
+            
+        } else if (fi.isDir()) {
+            
+            QMessageBox::critical(0, tr("Directory selected"),
+                                  tr("File \"%1\" is a directory").arg(path));
+            path = "";
+
+        } else if (!fi.isFile()) {
+            
+            QMessageBox::critical(0, tr("Non-file selected"),
+                                  tr("Path \"%1\" is not a file").arg(path));
+            path = "";
+            
+        } else if (fi.size() == 0) {
+            
+            QMessageBox::critical(0, tr("File is empty"),
+                                  tr("File \"%1\" is empty").arg(path));
+            path = "";
+        }                
+    }
+
+    if (path != "") {
+        settings.setValue(settingsKey,
+                          QFileInfo(path).absoluteDir().canonicalPath());
+    }
+    
+    return path;
+}
+
+QString
+FileFinder::getSaveFileName(FileType type, QString fallbackLocation)
+{
+    QString settingsKey;
+    QString lastPath = fallbackLocation;
+    
+    QString title = tr("Select file");
+    QString filter = tr("All files (*.*)");
+
+    switch (type) {
+
+    case SessionFile:
+        settingsKey = "savesessionpath";
+        title = tr("Select a session file");
+        filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
+        break;
+
+    case AudioFile:
+        settingsKey = "saveaudiopath";
+        title = "Select an audio file";
+        title = tr("Select a file to export to");
+        filter = tr("WAV audio files (*.wav)\nAll files (*.*)");
+        break;
+
+    case LayerFile:
+        settingsKey = "savelayerpath";
+        title = tr("Select a file to export to");
+        filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)");
+        break;
+
+    case LayerFileNoMidi:
+        settingsKey = "savelayerpath";
+        title = tr("Select a file to export to");
+        filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
+        break;
+
+    case SessionOrAudioFile:
+        std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl;
+        abort();
+
+    case ImageFile:
+        settingsKey = "saveimagepath";
+        title = tr("Select a file to export to");
+        filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)");
+        break;
+
+    case AnyFile:
+        std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl;
+        abort();
+    };
+
+    if (lastPath == "") {
+        char *home = getenv("HOME");
+        if (home) lastPath = home;
+        else lastPath = ".";
+    } else if (QFileInfo(lastPath).isDir()) {
+        lastPath = QFileInfo(lastPath).canonicalPath();
+    } else {
+        lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
+    }
+
+    QSettings settings;
+    settings.beginGroup("FileFinder");
+    lastPath = settings.value(settingsKey, lastPath).toString();
+
+    QString path = "";
+
+    // Use our own QFileDialog instead of static functions, as we may
+    // need to adjust the file extension based on the selected filter
+
+    QFileDialog dialog;
+    dialog.setFilters(filter.split('\n'));
+    dialog.setWindowTitle(title);
+    dialog.setDirectory(lastPath);
+
+    dialog.setAcceptMode(QFileDialog::AcceptSave);
+    dialog.setFileMode(QFileDialog::AnyFile);
+    dialog.setConfirmOverwrite(false); // we'll do that
+        
+    if (type == SessionFile) {
+        dialog.setDefaultSuffix("sv");
+    } else if (type == AudioFile) {
+        dialog.setDefaultSuffix("wav");
+    } else if (type == ImageFile) {
+        dialog.setDefaultSuffix("png");
+    }
+
+    bool good = false;
+
+    while (!good) {
+
+        path = "";
+        
+        if (!dialog.exec()) break;
+        
+        QStringList files = dialog.selectedFiles();
+        if (files.empty()) break;
+        path = *files.begin();
+        
+        QFileInfo fi(path);
+
+        std::cerr << "type = " << type << ", suffix = " << fi.suffix().toStdString() << std::endl;
+        
+        if ((type == LayerFile || type == LayerFileNoMidi)
+            && fi.suffix() == "") {
+            QString expectedExtension;
+            QString selectedFilter = dialog.selectedFilter();
+            if (selectedFilter.contains(".svl")) {
+                expectedExtension = "svl";
+            } else if (selectedFilter.contains(".txt")) {
+                expectedExtension = "txt";
+            } else if (selectedFilter.contains(".csv")) {
+                expectedExtension = "csv";
+            } else if (selectedFilter.contains(".mid")) {
+                expectedExtension = "mid";
+            }
+            std::cerr << "expected extension = " << expectedExtension.toStdString() << std::endl;
+            if (expectedExtension != "") {
+                path = QString("%1.%2").arg(path).arg(expectedExtension);
+                fi = QFileInfo(path);
+            }
+        }
+        
+        if (fi.isDir()) {
+            QMessageBox::critical(0, tr("Directory selected"),
+                                  tr("File \"%1\" is a directory").arg(path));
+            continue;
+        }
+        
+        if (fi.exists()) {
+            if (QMessageBox::question(0, tr("File exists"),
+                                      tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
+                                      QMessageBox::Ok,
+                                      QMessageBox::Cancel) != QMessageBox::Ok) {
+                continue;
+            }
+        }
+        
+        good = true;
+    }
+        
+    if (path != "") {
+        settings.setValue(settingsKey,
+                          QFileInfo(path).absoluteDir().canonicalPath());
+    }
+    
+    return path;
+}
+
+void
+FileFinder::registerLastOpenedFilePath(FileType type, QString path)
+{
+    QString settingsKey;
+
+    switch (type) {
+    case SessionFile:
+        settingsKey = "sessionpath";
+        break;
+
+    case AudioFile:
+        settingsKey = "audiopath";
+        break;
+
+    case LayerFile:
+        settingsKey = "layerpath";
+        break;
+
+    case LayerFileNoMidi:
+        settingsKey = "layerpath";
+        break;
+
+    case SessionOrAudioFile:
+        settingsKey = "lastpath";
+        break;
+
+    case ImageFile:
+        settingsKey = "imagepath";
+        break;
+
+    case AnyFile:
+        settingsKey = "lastpath";
+        break;
+    }
+
+    if (path != "") {
+        QSettings settings;
+        settings.beginGroup("FileFinder");
+        path = QFileInfo(path).absoluteDir().canonicalPath();
+        settings.setValue(settingsKey, path);
+        settings.setValue("lastpath", path);
+    }
+}
+    
+QString
+FileFinder::find(FileType type, QString location, QString lastKnownLocation)
+{
+    if (FileSource::canHandleScheme(location)) {
+        if (FileSource(location).isAvailable()) {
+            std::cerr << "FileFinder::find: ok, it's available... returning" << std::endl;
+            return location;
+        }
+    }
+
+    if (QFileInfo(location).exists()) return location;
+
+    QString foundAt = "";
+
+    if ((foundAt = findRelative(location, lastKnownLocation)) != "") {
+        return foundAt;
+    }
+
+    if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") {
+        return foundAt;
+    }
+
+    return locateInteractive(type, location);
+}
+
+QString
+FileFinder::findRelative(QString location, QString relativeTo)
+{
+    if (relativeTo == "") return "";
+
+    std::cerr << "Looking for \"" << location.toStdString() << "\" next to \""
+              << relativeTo.toStdString() << "\"..." << std::endl;
+
+    QString fileName;
+    QString resolved;
+
+    if (FileSource::isRemote(location)) {
+        fileName = QUrl(location).path().section('/', -1, -1,
+                                                 QString::SectionSkipEmpty);
+    } else {
+        if (QUrl(location).scheme() == "file") {
+            location = QUrl(location).toLocalFile();
+        }
+        fileName = QFileInfo(location).fileName();
+    }
+
+    if (FileSource::isRemote(relativeTo)) {
+        resolved = QUrl(relativeTo).resolved(fileName).toString();
+        if (!FileSource(resolved).isAvailable()) resolved = "";
+        std::cerr << "resolved: " << resolved.toStdString() << std::endl;
+    } else {
+        if (QUrl(relativeTo).scheme() == "file") {
+            relativeTo = QUrl(relativeTo).toLocalFile();
+        }
+        resolved = QFileInfo(relativeTo).dir().filePath(fileName);
+        if (!QFileInfo(resolved).exists() ||
+            !QFileInfo(resolved).isFile() ||
+            !QFileInfo(resolved).isReadable()) {
+            resolved = "";
+        }
+    }
+            
+    return resolved;
+}
+
+QString
+FileFinder::locateInteractive(FileType type, QString thing)
+{
+    QString question;
+    if (type == AudioFile) {
+        question = tr("Audio file \"%1\" could not be opened.\nDo you want to locate it?");
+    } else {
+        question = tr("File \"%1\" could not be opened.\nDo you want to locate it?");
+    }
+
+    QString path = "";
+    bool done = false;
+
+    while (!done) {
+
+        int rv = QMessageBox::question
+            (0, 
+             tr("Failed to open file"),
+             question.arg(thing),
+             tr("Locate file..."),
+             tr("Use URL..."),
+             tr("Cancel"),
+             0, 2);
+        
+        switch (rv) {
+
+        case 0: // Locate file
+
+            if (QFileInfo(thing).dir().exists()) {
+                path = QFileInfo(thing).dir().canonicalPath();
+            }
+            
+            path = getOpenFileName(type, path);
+            done = (path != "");
+            break;
+
+        case 1: // Use URL
+        {
+            bool ok = false;
+            path = QInputDialog::getText
+                (0, tr("Use URL"),
+                 tr("Please enter the URL to use for this file:"),
+                 QLineEdit::Normal, "", &ok);
+
+            if (ok && path != "") {
+                if (FileSource(path).isAvailable()) {
+                    done = true;
+                } else {
+                    QMessageBox::critical
+                        (0, tr("Failed to open location"),
+                         tr("URL \"%1\" could not be opened").arg(path));
+                    path = "";
+                }
+            }
+            break;
+        }
+
+        case 2: // Cancel
+            path = "";
+            done = true;
+            break;
+        }
+    }
+
+    if (path != "") m_lastLocatedLocation = path;
+    return path;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/FileFinder.h	Fri Mar 14 17:14:21 2008 +0000
@@ -0,0 +1,58 @@
+/* -*- 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 file copyright 2007 QMUL.
+    
+    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 _FILE_FINDER_H_
+#define _FILE_FINDER_H_
+
+#include <QString>
+#include <QObject>
+
+class FileFinder : public QObject
+{
+    Q_OBJECT
+
+public:
+    virtual ~FileFinder();
+
+    enum FileType {
+        SessionFile,
+        AudioFile,
+        LayerFile,
+        LayerFileNoMidi,
+        SessionOrAudioFile,
+        ImageFile,
+        AnyFile
+    };
+
+    QString getOpenFileName(FileType type, QString fallbackLocation = "");
+    QString getSaveFileName(FileType type, QString fallbackLocation = "");
+    void registerLastOpenedFilePath(FileType type, QString path);
+
+    QString find(FileType type, QString location, QString lastKnownLocation = "");
+
+    static FileFinder *getInstance();
+
+protected:
+    FileFinder();
+    static FileFinder *m_instance;
+
+    QString findRelative(QString location, QString relativeTo);
+    QString locateInteractive(FileType type, QString thing);
+
+    QString m_lastLocatedLocation;
+};
+
+#endif
+
--- a/widgets/ImageDialog.cpp	Thu Mar 13 14:06:03 2008 +0000
+++ b/widgets/ImageDialog.cpp	Fri Mar 14 17:14:21 2008 +0000
@@ -26,8 +26,10 @@
 #include <QUrl>
 #include <QMessageBox>
 
+#include "ProgressDialog.h"
+
 #include "data/fileio/FileSource.h"
-#include "data/fileio/FileFinder.h"
+#include "FileFinder.h"
 
 #include <iostream>
 
@@ -186,7 +188,9 @@
                                       tr("The URL scheme \"%1\" is not supported")
                                       .arg(url.scheme()));
             } else {
-                m_remoteFile = new FileSource(url, FileSource::ProgressDialog);
+                
+                ProgressDialog dialog(tr("Opening image URL..."), true, 2000);
+                m_remoteFile = new FileSource(url, &dialog);
                 m_remoteFile->waitForData();
                 if (!m_remoteFile->isOK()) {
                     QMessageBox::critical(this, tr("File download failed"),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/MIDIFileImportDialog.cpp	Fri Mar 14 17:14:21 2008 +0000
@@ -0,0 +1,72 @@
+/* -*- 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 "MIDIFileImportDialog.h"
+
+#include <QMessageBox>
+#include <QInputDialog>
+
+MIDIFileImportDialog::MIDIFileImportDialog(QWidget *parent) :
+    m_parent(parent)
+{
+}
+
+MIDIFileImportDialog::TrackPreference
+MIDIFileImportDialog::getTrackImportPreference(QStringList displayNames,
+                                               bool haveSomePercussion,
+                                               QString &singleTrack) const
+{
+    QStringList available;
+
+    QString allTracks = tr("Merge all tracks");
+    QString allNonPercussion = tr("Merge all non-percussion tracks");
+
+    singleTrack = "";
+
+    int nonTrackItems = 1;
+    
+    available << allTracks;
+
+    if (haveSomePercussion) {
+        available << allNonPercussion;
+        ++nonTrackItems;
+    }
+
+    available << displayNames;
+
+    bool ok = false;
+    QString selected = QInputDialog::getItem
+        (0, tr("Select track or tracks to import"),
+         tr("<b>Select track to import</b><p>You can only import this file as a single annotation layer, but the file contains more than one track, or notes on more than one channel.<p>Please select the track or merged tracks you wish to import:"),
+         available, 0, false, &ok);
+    
+    if (!ok || selected.isEmpty()) return ImportNothing;
+
+    TrackPreference pref;
+    if (selected == allTracks) pref = MergeAllTracks;
+    else if (selected == allNonPercussion) pref = MergeAllNonPercussionTracks;
+    else {
+        singleTrack = selected;
+        pref = ImportSingleTrack;
+    }
+
+    return pref;
+}
+
+void
+MIDIFileImportDialog::showError(QString error)
+{
+    QMessageBox::critical(0, tr("Error in MIDI file import"), error);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/MIDIFileImportDialog.h	Fri Mar 14 17:14:21 2008 +0000
@@ -0,0 +1,38 @@
+/* -*- 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 _MIDI_FILE_IMPORT_DIALOG_H_
+#define _MIDI_FILE_IMPORT_DIALOG_H_
+
+#include <QObject>
+
+#include "data/fileio/MIDIFileReader.h"
+
+class MIDIFileImportDialog : public QObject,
+                             public MIDIFileImportPreferenceAcquirer
+{
+public:
+    MIDIFileImportDialog(QWidget *parent = 0);
+
+    virtual TrackPreference getTrackImportPreference
+    (QStringList trackNames, bool haveSomePercussion,
+     QString &singleTrack) const;
+
+    virtual void showError(QString error);
+
+protected:
+    QWidget *m_parent;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/ProgressDialog.cpp	Fri Mar 14 17:14:21 2008 +0000
@@ -0,0 +1,89 @@
+/* -*- 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 file copyright 2007-2008 QMUL.
+    
+    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 "ProgressDialog.h"
+
+#include <QProgressDialog>
+#include <QApplication>
+#include <QTimer>
+
+ProgressDialog::ProgressDialog(QString message, bool cancellable,
+                               int timeBeforeShow, QWidget *parent) : 
+    m_showTimer(0),
+    m_timerElapsed(false)
+{
+    m_dialog = new QProgressDialog(message, cancellable ? tr("Cancel") : 0,
+                                   0, 100, parent);
+    if (timeBeforeShow > 0) {
+        m_dialog->hide();
+        m_showTimer = new QTimer;
+        connect(m_showTimer, SIGNAL(timeout()), this, SLOT(showTimerElapsed()));
+        m_showTimer->setSingleShot(true);
+        m_showTimer->start(timeBeforeShow);
+    } else {
+        m_dialog->show();
+        m_dialog->raise();
+        m_timerElapsed = true;
+    }
+
+    if (cancellable) {
+        connect(m_dialog, SIGNAL(canceled()), this, SIGNAL(cancelled()));
+    }
+}
+
+ProgressDialog::~ProgressDialog()
+{
+    delete m_showTimer;
+    delete m_dialog;
+}
+
+void
+ProgressDialog::setMessage(QString text)
+{
+    m_dialog->setLabelText(text);
+}
+
+void
+ProgressDialog::showTimerElapsed()
+{
+    m_timerElapsed = true;
+    delete m_showTimer;
+    m_showTimer = 0;
+    if (m_dialog->value() > 0) {
+        m_dialog->show();
+    }
+    qApp->processEvents();
+}
+
+void
+ProgressDialog::setProgress(int percentage)
+{
+    if (percentage > m_dialog->value()) {
+
+        m_dialog->setValue(percentage);
+
+        if (percentage >= 100) {
+            delete m_showTimer;
+            m_showTimer = 0;
+            m_dialog->hide();
+        } else if (m_timerElapsed && !m_dialog->isVisible()) {
+            m_dialog->show();
+            m_dialog->raise();
+        }
+
+        qApp->processEvents();
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/ProgressDialog.h	Fri Mar 14 17:14:21 2008 +0000
@@ -0,0 +1,48 @@
+/* -*- 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 file copyright 2007-2008 QMUL.
+    
+    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 _PROGRESS_DIALOG_H_
+
+#include "base/ProgressReporter.h"
+
+class QProgressDialog;
+class QTimer;
+
+class ProgressDialog : public ProgressReporter
+{
+    Q_OBJECT
+    
+public:
+    ProgressDialog(QString message, bool cancellable,
+                   int timeBeforeShow = 0, QWidget *parent = 0);
+    virtual ~ProgressDialog();
+
+signals:
+    void cancelled();
+
+public slots:
+    virtual void setMessage(QString text);
+    virtual void setProgress(int percentage);
+
+protected slots:
+    virtual void showTimerElapsed();
+
+protected:
+    QProgressDialog *m_dialog;
+    QTimer *m_showTimer;
+    bool m_timerElapsed;
+};
+
+#endif
--- a/widgets/widgets.pro	Thu Mar 13 14:06:03 2008 +0000
+++ b/widgets/widgets.pro	Fri Mar 14 17:14:21 2008 +0000
@@ -17,7 +17,9 @@
 HEADERS += AudioDial.h \
            ColourNameDialog.h \
            CommandHistory.h \
+           CSVFormatDialog.h \
            Fader.h \
+           FileFinder.h \
            IconLoader.h \
            ImageDialog.h \
            ItemEditDialog.h \
@@ -27,6 +29,7 @@
            LayerTreeDialog.h \
            LEDButton.h \
            ListInputDialog.h \
+           MIDIFileImportDialog.h \
            NotifyingCheckBox.h \
            NotifyingComboBox.h \
            NotifyingPushButton.h \
@@ -34,6 +37,7 @@
            Panner.h \
            PluginParameterBox.h \
            PluginParameterDialog.h \
+           ProgressDialog.h \
            PropertyBox.h \
            PropertyStack.h \
            RangeInputDialog.h \
@@ -46,7 +50,9 @@
 SOURCES += AudioDial.cpp \
            ColourNameDialog.cpp \
            CommandHistory.cpp \
+           CSVFormatDialog.cpp \
            Fader.cpp \
+           FileFinder.cpp \
            IconLoader.cpp \
            ImageDialog.cpp \
            ItemEditDialog.cpp \
@@ -56,6 +62,7 @@
            LayerTreeDialog.cpp \
            LEDButton.cpp \
            ListInputDialog.cpp \
+           MIDIFileImportDialog.cpp \
            NotifyingCheckBox.cpp \
            NotifyingComboBox.cpp \
            NotifyingPushButton.cpp \
@@ -63,6 +70,7 @@
            Panner.cpp \
            PluginParameterBox.cpp \
            PluginParameterDialog.cpp \
+           ProgressDialog.cpp \
            PropertyBox.cpp \
            PropertyStack.cpp \
            RangeInputDialog.cpp \