Mercurial > hg > sonic-visualiser
diff main/MainWindow.cpp @ 0:cd5d7ff8ef38
* Reorganising code base. This revision will not compile.
author | Chris Cannam |
---|---|
date | Mon, 31 Jul 2006 12:03:45 +0000 |
parents | |
children | 40116f709d3b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/MainWindow.cpp Mon Jul 31 12:03:45 2006 +0000 @@ -0,0 +1,3097 @@ +/* -*- 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 "../version.h" + +#include "MainWindow.h" +#include "Document.h" +#include "PreferencesDialog.h" + +#include "widgets/Pane.h" +#include "widgets/PaneStack.h" +#include "model/WaveFileModel.h" +#include "model/SparseOneDimensionalModel.h" +#include "base/ViewManager.h" +#include "base/Preferences.h" +#include "layer/WaveformLayer.h" +#include "layer/TimeRulerLayer.h" +#include "layer/TimeInstantLayer.h" +#include "layer/TimeValueLayer.h" +#include "layer/Colour3DPlotLayer.h" +#include "widgets/Fader.h" +#include "widgets/Panner.h" +#include "widgets/PropertyBox.h" +#include "widgets/PropertyStack.h" +#include "widgets/AudioDial.h" +#include "widgets/LayerTree.h" +#include "widgets/ListInputDialog.h" +#include "audioio/AudioCallbackPlaySource.h" +#include "audioio/AudioCallbackPlayTarget.h" +#include "audioio/AudioTargetFactory.h" +#include "fileio/AudioFileReaderFactory.h" +#include "fileio/DataFileReaderFactory.h" +#include "fileio/WavFileWriter.h" +#include "fileio/CSVFileWriter.h" +#include "fileio/BZipFileDevice.h" +#include "fileio/RecentFiles.h" +#include "transform/TransformFactory.h" +#include "base/PlayParameterRepository.h" +#include "base/XmlExportable.h" +#include "base/CommandHistory.h" +#include "base/Profiler.h" +#include "base/Clipboard.h" + +// For version information +#include "vamp/vamp.h" +#include "vamp-sdk/PluginBase.h" +#include "plugin/api/ladspa.h" +#include "plugin/api/dssi.h" + +#include <QApplication> +#include <QPushButton> +#include <QFileDialog> +#include <QMessageBox> +#include <QGridLayout> +#include <QLabel> +#include <QAction> +#include <QMenuBar> +#include <QToolBar> +#include <QInputDialog> +#include <QStatusBar> +#include <QTreeView> +#include <QFile> +#include <QTextStream> +#include <QProcess> + +#include <iostream> +#include <cstdio> +#include <errno.h> + +using std::cerr; +using std::endl; + + +MainWindow::MainWindow() : + m_document(0), + m_paneStack(0), + m_viewManager(0), + m_panner(0), + m_timeRulerLayer(0), + m_playSource(0), + m_playTarget(0), + m_mainMenusCreated(false), + m_paneMenu(0), + m_layerMenu(0), + m_existingLayersMenu(0), + m_rightButtonMenu(0), + m_rightButtonLayerMenu(0), + m_documentModified(false), + m_preferencesDialog(0) +{ + setWindowTitle(tr("Sonic Visualiser")); + + UnitDatabase::getInstance()->registerUnit("Hz"); + UnitDatabase::getInstance()->registerUnit("dB"); + + connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()), + this, SLOT(documentModified())); + connect(CommandHistory::getInstance(), SIGNAL(documentRestored()), + this, SLOT(documentRestored())); + + QFrame *frame = new QFrame; + setCentralWidget(frame); + + QGridLayout *layout = new QGridLayout; + + m_viewManager = new ViewManager(); + connect(m_viewManager, SIGNAL(selectionChanged()), + this, SLOT(updateMenuStates())); + + m_descriptionLabel = new QLabel; + + m_paneStack = new PaneStack(frame, m_viewManager); + connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)), + this, SLOT(currentPaneChanged(Pane *))); + connect(m_paneStack, SIGNAL(currentLayerChanged(Pane *, Layer *)), + this, SLOT(currentLayerChanged(Pane *, Layer *))); + connect(m_paneStack, SIGNAL(rightButtonMenuRequested(Pane *, QPoint)), + this, SLOT(rightButtonMenuRequested(Pane *, QPoint))); + + m_panner = new Panner(frame); + m_panner->setViewManager(m_viewManager); + m_panner->setFixedHeight(40); + + m_panLayer = new WaveformLayer; + m_panLayer->setChannelMode(WaveformLayer::MergeChannels); +// m_panLayer->setScale(WaveformLayer::MeterScale); + m_panLayer->setAutoNormalize(true); + m_panLayer->setBaseColour(Qt::darkGreen); + m_panLayer->setAggressiveCacheing(true); + m_panner->addLayer(m_panLayer); + + m_playSource = new AudioCallbackPlaySource(m_viewManager); + + connect(m_playSource, SIGNAL(sampleRateMismatch(size_t, size_t, bool)), + this, SLOT(sampleRateMismatch(size_t, size_t, bool))); + + m_fader = new Fader(frame, false); + + m_playSpeed = new AudioDial(frame); + m_playSpeed->setMinimum(1); + m_playSpeed->setMaximum(10); + m_playSpeed->setValue(10); + m_playSpeed->setFixedWidth(24); + m_playSpeed->setFixedHeight(24); + m_playSpeed->setNotchesVisible(true); + m_playSpeed->setPageStep(1); + m_playSpeed->setToolTip(tr("Playback speed: Full")); + m_playSpeed->setDefaultValue(10); + connect(m_playSpeed, SIGNAL(valueChanged(int)), + this, SLOT(playSpeedChanged(int))); + + layout->addWidget(m_paneStack, 0, 0, 1, 3); + layout->addWidget(m_panner, 1, 0); + layout->addWidget(m_fader, 1, 1); + layout->addWidget(m_playSpeed, 1, 2); + frame->setLayout(layout); + + connect(m_viewManager, SIGNAL(outputLevelsChanged(float, float)), + this, SLOT(outputLevelsChanged(float, float))); + + connect(Preferences::getInstance(), + SIGNAL(propertyChanged(PropertyContainer::PropertyName)), + this, + SLOT(preferenceChanged(PropertyContainer::PropertyName))); + + setupMenus(); + setupToolbars(); + +// statusBar()->addWidget(m_descriptionLabel); + + newSession(); +} + +MainWindow::~MainWindow() +{ + closeSession(); + delete m_playTarget; + delete m_playSource; + delete m_viewManager; + Profiles::getInstance()->dump(); +} + +void +MainWindow::setupMenus() +{ + QAction *action = 0; + QMenu *menu = 0; + QToolBar *toolbar = 0; + + if (!m_mainMenusCreated) { + m_rightButtonMenu = new QMenu(); + } + + if (m_rightButtonLayerMenu) { + m_rightButtonLayerMenu->clear(); + } else { + m_rightButtonLayerMenu = m_rightButtonMenu->addMenu(tr("&Layer")); + m_rightButtonMenu->addSeparator(); + } + + if (!m_mainMenusCreated) { + + CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); + m_rightButtonMenu->addSeparator(); + + menu = menuBar()->addMenu(tr("&File")); + toolbar = addToolBar(tr("File Toolbar")); + + QIcon icon(":icons/filenew.png"); + icon.addFile(":icons/filenew-22.png"); + action = new QAction(icon, tr("&New Session"), this); + action->setShortcut(tr("Ctrl+N")); + action->setStatusTip(tr("Clear the current Sonic Visualiser session and start a new one")); + connect(action, SIGNAL(triggered()), this, SLOT(newSession())); + menu->addAction(action); + toolbar->addAction(action); + + icon = QIcon(":icons/fileopen.png"); + icon.addFile(":icons/fileopen-22.png"); + + action = new QAction(icon, tr("&Open Session..."), this); + action->setShortcut(tr("Ctrl+O")); + action->setStatusTip(tr("Open a previously saved Sonic Visualiser session file")); + connect(action, SIGNAL(triggered()), this, SLOT(openSession())); + menu->addAction(action); + + action = new QAction(icon, tr("&Open..."), this); + action->setStatusTip(tr("Open a session file, audio file, or layer")); + connect(action, SIGNAL(triggered()), this, SLOT(openSomething())); + toolbar->addAction(action); + + icon = QIcon(":icons/filesave.png"); + icon.addFile(":icons/filesave-22.png"); + action = new QAction(icon, tr("&Save Session"), this); + action->setShortcut(tr("Ctrl+S")); + action->setStatusTip(tr("Save the current session into a Sonic Visualiser session file")); + connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); + connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + toolbar->addAction(action); + + icon = QIcon(":icons/filesaveas.png"); + icon.addFile(":icons/filesaveas-22.png"); + action = new QAction(icon, tr("Save Session &As..."), this); + action->setStatusTip(tr("Save the current session into a new Sonic Visualiser session file")); + connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); + menu->addAction(action); + toolbar->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("&Import Audio File..."), this); + action->setShortcut(tr("Ctrl+I")); + action->setStatusTip(tr("Import an existing audio file")); + connect(action, SIGNAL(triggered()), this, SLOT(importAudio())); + menu->addAction(action); + + action = new QAction(tr("Import Secondary Audio File..."), this); + action->setShortcut(tr("Ctrl+Shift+I")); + action->setStatusTip(tr("Import an extra audio file as a separate layer")); + connect(action, SIGNAL(triggered()), this, SLOT(importMoreAudio())); + connect(this, SIGNAL(canImportMoreAudio(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("&Export Audio File..."), this); + action->setStatusTip(tr("Export selection as an audio file")); + connect(action, SIGNAL(triggered()), this, SLOT(exportAudio())); + connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("Import Annotation &Layer..."), this); + action->setShortcut(tr("Ctrl+L")); + action->setStatusTip(tr("Import layer data from an existing file")); + connect(action, SIGNAL(triggered()), this, SLOT(importLayer())); + connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Export Annotation Layer..."), this); + action->setStatusTip(tr("Export layer data to a file")); + connect(action, SIGNAL(triggered()), this, SLOT(exportLayer())); + connect(this, SIGNAL(canExportLayer(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + m_recentFilesMenu = menu->addMenu(tr("&Recent Files")); + menu->addMenu(m_recentFilesMenu); + setupRecentFilesMenu(); + connect(RecentFiles::getInstance(), SIGNAL(recentFilesChanged()), + this, SLOT(setupRecentFilesMenu())); + + menu->addSeparator(); + action = new QAction(tr("&Preferences..."), this); + action->setStatusTip(tr("Adjust the application preferences")); + connect(action, SIGNAL(triggered()), this, SLOT(preferences())); + menu->addAction(action); + + /*!!! + menu->addSeparator(); + + action = new QAction(tr("Play / Pause"), this); + action->setShortcut(tr("Space")); + action->setStatusTip(tr("Start or stop playback from the current position")); + connect(action, SIGNAL(triggered()), this, SLOT(play())); + menu->addAction(action); + */ + + menu->addSeparator(); + action = new QAction(QIcon(":/icons/exit.png"), + tr("&Quit"), this); + action->setShortcut(tr("Ctrl+Q")); + connect(action, SIGNAL(triggered()), this, SLOT(close())); + menu->addAction(action); + + menu = menuBar()->addMenu(tr("&Edit")); + CommandHistory::getInstance()->registerMenu(menu); + + menu->addSeparator(); + + action = new QAction(QIcon(":/icons/editcut.png"), + tr("Cu&t"), this); + action->setShortcut(tr("Ctrl+X")); + connect(action, SIGNAL(triggered()), this, SLOT(cut())); + connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(QIcon(":/icons/editcopy.png"), + tr("&Copy"), this); + action->setShortcut(tr("Ctrl+C")); + connect(action, SIGNAL(triggered()), this, SLOT(copy())); + connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(QIcon(":/icons/editpaste.png"), + tr("&Paste"), this); + action->setShortcut(tr("Ctrl+V")); + connect(action, SIGNAL(triggered()), this, SLOT(paste())); + connect(this, SIGNAL(canPaste(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("&Delete Selected Items"), this); + action->setShortcut(tr("Del")); + connect(action, SIGNAL(triggered()), this, SLOT(deleteSelected())); + connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + menu->addSeparator(); + m_rightButtonMenu->addSeparator(); + + action = new QAction(tr("Select &All"), this); + action->setShortcut(tr("Ctrl+A")); + connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); + connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("Select &Visible Range"), this); + action->setShortcut(tr("Ctrl+Shift+A")); + connect(action, SIGNAL(triggered()), this, SLOT(selectVisible())); + connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Select to &Start"), this); + action->setShortcut(tr("Shift+Left")); + connect(action, SIGNAL(triggered()), this, SLOT(selectToStart())); + connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Select to &End"), this); + action->setShortcut(tr("Shift+Right")); + connect(action, SIGNAL(triggered()), this, SLOT(selectToEnd())); + connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("C&lear Selection"), this); + action->setShortcut(tr("Esc")); + connect(action, SIGNAL(triggered()), this, SLOT(clearSelection())); + connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("&Insert Instant at Playback Position"), this); + action->setShortcut(tr("Enter")); + connect(action, SIGNAL(triggered()), this, SLOT(insertInstant())); + connect(this, SIGNAL(canInsertInstant(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu = menuBar()->addMenu(tr("&View")); + + QActionGroup *overlayGroup = new QActionGroup(this); + + action = new QAction(tr("&No Text Overlays"), this); + action->setShortcut(tr("0")); + action->setStatusTip(tr("Show no texts for frame times, layer names etc")); + connect(action, SIGNAL(triggered()), this, SLOT(showNoOverlays())); + action->setCheckable(true); + action->setChecked(false); + overlayGroup->addAction(action); + menu->addAction(action); + + action = new QAction(tr("Basic &Text Overlays"), this); + action->setShortcut(tr("9")); + action->setStatusTip(tr("Show texts for frame times etc, but not layer names etc")); + connect(action, SIGNAL(triggered()), this, SLOT(showBasicOverlays())); + action->setCheckable(true); + action->setChecked(true); + overlayGroup->addAction(action); + menu->addAction(action); + + action = new QAction(tr("&All Text Overlays"), this); + action->setShortcut(tr("8")); + action->setStatusTip(tr("Show texts for frame times, layer names etc")); + connect(action, SIGNAL(triggered()), this, SLOT(showAllOverlays())); + action->setCheckable(true); + action->setChecked(false); + overlayGroup->addAction(action); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("Scroll &Left"), this); + action->setShortcut(tr("Left")); + action->setStatusTip(tr("Scroll the current pane to the left")); + connect(action, SIGNAL(triggered()), this, SLOT(scrollLeft())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Scroll &Right"), this); + action->setShortcut(tr("Right")); + action->setStatusTip(tr("Scroll the current pane to the right")); + connect(action, SIGNAL(triggered()), this, SLOT(scrollRight())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Jump Left"), this); + action->setShortcut(tr("Ctrl+Left")); + action->setStatusTip(tr("Scroll the current pane a big step to the left")); + connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Jump Right"), this); + action->setShortcut(tr("Ctrl+Right")); + action->setStatusTip(tr("Scroll the current pane a big step to the right")); + connect(action, SIGNAL(triggered()), this, SLOT(jumpRight())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(QIcon(":/icons/zoom-in.png"), + tr("Zoom &In"), this); + action->setShortcut(tr("Up")); + action->setStatusTip(tr("Increase the zoom level")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomIn())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(QIcon(":/icons/zoom-out.png"), + tr("Zoom &Out"), this); + action->setShortcut(tr("Down")); + action->setStatusTip(tr("Decrease the zoom level")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomOut())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Restore &Default Zoom"), this); + connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Zoom to &Fit"), this); + action->setStatusTip(tr("Zoom to show the whole file")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomToFit())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + +/*!!! This one doesn't work properly yet + + menu->addSeparator(); + + action = new QAction(tr("Show &Layer Hierarchy"), this); + action->setShortcut(tr("Alt+L")); + connect(action, SIGNAL(triggered()), this, SLOT(showLayerTree())); + menu->addAction(action); +*/ + } + + if (m_paneMenu) { + m_paneActions.clear(); + m_paneMenu->clear(); + } else { + m_paneMenu = menuBar()->addMenu(tr("&Pane")); + } + + if (m_layerMenu) { + m_layerTransformActions.clear(); + m_layerActions.clear(); + m_layerMenu->clear(); + } else { + m_layerMenu = menuBar()->addMenu(tr("&Layer")); + } + + TransformFactory::TransformList transforms = + TransformFactory::getInstance()->getAllTransforms(); + + std::vector<QString> types = + TransformFactory::getInstance()->getAllTransformTypes(); + + std::map<QString, QMenu *> transformMenus; + + for (std::vector<QString>::iterator i = types.begin(); i != types.end(); ++i) { + transformMenus[*i] = m_layerMenu->addMenu(*i); + m_rightButtonLayerMenu->addMenu(transformMenus[*i]); + } + + for (unsigned int i = 0; i < transforms.size(); ++i) { + + QString description = transforms[i].description; + if (description == "") description = transforms[i].name; + + QString actionText = description; + if (transforms[i].configurable) { + actionText = QString("%1...").arg(actionText); + } + + action = new QAction(actionText, this); + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + m_layerTransformActions[action] = transforms[i].name; + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + transformMenus[transforms[i].type]->addAction(action); + } + + m_rightButtonLayerMenu->addSeparator(); + + menu = m_paneMenu; + + action = new QAction(QIcon(":/icons/pane.png"), tr("Add &New Pane"), this); + action->setShortcut(tr("Alt+N")); + action->setStatusTip(tr("Add a new pane containing only a time ruler")); + connect(action, SIGNAL(triggered()), this, SLOT(addPane())); + connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool))); + m_paneActions[action] = PaneConfiguration(LayerFactory::TimeRuler); + menu->addAction(action); + + menu->addSeparator(); + + menu = m_layerMenu; + + menu->addSeparator(); + + LayerFactory::LayerTypeSet emptyLayerTypes = + LayerFactory::getInstance()->getValidEmptyLayerTypes(); + + for (LayerFactory::LayerTypeSet::iterator i = emptyLayerTypes.begin(); + i != emptyLayerTypes.end(); ++i) { + + QIcon icon; + QString mainText, tipText, channelText; + LayerFactory::LayerType type = *i; + QString name = LayerFactory::getInstance()->getLayerPresentationName(type); + + icon = QIcon(QString(":/icons/%1.png") + .arg(LayerFactory::getInstance()->getLayerIconName(type))); + + mainText = tr("Add New %1 Layer").arg(name); + tipText = tr("Add a new empty layer of type %1").arg(name); + + action = new QAction(icon, mainText, this); + action->setStatusTip(tipText); + + if (type == LayerFactory::Text) { + action->setShortcut(tr("Alt+T")); + } + + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + m_layerActions[action] = type; + menu->addAction(action); + m_rightButtonLayerMenu->addAction(action); + } + + m_rightButtonLayerMenu->addSeparator(); + menu->addSeparator(); + + int channels = 1; + if (getMainModel()) channels = getMainModel()->getChannelCount(); + + if (channels < 1) channels = 1; + + LayerFactory::LayerType backgroundTypes[] = { + LayerFactory::Waveform, + LayerFactory::Spectrogram, + LayerFactory::MelodicRangeSpectrogram, + LayerFactory::PeakFrequencySpectrogram + }; + + for (unsigned int i = 0; + i < sizeof(backgroundTypes)/sizeof(backgroundTypes[0]); ++i) { + + for (int menuType = 0; menuType <= 1; ++menuType) { // pane, layer + + if (menuType == 0) menu = m_paneMenu; + else menu = m_layerMenu; + + QMenu *submenu = 0; + + for (int c = 0; c <= channels; ++c) { + + if (c == 1 && channels == 1) continue; + bool isDefault = (c == 0); + bool isOnly = (isDefault && (channels == 1)); + + if (menuType == 1) { + if (isDefault) isOnly = true; + else continue; + } + + QIcon icon; + QString mainText, shortcutText, tipText, channelText; + LayerFactory::LayerType type = backgroundTypes[i]; + bool mono = true; + + switch (type) { + + case LayerFactory::Waveform: + icon = QIcon(":/icons/waveform.png"); + mainText = tr("Add &Waveform"); + if (menuType == 0) { + shortcutText = tr("Alt+W"); + tipText = tr("Add a new pane showing a waveform view"); + } else { + tipText = tr("Add a new layer showing a waveform view"); + } + mono = false; + break; + + case LayerFactory::Spectrogram: + mainText = tr("Add &Spectrogram"); + if (menuType == 0) { + shortcutText = tr("Alt+S"); + tipText = tr("Add a new pane showing a dB spectrogram"); + } else { + tipText = tr("Add a new layer showing a dB spectrogram"); + } + break; + + case LayerFactory::MelodicRangeSpectrogram: + mainText = tr("Add &Melodic Range Spectrogram"); + if (menuType == 0) { + shortcutText = tr("Alt+M"); + tipText = tr("Add a new pane showing a spectrogram set up for a pitch overview"); + } else { + tipText = tr("Add a new layer showing a spectrogram set up for a pitch overview"); + } + break; + + case LayerFactory::PeakFrequencySpectrogram: + mainText = tr("Add &Peak Frequency Spectrogram"); + if (menuType == 0) { + shortcutText = tr("Alt+P"); + tipText = tr("Add a new pane showing a spectrogram set up for tracking frequencies"); + } else { + tipText = tr("Add a new layer showing a spectrogram set up for tracking frequencies"); + } + break; + + default: break; + } + + if (isOnly) { + + action = new QAction(icon, mainText, this); + action->setShortcut(shortcutText); + action->setStatusTip(tipText); + if (menuType == 0) { + connect(action, SIGNAL(triggered()), this, SLOT(addPane())); + connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool))); + m_paneActions[action] = PaneConfiguration(type); + } else { + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + m_layerActions[action] = type; + } + menu->addAction(action); + + } else { + + QString actionText; + if (c == 0) + if (mono) actionText = tr("&All Channels Mixed"); + else actionText = tr("&All Channels"); + else actionText = tr("Channel &%1").arg(c); + + if (!submenu) { + submenu = menu->addMenu(mainText); + } + + action = new QAction(icon, actionText, this); + if (isDefault) action->setShortcut(shortcutText); + action->setStatusTip(tipText); + if (menuType == 0) { + connect(action, SIGNAL(triggered()), this, SLOT(addPane())); + connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool))); + m_paneActions[action] = PaneConfiguration(type, c - 1); + } else { + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + m_layerActions[action] = type; + } + submenu->addAction(action); + } + } + } + } + + menu = m_paneMenu; + + menu->addSeparator(); + + action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Pane"), this); + action->setShortcut(tr("Alt+D")); + action->setStatusTip(tr("Delete the currently selected pane")); + connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentPane())); + connect(this, SIGNAL(canDeleteCurrentPane(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu = m_layerMenu; + + action = new QAction(QIcon(":/icons/timeruler.png"), tr("Add &Time Ruler"), this); + action->setStatusTip(tr("Add a new layer showing a time ruler")); + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + m_layerActions[action] = LayerFactory::TimeRuler; + menu->addAction(action); + + menu->addSeparator(); + + m_existingLayersMenu = menu->addMenu(tr("Add &Existing Layer")); + m_rightButtonLayerMenu->addMenu(m_existingLayersMenu); + setupExistingLayersMenu(); + + m_rightButtonLayerMenu->addSeparator(); + menu->addSeparator(); + + action = new QAction(tr("&Rename Layer..."), this); + action->setShortcut(tr("Alt+R")); + action->setStatusTip(tr("Rename the currently active layer")); + connect(action, SIGNAL(triggered()), this, SLOT(renameCurrentLayer())); + connect(this, SIGNAL(canRenameLayer(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonLayerMenu->addAction(action); + + action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Layer"), this); + action->setShortcut(tr("Alt+Shift+D")); + action->setStatusTip(tr("Delete the currently active layer")); + connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentLayer())); + connect(this, SIGNAL(canDeleteCurrentLayer(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonLayerMenu->addAction(action); + + if (!m_mainMenusCreated) { + + menu = menuBar()->addMenu(tr("&Help")); + + action = new QAction(tr("&Help Reference"), this); + action->setStatusTip(tr("Open the Sonic Visualiser reference manual")); + connect(action, SIGNAL(triggered()), this, SLOT(help())); + menu->addAction(action); + + action = new QAction(tr("Sonic Visualiser on the &Web"), this); + action->setStatusTip(tr("Open the Sonic Visualiser website")); + connect(action, SIGNAL(triggered()), this, SLOT(website())); + menu->addAction(action); + + action = new QAction(tr("&About Sonic Visualiser"), this); + action->setStatusTip(tr("Show information about Sonic Visualiser")); + connect(action, SIGNAL(triggered()), this, SLOT(about())); + menu->addAction(action); +/* + action = new QAction(tr("About &Qt"), this); + action->setStatusTip(tr("Show information about Qt")); + connect(action, SIGNAL(triggered()), + QApplication::getInstance(), SLOT(aboutQt())); + menu->addAction(action); +*/ + } + + m_mainMenusCreated = true; +} + +void +MainWindow::setupRecentFilesMenu() +{ + m_recentFilesMenu->clear(); + std::vector<QString> files = RecentFiles::getInstance()->getRecentFiles(); + for (size_t i = 0; i < files.size(); ++i) { + QAction *action = new QAction(files[i], this); + connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); + m_recentFilesMenu->addAction(action); + } +} + +void +MainWindow::setupExistingLayersMenu() +{ + if (!m_existingLayersMenu) return; // should have been created by setupMenus + +// std::cerr << "MainWindow::setupExistingLayersMenu" << std::endl; + + m_existingLayersMenu->clear(); + m_existingLayerActions.clear(); + + std::vector<Layer *> orderedLayers; + std::set<Layer *> observedLayers; + + for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { + + Pane *pane = m_paneStack->getPane(i); + if (!pane) continue; + + for (int j = 0; j < pane->getLayerCount(); ++j) { + + Layer *layer = pane->getLayer(j); + if (!layer) continue; + if (observedLayers.find(layer) != observedLayers.end()) { + std::cerr << "found duplicate layer " << layer << std::endl; + continue; + } + +// std::cerr << "found new layer " << layer << " (name = " +// << layer->getLayerPresentationName().toStdString() << ")" << std::endl; + + orderedLayers.push_back(layer); + observedLayers.insert(layer); + } + } + + std::map<QString, int> observedNames; + + for (int i = 0; i < orderedLayers.size(); ++i) { + + QString name = orderedLayers[i]->getLayerPresentationName(); + int n = ++observedNames[name]; + if (n > 1) name = QString("%1 <%2>").arg(name).arg(n); + + QAction *action = new QAction(name, this); + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + m_existingLayerActions[action] = orderedLayers[i]; + + m_existingLayersMenu->addAction(action); + } +} + +void +MainWindow::setupToolbars() +{ + QToolBar *toolbar = addToolBar(tr("Transport Toolbar")); + + QAction *action = toolbar->addAction(QIcon(":/icons/rewind-start.png"), + tr("Rewind to Start")); + action->setShortcut(tr("Home")); + action->setStatusTip(tr("Rewind to the start")); + connect(action, SIGNAL(triggered()), this, SLOT(rewindStart())); + connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/rewind.png"), + tr("Rewind")); + action->setShortcut(tr("PageUp")); + action->setStatusTip(tr("Rewind to the previous time instant in the current layer")); + connect(action, SIGNAL(triggered()), this, SLOT(rewind())); + connect(this, SIGNAL(canRewind(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/playpause.png"), + tr("Play / Pause")); + action->setCheckable(true); + action->setShortcut(tr("Space")); + action->setStatusTip(tr("Start or stop playback from the current position")); + connect(action, SIGNAL(triggered()), this, SLOT(play())); + connect(m_playSource, SIGNAL(playStatusChanged(bool)), + action, SLOT(setChecked(bool))); + connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/ffwd.png"), + tr("Fast Forward")); + action->setShortcut(tr("PageDown")); + action->setStatusTip(tr("Fast forward to the next time instant in the current layer")); + connect(action, SIGNAL(triggered()), this, SLOT(ffwd())); + connect(this, SIGNAL(canFfwd(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/ffwd-end.png"), + tr("Fast Forward to End")); + action->setShortcut(tr("End")); + action->setStatusTip(tr("Fast-forward to the end")); + connect(action, SIGNAL(triggered()), this, SLOT(ffwdEnd())); + connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); + + toolbar = addToolBar(tr("Play Mode Toolbar")); + + action = toolbar->addAction(QIcon(":/icons/playselection.png"), + tr("Constrain Playback to Selection")); + action->setCheckable(true); + action->setChecked(m_viewManager->getPlaySelectionMode()); + action->setShortcut(tr("s")); + action->setStatusTip(tr("Constrain playback to the selected area")); + connect(action, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); + connect(this, SIGNAL(canPlaySelection(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/playloop.png"), + tr("Loop Playback")); + action->setCheckable(true); + action->setChecked(m_viewManager->getPlayLoopMode()); + action->setShortcut(tr("l")); + action->setStatusTip(tr("Loop playback")); + connect(action, SIGNAL(triggered()), this, SLOT(playLoopToggled())); + connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); + + toolbar = addToolBar(tr("Edit Toolbar")); + CommandHistory::getInstance()->registerToolbar(toolbar); + + toolbar = addToolBar(tr("Tools Toolbar")); + QActionGroup *group = new QActionGroup(this); + + action = toolbar->addAction(QIcon(":/icons/navigate.png"), + tr("Navigate")); + action->setCheckable(true); + action->setChecked(true); + action->setShortcut(tr("1")); + connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); + group->addAction(action); + m_toolActions[ViewManager::NavigateMode] = action; + + action = toolbar->addAction(QIcon(":/icons/select.png"), + tr("Select")); + action->setCheckable(true); + action->setShortcut(tr("2")); + connect(action, SIGNAL(triggered()), this, SLOT(toolSelectSelected())); + group->addAction(action); + m_toolActions[ViewManager::SelectMode] = action; + + action = toolbar->addAction(QIcon(":/icons/move.png"), + tr("Edit")); + action->setCheckable(true); + action->setShortcut(tr("3")); + connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); + connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); + group->addAction(action); + m_toolActions[ViewManager::EditMode] = action; + + action = toolbar->addAction(QIcon(":/icons/draw.png"), + tr("Draw")); + action->setCheckable(true); + action->setShortcut(tr("4")); + connect(action, SIGNAL(triggered()), this, SLOT(toolDrawSelected())); + connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); + group->addAction(action); + m_toolActions[ViewManager::DrawMode] = action; + +// action = toolbar->addAction(QIcon(":/icons/text.png"), +// tr("Text")); +// action->setCheckable(true); +// action->setShortcut(tr("5")); +// connect(action, SIGNAL(triggered()), this, SLOT(toolTextSelected())); +// group->addAction(action); +// m_toolActions[ViewManager::TextMode] = action; + + toolNavigateSelected(); +} + +void +MainWindow::updateMenuStates() +{ + bool haveCurrentPane = + (m_paneStack && + (m_paneStack->getCurrentPane() != 0)); + bool haveCurrentLayer = + (haveCurrentPane && + (m_paneStack->getCurrentPane()->getSelectedLayer())); + bool haveMainModel = + (getMainModel() != 0); + bool havePlayTarget = + (m_playTarget != 0); + bool haveSelection = + (m_viewManager && + !m_viewManager->getSelections().empty()); + bool haveCurrentEditableLayer = + (haveCurrentLayer && + m_paneStack->getCurrentPane()->getSelectedLayer()-> + isLayerEditable()); + bool haveCurrentTimeInstantsLayer = + (haveCurrentLayer && + dynamic_cast<TimeInstantLayer *> + (m_paneStack->getCurrentPane()->getSelectedLayer())); + bool haveCurrentTimeValueLayer = + (haveCurrentLayer && + dynamic_cast<TimeValueLayer *> + (m_paneStack->getCurrentPane()->getSelectedLayer())); + bool haveCurrentColour3DPlot = + (haveCurrentLayer && + dynamic_cast<Colour3DPlotLayer *> + (m_paneStack->getCurrentPane()->getSelectedLayer())); + bool haveClipboardContents = + (m_viewManager && + !m_viewManager->getClipboard().empty()); + + emit canAddPane(haveMainModel); + emit canDeleteCurrentPane(haveCurrentPane); + emit canZoom(haveMainModel && haveCurrentPane); + emit canScroll(haveMainModel && haveCurrentPane); + emit canAddLayer(haveMainModel && haveCurrentPane); + emit canImportMoreAudio(haveMainModel); + emit canImportLayer(haveMainModel && haveCurrentPane); + emit canExportAudio(haveMainModel); + emit canExportLayer(haveMainModel && + (haveCurrentEditableLayer || haveCurrentColour3DPlot)); + emit canDeleteCurrentLayer(haveCurrentLayer); + emit canRenameLayer(haveCurrentLayer); + emit canEditLayer(haveCurrentEditableLayer); + emit canSelect(haveMainModel && haveCurrentPane); + emit canPlay(/*!!! haveMainModel && */ havePlayTarget); + emit canFfwd(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer); + emit canRewind(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer); + emit canPaste(haveCurrentEditableLayer && haveClipboardContents); + emit canInsertInstant(haveCurrentPane); + emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection); + emit canClearSelection(haveSelection); + emit canEditSelection(haveSelection && haveCurrentEditableLayer); + emit canSave(m_sessionFile != "" && m_documentModified); +} + +void +MainWindow::updateDescriptionLabel() +{ + if (!getMainModel()) { + m_descriptionLabel->setText(tr("No audio file loaded.")); + return; + } + + QString description; + + size_t ssr = getMainModel()->getSampleRate(); + size_t tsr = ssr; + if (m_playSource) tsr = m_playSource->getTargetSampleRate(); + + if (ssr != tsr) { + description = tr("%1Hz (resampling to %2Hz)").arg(ssr).arg(tsr); + } else { + description = QString("%1Hz").arg(ssr); + } + + description = QString("%1 - %2") + .arg(RealTime::frame2RealTime(getMainModel()->getEndFrame(), ssr) + .toText(false).c_str()) + .arg(description); + + m_descriptionLabel->setText(description); +} + +void +MainWindow::documentModified() +{ +// std::cerr << "MainWindow::documentModified" << std::endl; + + if (!m_documentModified) { + setWindowTitle(tr("%1 (modified)").arg(windowTitle())); + } + + m_documentModified = true; + updateMenuStates(); +} + +void +MainWindow::documentRestored() +{ +// std::cerr << "MainWindow::documentRestored" << std::endl; + + if (m_documentModified) { + QString wt(windowTitle()); + wt.replace(tr(" (modified)"), ""); + setWindowTitle(wt); + } + + m_documentModified = false; + updateMenuStates(); +} + +void +MainWindow::playLoopToggled() +{ + QAction *action = dynamic_cast<QAction *>(sender()); + + if (action) { + m_viewManager->setPlayLoopMode(action->isChecked()); + } else { + m_viewManager->setPlayLoopMode(!m_viewManager->getPlayLoopMode()); + } +} + +void +MainWindow::playSelectionToggled() +{ + QAction *action = dynamic_cast<QAction *>(sender()); + + if (action) { + m_viewManager->setPlaySelectionMode(action->isChecked()); + } else { + m_viewManager->setPlaySelectionMode(!m_viewManager->getPlaySelectionMode()); + } +} + +void +MainWindow::currentPaneChanged(Pane *) +{ + updateMenuStates(); +} + +void +MainWindow::currentLayerChanged(Pane *, Layer *) +{ + updateMenuStates(); +} + +void +MainWindow::toolNavigateSelected() +{ + m_viewManager->setToolMode(ViewManager::NavigateMode); +} + +void +MainWindow::toolSelectSelected() +{ + m_viewManager->setToolMode(ViewManager::SelectMode); +} + +void +MainWindow::toolEditSelected() +{ + m_viewManager->setToolMode(ViewManager::EditMode); +} + +void +MainWindow::toolDrawSelected() +{ + m_viewManager->setToolMode(ViewManager::DrawMode); +} + +//void +//MainWindow::toolTextSelected() +//{ +// m_viewManager->setToolMode(ViewManager::TextMode); +//} + +void +MainWindow::selectAll() +{ + if (!getMainModel()) return; + m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), + getMainModel()->getEndFrame())); +} + +void +MainWindow::selectToStart() +{ + if (!getMainModel()) return; + m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), + m_viewManager->getGlobalCentreFrame())); +} + +void +MainWindow::selectToEnd() +{ + if (!getMainModel()) return; + m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(), + getMainModel()->getEndFrame())); +} + +void +MainWindow::selectVisible() +{ + Model *model = getMainModel(); + if (!model) return; + + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + size_t startFrame, endFrame; + + if (currentPane->getStartFrame() < 0) startFrame = 0; + else startFrame = currentPane->getStartFrame(); + + if (currentPane->getEndFrame() > model->getEndFrame()) endFrame = model->getEndFrame(); + else endFrame = currentPane->getEndFrame(); + + m_viewManager->setSelection(Selection(startFrame, endFrame)); +} + +void +MainWindow::clearSelection() +{ + m_viewManager->clearSelections(); +} + +void +MainWindow::cut() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + Layer *layer = currentPane->getSelectedLayer(); + if (!layer) return; + + Clipboard &clipboard = m_viewManager->getClipboard(); + clipboard.clear(); + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + CommandHistory::getInstance()->startCompoundOperation(tr("Cut"), true); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + layer->copy(*i, clipboard); + layer->deleteSelection(*i); + } + + CommandHistory::getInstance()->endCompoundOperation(); +} + +void +MainWindow::copy() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + Layer *layer = currentPane->getSelectedLayer(); + if (!layer) return; + + Clipboard &clipboard = m_viewManager->getClipboard(); + clipboard.clear(); + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + layer->copy(*i, clipboard); + } +} + +void +MainWindow::paste() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + //!!! if we have no current layer, we should create one of the most + // appropriate type + + Layer *layer = currentPane->getSelectedLayer(); + if (!layer) return; + + Clipboard &clipboard = m_viewManager->getClipboard(); + Clipboard::PointList contents = clipboard.getPoints(); +/* + long minFrame = 0; + bool have = false; + for (int i = 0; i < contents.size(); ++i) { + if (!contents[i].haveFrame()) continue; + if (!have || contents[i].getFrame() < minFrame) { + minFrame = contents[i].getFrame(); + have = true; + } + } + + long frameOffset = long(m_viewManager->getGlobalCentreFrame()) - minFrame; + + layer->paste(clipboard, frameOffset); +*/ + layer->paste(clipboard, 0, true); +} + +void +MainWindow::deleteSelected() +{ + if (m_paneStack->getCurrentPane() && + m_paneStack->getCurrentPane()->getSelectedLayer()) { + + MultiSelection::SelectionList selections = + m_viewManager->getSelections(); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + + m_paneStack->getCurrentPane()->getSelectedLayer()->deleteSelection(*i); + } + } +} + +void +MainWindow::insertInstant() +{ + int frame = m_viewManager->getPlaybackFrame(); + + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) { + return; + } + + Layer *layer = dynamic_cast<TimeInstantLayer *> + (pane->getSelectedLayer()); + + if (!layer) { + for (int i = pane->getLayerCount(); i > 0; --i) { + layer = dynamic_cast<TimeInstantLayer *>(pane->getLayer(i - 1)); + if (layer) break; + } + + if (!layer) { + CommandHistory::getInstance()->startCompoundOperation + (tr("Add Point"), true); + layer = m_document->createEmptyLayer(LayerFactory::TimeInstants); + if (layer) { + m_document->addLayerToView(pane, layer); + m_paneStack->setCurrentLayer(pane, layer); + } + CommandHistory::getInstance()->endCompoundOperation(); + } + } + + if (layer) { + + Model *model = layer->getModel(); + SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *> + (model); + + if (sodm) { + SparseOneDimensionalModel::Point point + (frame, QString("%1").arg(sodm->getPointCount() + 1)); + CommandHistory::getInstance()->addCommand + (new SparseOneDimensionalModel::AddPointCommand(sodm, point, + tr("Add Points")), + true, true); // bundled + } + } +} + +void +MainWindow::importAudio() +{ + QString orig = m_audioFile; + +// std::cerr << "orig = " << orig.toStdString() << std::endl; + + if (orig == "") orig = "."; + + QString path = QFileDialog::getOpenFileName + (this, tr("Select an audio file"), orig, + tr("Audio files (%1)\nAll files (*.*)") + .arg(AudioFileReaderFactory::getKnownExtensions())); + + if (path != "") { + if (!openAudioFile(path, ReplaceMainModel)) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Audio file \"%1\" could not be opened").arg(path)); + } + } +} + +void +MainWindow::importMoreAudio() +{ + QString orig = m_audioFile; + +// std::cerr << "orig = " << orig.toStdString() << std::endl; + + if (orig == "") orig = "."; + + QString path = QFileDialog::getOpenFileName + (this, tr("Select an audio file"), orig, + tr("Audio files (%1)\nAll files (*.*)") + .arg(AudioFileReaderFactory::getKnownExtensions())); + + if (path != "") { + if (!openAudioFile(path, CreateAdditionalModel)) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Audio file \"%1\" could not be opened").arg(path)); + } + } +} + +void +MainWindow::exportAudio() +{ + if (!getMainModel()) return; + + QString path = QFileDialog::getSaveFileName + (this, tr("Select a file to export to"), ".", + tr("WAV audio files (*.wav)\nAll files (*.*)")); + + if (path == "") return; + + if (!path.endsWith(".wav")) path = path + ".wav"; + + bool ok = false; + QString error; + + WavFileWriter *writer = 0; + MultiSelection ms = m_viewManager->getSelection(); + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + bool multiple = false; + + if (selections.empty()) { + + writer = new WavFileWriter(path, getMainModel()->getSampleRate(), + getMainModel(), 0); + + } else if (selections.size() == 1) { + + QStringList items; + items << tr("Export the selected region only") + << tr("Export the whole audio file"); + + bool ok = false; + QString item = ListInputDialog::getItem + (this, tr("Select region to export"), + tr("Which region from the original audio file do you want to export?"), + items, 0, &ok); + + if (!ok || item.isEmpty()) return; + + if (item == items[0]) { + + writer = new WavFileWriter(path, getMainModel()->getSampleRate(), + getMainModel(), &ms); + + } else { + + writer = new WavFileWriter(path, getMainModel()->getSampleRate(), + getMainModel(), 0); + } + } else { + + QStringList items; + items << tr("Export the selected regions into a single audio file") + << tr("Export the selected regions into separate files") + << tr("Export the whole audio file"); + + bool ok = false; + QString item = ListInputDialog::getItem + (this, tr("Select region to export"), + tr("Multiple regions of the original audio file are selected.\nWhat do you want to export?"), + items, 0, &ok); + + if (!ok || item.isEmpty()) return; + + if (item == items[0]) { + + writer = new WavFileWriter(path, getMainModel()->getSampleRate(), + getMainModel(), &ms); + + } else if (item == items[2]) { + + writer = new WavFileWriter(path, getMainModel()->getSampleRate(), + getMainModel(), 0); + + } else { + + multiple = true; + + int n = 1; + QString base = path; + base.replace(".wav", ""); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + + MultiSelection subms; + subms.setSelection(*i); + + QString subpath = QString("%1.%2.wav").arg(base).arg(n); + ++n; + + if (QFileInfo(subpath).exists()) { + error = tr("Fragment file %1 already exists, aborting").arg(subpath); + break; + } + + WavFileWriter subwriter(subpath, getMainModel()->getSampleRate(), + getMainModel(), &subms); + subwriter.write(); + ok = subwriter.isOK(); + + if (!ok) { + error = subwriter.getError(); + break; + } + } + } + } + + if (writer) { + writer->write(); + ok = writer->isOK(); + error = writer->getError(); + delete writer; + } + + if (ok) { + if (!multiple) { + RecentFiles::getInstance()->addFile(path); + } + } else { + QMessageBox::critical(this, tr("Failed to write file"), error); + } +} + +void +MainWindow::importLayer() +{ + Pane *pane = m_paneStack->getCurrentPane(); + + if (!pane) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindow::importLayer: no current pane" << std::endl; + return; + } + + if (!getMainModel()) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindow::importLayer: No main model -- hence no default sample rate available" << std::endl; + return; + } + + QString path = QFileDialog::getOpenFileName + (this, tr("Select file"), ".", + 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())); + + if (path != "") { + + if (!openLayerFile(path)) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("File %1 could not be opened.").arg(path)); + return; + } + } +} + +bool +MainWindow::openLayerFile(QString path) +{ + Pane *pane = m_paneStack->getCurrentPane(); + + if (!pane) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindow::openLayerFile: no current pane" << std::endl; + return false; + } + + if (!getMainModel()) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindow::openLayerFile: No main model -- hence no default sample rate available" << std::endl; + return false; + } + + if (path.endsWith(".svl") || path.endsWith(".xml")) { + + PaneCallback callback(this); + QFile file(path); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + std::cerr << "ERROR: MainWindow::openLayerFile(" + << path.toStdString() + << "): Failed to open file for reading" << std::endl; + return false; + } + + SVFileReader reader(m_document, callback); + reader.setCurrentPane(pane); + + QXmlInputSource inputSource(&file); + reader.parse(inputSource); + + if (!reader.isOK()) { + std::cerr << "ERROR: MainWindow::openLayerFile(" + << path.toStdString() + << "): Failed to read XML file: " + << reader.getErrorString().toStdString() << std::endl; + return false; + } + + RecentFiles::getInstance()->addFile(path); + return true; + + } else { + + Model *model = DataFileReaderFactory::load(path, getMainModel()->getSampleRate()); + + if (model) { + Layer *newLayer = m_document->createImportedLayer(model); + if (newLayer) { + m_document->addLayerToView(pane, newLayer); + RecentFiles::getInstance()->addFile(path); + return true; + } + } + } + + return false; +} + +void +MainWindow::exportLayer() +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) return; + + Layer *layer = pane->getSelectedLayer(); + if (!layer) return; + + Model *model = layer->getModel(); + if (!model) return; + + QString path = QFileDialog::getSaveFileName + (this, tr("Select a file to export to"), ".", + tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)")); + + if (path == "") return; + + if (QFileInfo(path).suffix() == "") path += ".svl"; + + QString error; + + if (path.endsWith(".xml") || path.endsWith(".svl")) { + + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + error = tr("Failed to open file %1 for writing").arg(path); + } else { + QTextStream out(&file); + out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + << "<!DOCTYPE sonic-visualiser>\n" + << "<sv>\n" + << " <data>\n"; + + model->toXml(out, " "); + + out << " </data>\n" + << " <display>\n"; + + layer->toXml(out, " "); + + out << " </display>\n" + << "</sv>\n"; + } + + } else { + + CSVFileWriter writer(path, model, + (path.endsWith(".csv") ? "," : "\t")); + writer.write(); + + if (!writer.isOK()) { + error = writer.getError(); + } + } + + if (error != "") { + QMessageBox::critical(this, tr("Failed to write file"), error); + } else { + RecentFiles::getInstance()->addFile(path); + } +} + +bool +MainWindow::openAudioFile(QString path, AudioFileOpenMode mode) +{ + if (!(QFileInfo(path).exists() && + QFileInfo(path).isFile() && + QFileInfo(path).isReadable())) { + return false; + } + + WaveFileModel *newModel = new WaveFileModel(path); + + if (!newModel->isOK()) { + delete newModel; + return false; + } + + bool setAsMain = true; + static bool prevSetAsMain = true; + + if (mode == CreateAdditionalModel) setAsMain = false; + else if (mode == AskUser) { + if (m_document->getMainModel()) { + + QStringList items; + items << tr("Replace the existing main waveform") + << tr("Load this file into a new waveform pane"); + + bool ok = false; + QString item = ListInputDialog::getItem + (this, tr("Select target for import"), + tr("You already have an audio waveform loaded.\nWhat would you like to do with the new audio file?"), + items, prevSetAsMain ? 0 : 1, &ok); + + if (!ok || item.isEmpty()) { + delete newModel; + return false; + } + + setAsMain = (item == items[0]); + prevSetAsMain = setAsMain; + } + } + + if (setAsMain) { + + Model *prevMain = getMainModel(); + if (prevMain) m_playSource->removeModel(prevMain); + + PlayParameterRepository::getInstance()->clear(); + + // The clear() call will have removed the parameters for the + // main model. Re-add them with the new one. + PlayParameterRepository::getInstance()->addModel(newModel); + + m_document->setMainModel(newModel); + setupMenus(); + + if (m_sessionFile == "") { + setWindowTitle(tr("Sonic Visualiser: %1") + .arg(QFileInfo(path).fileName())); + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + m_documentModified = false; + } else { + setWindowTitle(tr("Sonic Visualiser: %1 [%2]") + .arg(QFileInfo(m_sessionFile).fileName()) + .arg(QFileInfo(path).fileName())); + if (m_documentModified) { + m_documentModified = false; + documentModified(); // so as to restore "(modified)" window title + } + } + + m_audioFile = path; + + } else { // !setAsMain + + CommandHistory::getInstance()->startCompoundOperation + (tr("Import \"%1\"").arg(QFileInfo(path).fileName()), true); + + m_document->addImportedModel(newModel); + + AddPaneCommand *command = new AddPaneCommand(this); + CommandHistory::getInstance()->addCommand(command); + + Pane *pane = command->getPane(); + + if (!m_timeRulerLayer) { + m_timeRulerLayer = m_document->createMainModelLayer + (LayerFactory::TimeRuler); + } + + m_document->addLayerToView(pane, m_timeRulerLayer); + + Layer *newLayer = m_document->createImportedLayer(newModel); + + if (newLayer) { + m_document->addLayerToView(pane, newLayer); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } + + updateMenuStates(); + RecentFiles::getInstance()->addFile(path); + + return true; +} + +void +MainWindow::createPlayTarget() +{ + if (m_playTarget) return; + + m_playTarget = AudioTargetFactory::createCallbackTarget(m_playSource); + if (!m_playTarget) { + QMessageBox::warning + (this, tr("Couldn't open audio device"), + tr("Could not open an audio device for playback.\nAudio playback will not be available during this session.\n"), + QMessageBox::Ok, 0); + } + connect(m_fader, SIGNAL(valueChanged(float)), + m_playTarget, SLOT(setOutputGain(float))); +} + +WaveFileModel * +MainWindow::getMainModel() +{ + if (!m_document) return 0; + return m_document->getMainModel(); +} + +void +MainWindow::newSession() +{ + if (!checkSaveModified()) return; + + closeSession(); + createDocument(); + + Pane *pane = m_paneStack->addPane(); + + if (!m_timeRulerLayer) { + m_timeRulerLayer = m_document->createMainModelLayer + (LayerFactory::TimeRuler); + } + + m_document->addLayerToView(pane, m_timeRulerLayer); + + Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); + m_document->addLayerToView(pane, waveform); + + m_panner->registerView(pane); + + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + updateMenuStates(); +} + +void +MainWindow::createDocument() +{ + m_document = new Document; + + connect(m_document, SIGNAL(layerAdded(Layer *)), + this, SLOT(layerAdded(Layer *))); + connect(m_document, SIGNAL(layerRemoved(Layer *)), + this, SLOT(layerRemoved(Layer *))); + connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)), + this, SLOT(layerAboutToBeDeleted(Layer *))); + connect(m_document, SIGNAL(layerInAView(Layer *, bool)), + this, SLOT(layerInAView(Layer *, bool))); + + connect(m_document, SIGNAL(modelAdded(Model *)), + this, SLOT(modelAdded(Model *))); + connect(m_document, SIGNAL(mainModelChanged(WaveFileModel *)), + this, SLOT(mainModelChanged(WaveFileModel *))); + connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)), + this, SLOT(modelAboutToBeDeleted(Model *))); + + connect(m_document, SIGNAL(modelGenerationFailed(QString)), + this, SLOT(modelGenerationFailed(QString))); + connect(m_document, SIGNAL(modelRegenerationFailed(QString)), + this, SLOT(modelRegenerationFailed(QString))); +} + +void +MainWindow::closeSession() +{ + if (!checkSaveModified()) return; + + while (m_paneStack->getPaneCount() > 0) { + + Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); + + while (pane->getLayerCount() > 0) { + m_document->removeLayerFromView + (pane, pane->getLayer(pane->getLayerCount() - 1)); + } + + m_panner->unregisterView(pane); + m_paneStack->deletePane(pane); + } + + while (m_paneStack->getHiddenPaneCount() > 0) { + + Pane *pane = m_paneStack->getHiddenPane + (m_paneStack->getHiddenPaneCount() - 1); + + while (pane->getLayerCount() > 0) { + m_document->removeLayerFromView + (pane, pane->getLayer(pane->getLayerCount() - 1)); + } + + m_panner->unregisterView(pane); + m_paneStack->deletePane(pane); + } + + delete m_document; + m_document = 0; + m_viewManager->clearSelections(); + m_timeRulerLayer = 0; // document owned this + + m_sessionFile = ""; + setWindowTitle(tr("Sonic Visualiser")); + + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + documentRestored(); +} + +void +MainWindow::openSession() +{ + if (!checkSaveModified()) return; + + QString orig = m_audioFile; + if (orig == "") orig = "."; + else orig = QFileInfo(orig).absoluteDir().canonicalPath(); + + QString path = QFileDialog::getOpenFileName + (this, tr("Select a session file"), orig, + tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)")); + + if (path.isEmpty()) return; + + if (!(QFileInfo(path).exists() && + QFileInfo(path).isFile() && + QFileInfo(path).isReadable())) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("File \"%1\" does not exist or is not a readable file").arg(path)); + return; + } + + if (!openSessionFile(path)) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Session file \"%1\" could not be opened").arg(path)); + } +} + +void +MainWindow::openSomething() +{ + QString orig = m_audioFile; + if (orig == "") orig = "."; + else orig = QFileInfo(orig).absoluteDir().canonicalPath(); + + bool canImportLayer = (getMainModel() != 0 && + m_paneStack != 0 && + m_paneStack->getCurrentPane() != 0); + + QString importSpec; + + if (canImportLayer) { + importSpec = 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()); + } else { + importSpec = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)") + .arg(AudioFileReaderFactory::getKnownExtensions()); + } + + QString path = QFileDialog::getOpenFileName + (this, tr("Select a file to open"), orig, importSpec); + + if (path.isEmpty()) return; + + if (!(QFileInfo(path).exists() && + QFileInfo(path).isFile() && + QFileInfo(path).isReadable())) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("File \"%1\" does not exist or is not a readable file").arg(path)); + return; + } + + if (path.endsWith(".sv")) { + + if (!checkSaveModified()) return; + + if (!openSessionFile(path)) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Session file \"%1\" could not be opened").arg(path)); + } + + } else { + + if (!openAudioFile(path, AskUser)) { + + if (!canImportLayer || !openLayerFile(path)) { + + QMessageBox::critical(this, tr("Failed to open file"), + tr("File \"%1\" could not be opened").arg(path)); + } + } + } +} + +void +MainWindow::openRecentFile() +{ + QObject *obj = sender(); + QAction *action = dynamic_cast<QAction *>(obj); + + if (!action) { + std::cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" + << std::endl; + return; + } + + QString path = action->text(); + if (path == "") return; + + if (path.endsWith("sv")) { + + if (!checkSaveModified()) return ; + + if (!openSessionFile(path)) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Session file \"%1\" could not be opened").arg(path)); + } + + } else { + + if (!openAudioFile(path, AskUser)) { + + bool canImportLayer = (getMainModel() != 0 && + m_paneStack != 0 && + m_paneStack->getCurrentPane() != 0); + + if (!canImportLayer || !openLayerFile(path)) { + + QMessageBox::critical(this, tr("Failed to open file"), + tr("File \"%1\" could not be opened").arg(path)); + } + } + } +} + +bool +MainWindow::openSomeFile(QString path) +{ + if (openAudioFile(path)) { + return true; + } else if (openSessionFile(path)) { + return true; + } else { + return false; + } +} + +bool +MainWindow::openSessionFile(QString path) +{ + BZipFileDevice bzFile(path); + if (!bzFile.open(QIODevice::ReadOnly)) { + std::cerr << "Failed to open session file \"" << path.toStdString() + << "\": " << bzFile.errorString().toStdString() << std::endl; + return false; + } + + QString error; + closeSession(); + createDocument(); + + PaneCallback callback(this); + m_viewManager->clearSelections(); + + SVFileReader reader(m_document, callback); + QXmlInputSource inputSource(&bzFile); + reader.parse(inputSource); + + if (!reader.isOK()) { + error = tr("SV XML file read error:\n%1").arg(reader.getErrorString()); + } + + bzFile.close(); + + bool ok = (error == ""); + + if (ok) { + setWindowTitle(tr("Sonic Visualiser: %1") + .arg(QFileInfo(path).fileName())); + m_sessionFile = path; + setupMenus(); + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + m_documentModified = false; + updateMenuStates(); + RecentFiles::getInstance()->addFile(path); + } else { + setWindowTitle(tr("Sonic Visualiser")); + } + + return ok; +} + +void +MainWindow::closeEvent(QCloseEvent *e) +{ + if (!checkSaveModified()) { + e->ignore(); + return; + } + + e->accept(); + return; +} + +bool +MainWindow::checkSaveModified() +{ + // Called before some destructive operation (e.g. new session, + // exit program). Return true if we can safely proceed, false to + // cancel. + + if (!m_documentModified) return true; + + int button = + QMessageBox::warning(this, + tr("Session modified"), + tr("The current session has been modified.\nDo you want to save it?"), + QMessageBox::Yes, + QMessageBox::No, + QMessageBox::Cancel); + + if (button == QMessageBox::Yes) { + saveSession(); + if (m_documentModified) { // save failed -- don't proceed! + return false; + } else { + return true; // saved, so it's safe to continue now + } + } else if (button == QMessageBox::No) { + m_documentModified = false; // so we know to abandon it + return true; + } + + // else cancel + return false; +} + +void +MainWindow::saveSession() +{ + if (m_sessionFile != "") { + if (!saveSessionFile(m_sessionFile)) { + QMessageBox::critical(this, tr("Failed to save file"), + tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); + } else { + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + } + } else { + saveSessionAs(); + } +} + +void +MainWindow::saveSessionAs() +{ + QString orig = m_audioFile; + if (orig == "") orig = "."; + else orig = QFileInfo(orig).absoluteDir().canonicalPath(); + + QString path; + bool good = false; + + while (!good) { + + path = QFileDialog::getSaveFileName + (this, tr("Select a file to save to"), orig, + tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"), 0, + QFileDialog::DontConfirmOverwrite); // we'll do that + + if (path.isEmpty()) return; + + if (!path.endsWith(".sv")) path = path + ".sv"; + + QFileInfo fi(path); + + if (fi.isDir()) { + QMessageBox::critical(this, tr("Directory selected"), + tr("File \"%1\" is a directory").arg(path)); + continue; + } + + if (fi.exists()) { + if (QMessageBox::question(this, tr("File exists"), + tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), + QMessageBox::Ok, + QMessageBox::Cancel) == QMessageBox::Ok) { + good = true; + } else { + continue; + } + } + + good = true; + } + + if (!saveSessionFile(path)) { + QMessageBox::critical(this, tr("Failed to save file"), + tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); + } else { + setWindowTitle(tr("Sonic Visualiser: %1") + .arg(QFileInfo(path).fileName())); + m_sessionFile = path; + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + RecentFiles::getInstance()->addFile(path); + } +} + +bool +MainWindow::saveSessionFile(QString path) +{ + BZipFileDevice bzFile(path); + if (!bzFile.open(QIODevice::WriteOnly)) { + std::cerr << "Failed to open session file \"" << path.toStdString() + << "\" for writing: " + << bzFile.errorString().toStdString() << std::endl; + return false; + } + + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + QTextStream out(&bzFile); + toXml(out); + out.flush(); + + QApplication::restoreOverrideCursor(); + + if (bzFile.errorString() != "") { + QMessageBox::critical(this, tr("Failed to write file"), + tr("Failed to write to file \"%1\": %2") + .arg(path).arg(bzFile.errorString())); + bzFile.close(); + return false; + } + + bzFile.close(); + return true; +} + +void +MainWindow::toXml(QTextStream &out) +{ + QString indent(" "); + + out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + out << "<!DOCTYPE sonic-visualiser>\n"; + out << "<sv>\n"; + + m_document->toXml(out, "", ""); + + out << "<display>\n"; + + out << QString(" <window width=\"%1\" height=\"%2\"/>\n") + .arg(width()).arg(height()); + + for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { + + Pane *pane = m_paneStack->getPane(i); + + if (pane) { + pane->toXml(out, indent); + } + } + + out << "</display>\n"; + + m_viewManager->getSelection().toXml(out); + + out << "</sv>\n"; +} + +Pane * +MainWindow::addPaneToStack() +{ + AddPaneCommand *command = new AddPaneCommand(this); + CommandHistory::getInstance()->addCommand(command); + return command->getPane(); +} + +void +MainWindow::zoomIn() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->zoom(true); +} + +void +MainWindow::zoomOut() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->zoom(false); +} + +void +MainWindow::zoomToFit() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + Model *model = getMainModel(); + if (!model) return; + + size_t start = model->getStartFrame(); + size_t end = model->getEndFrame(); + size_t pixels = currentPane->width(); + size_t zoomLevel = (end - start) / pixels; + + currentPane->setZoomLevel(zoomLevel); + currentPane->setStartFrame(start); +} + +void +MainWindow::zoomDefault() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->setZoomLevel(1024); +} + +void +MainWindow::scrollLeft() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(false, false); +} + +void +MainWindow::jumpLeft() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(false, true); +} + +void +MainWindow::scrollRight() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(true, false); +} + +void +MainWindow::jumpRight() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(true, true); +} + +void +MainWindow::showNoOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::NoOverlays); +} + +void +MainWindow::showBasicOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::BasicOverlays); +} + +void +MainWindow::showAllOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::AllOverlays); +} + +void +MainWindow::play() +{ + if (m_playSource->isPlaying()) { + m_playSource->stop(); + } else { + m_playSource->play(m_viewManager->getPlaybackFrame()); + } +} + +void +MainWindow::ffwd() +{ + if (!getMainModel()) return; + + int frame = m_viewManager->getPlaybackFrame(); + ++frame; + + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) return; + + Layer *layer = pane->getSelectedLayer(); + + if (!dynamic_cast<TimeInstantLayer *>(layer) && + !dynamic_cast<TimeValueLayer *>(layer)) return; + + size_t resolution = 0; + if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapRight)) { + frame = getMainModel()->getEndFrame(); + } + + m_viewManager->setPlaybackFrame(frame); +} + +void +MainWindow::ffwdEnd() +{ + if (!getMainModel()) return; + m_viewManager->setPlaybackFrame(getMainModel()->getEndFrame()); +} + +void +MainWindow::rewind() +{ + if (!getMainModel()) return; + + int frame = m_viewManager->getPlaybackFrame(); + if (frame > 0) --frame; + + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) return; + + Layer *layer = pane->getSelectedLayer(); + + if (!dynamic_cast<TimeInstantLayer *>(layer) && + !dynamic_cast<TimeValueLayer *>(layer)) return; + + size_t resolution = 0; + if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapLeft)) { + frame = getMainModel()->getEndFrame(); + } + + m_viewManager->setPlaybackFrame(frame); +} + +void +MainWindow::rewindStart() +{ + if (!getMainModel()) return; + m_viewManager->setPlaybackFrame(getMainModel()->getStartFrame()); +} + +void +MainWindow::stop() +{ + m_playSource->stop(); +} + +void +MainWindow::addPane() +{ + QObject *s = sender(); + QAction *action = dynamic_cast<QAction *>(s); + + if (!action) { + std::cerr << "WARNING: MainWindow::addPane: sender is not an action" + << std::endl; + return; + } + + PaneActionMap::iterator i = m_paneActions.find(action); + + if (i == m_paneActions.end()) { + std::cerr << "WARNING: MainWindow::addPane: unknown action " + << action->objectName().toStdString() << std::endl; + return; + } + + CommandHistory::getInstance()->startCompoundOperation + (action->text(), true); + + AddPaneCommand *command = new AddPaneCommand(this); + CommandHistory::getInstance()->addCommand(command); + + Pane *pane = command->getPane(); + + if (i->second.layer != LayerFactory::TimeRuler) { + if (!m_timeRulerLayer) { +// std::cerr << "no time ruler layer, creating one" << std::endl; + m_timeRulerLayer = m_document->createMainModelLayer + (LayerFactory::TimeRuler); + } + +// std::cerr << "adding time ruler layer " << m_timeRulerLayer << std::endl; + + m_document->addLayerToView(pane, m_timeRulerLayer); + } + + Layer *newLayer = m_document->createLayer(i->second.layer); + m_document->setModel(newLayer, m_document->getMainModel()); + m_document->setChannel(newLayer, i->second.channel); + m_document->addLayerToView(pane, newLayer); + + m_paneStack->setCurrentPane(pane); + + CommandHistory::getInstance()->endCompoundOperation(); + + updateMenuStates(); +} + +MainWindow::AddPaneCommand::AddPaneCommand(MainWindow *mw) : + m_mw(mw), + m_pane(0), + m_prevCurrentPane(0), + m_added(false) +{ +} + +MainWindow::AddPaneCommand::~AddPaneCommand() +{ + if (m_pane && !m_added) { + m_mw->m_paneStack->deletePane(m_pane); + } +} + +QString +MainWindow::AddPaneCommand::getName() const +{ + return tr("Add Pane"); +} + +void +MainWindow::AddPaneCommand::execute() +{ + if (!m_pane) { + m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); + m_pane = m_mw->m_paneStack->addPane(); + } else { + m_mw->m_paneStack->showPane(m_pane); + } + + m_mw->m_paneStack->setCurrentPane(m_pane); + m_mw->m_panner->registerView(m_pane); + m_added = true; +} + +void +MainWindow::AddPaneCommand::unexecute() +{ + m_mw->m_paneStack->hidePane(m_pane); + m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); + m_mw->m_panner->unregisterView(m_pane); + m_added = false; +} + +MainWindow::RemovePaneCommand::RemovePaneCommand(MainWindow *mw, Pane *pane) : + m_mw(mw), + m_pane(pane), + m_added(true) +{ +} + +MainWindow::RemovePaneCommand::~RemovePaneCommand() +{ + if (m_pane && !m_added) { + m_mw->m_paneStack->deletePane(m_pane); + } +} + +QString +MainWindow::RemovePaneCommand::getName() const +{ + return tr("Remove Pane"); +} + +void +MainWindow::RemovePaneCommand::execute() +{ + m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); + m_mw->m_paneStack->hidePane(m_pane); + m_mw->m_panner->unregisterView(m_pane); + m_added = false; +} + +void +MainWindow::RemovePaneCommand::unexecute() +{ + m_mw->m_paneStack->showPane(m_pane); + m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); + m_mw->m_panner->registerView(m_pane); + m_added = true; +} + +void +MainWindow::addLayer() +{ + QObject *s = sender(); + QAction *action = dynamic_cast<QAction *>(s); + + if (!action) { + std::cerr << "WARNING: MainWindow::addLayer: sender is not an action" + << std::endl; + return; + } + + Pane *pane = m_paneStack->getCurrentPane(); + + if (!pane) { + std::cerr << "WARNING: MainWindow::addLayer: no current pane" << std::endl; + return; + } + + ExistingLayerActionMap::iterator ei = m_existingLayerActions.find(action); + + if (ei != m_existingLayerActions.end()) { + Layer *newLayer = ei->second; + m_document->addLayerToView(pane, newLayer); + m_paneStack->setCurrentLayer(pane, newLayer); + return; + } + + TransformActionMap::iterator i = m_layerTransformActions.find(action); + + if (i == m_layerTransformActions.end()) { + + LayerActionMap::iterator i = m_layerActions.find(action); + + if (i == m_layerActions.end()) { + std::cerr << "WARNING: MainWindow::addLayer: unknown action " + << action->objectName().toStdString() << std::endl; + return; + } + + LayerFactory::LayerType type = i->second; + + LayerFactory::LayerTypeSet emptyTypes = + LayerFactory::getInstance()->getValidEmptyLayerTypes(); + + Layer *newLayer; + + if (emptyTypes.find(type) != emptyTypes.end()) { + + newLayer = m_document->createEmptyLayer(type); + m_toolActions[ViewManager::DrawMode]->trigger(); + + } else { + + newLayer = m_document->createMainModelLayer(type); + } + + m_document->addLayerToView(pane, newLayer); + m_paneStack->setCurrentLayer(pane, newLayer); + + return; + } + + TransformName transform = i->second; + TransformFactory *factory = TransformFactory::getInstance(); + + QString configurationXml; + + int channel = -1; + // pick up the default channel from any existing layers on the same pane + for (int j = 0; j < pane->getLayerCount(); ++j) { + int c = LayerFactory::getInstance()->getChannel(pane->getLayer(j)); + if (c != -1) { + channel = c; + break; + } + } + + bool needConfiguration = false; + + if (factory->isTransformConfigurable(transform)) { + needConfiguration = true; + } else { + int minChannels, maxChannels; + int myChannels = m_document->getMainModel()->getChannelCount(); + if (factory->getTransformChannelRange(transform, + minChannels, + maxChannels)) { +// std::cerr << "myChannels: " << myChannels << ", minChannels: " << minChannels << ", maxChannels: " << maxChannels << std::endl; + needConfiguration = (myChannels > maxChannels && maxChannels == 1); + } + } + + if (needConfiguration) { + bool ok = + factory->getConfigurationForTransform + (transform, m_document->getMainModel(), channel, configurationXml); + if (!ok) return; + } + + Layer *newLayer = m_document->createDerivedLayer(transform, + m_document->getMainModel(), + channel, + configurationXml); + + if (newLayer) { + m_document->addLayerToView(pane, newLayer); + m_document->setChannel(newLayer, channel); + } + + updateMenuStates(); +} + +void +MainWindow::deleteCurrentPane() +{ + CommandHistory::getInstance()->startCompoundOperation + (tr("Delete Pane"), true); + + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) { + while (pane->getLayerCount() > 0) { + Layer *layer = pane->getLayer(0); + if (layer) { + m_document->removeLayerFromView(pane, layer); + } else { + break; + } + } + + RemovePaneCommand *command = new RemovePaneCommand(this, pane); + CommandHistory::getInstance()->addCommand(command); + } + + CommandHistory::getInstance()->endCompoundOperation(); + + updateMenuStates(); +} + +void +MainWindow::deleteCurrentLayer() +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) { + Layer *layer = pane->getSelectedLayer(); + if (layer) { + m_document->removeLayerFromView(pane, layer); + } + } + updateMenuStates(); +} + +void +MainWindow::renameCurrentLayer() +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) { + Layer *layer = pane->getSelectedLayer(); + if (layer) { + bool ok = false; + QString newName = QInputDialog::getText + (this, tr("Rename Layer"), + tr("New name for this layer:"), + QLineEdit::Normal, layer->objectName(), &ok); + if (ok) { + layer->setObjectName(newName); + setupExistingLayersMenu(); + } + } + } +} + +void +MainWindow::playSpeedChanged(int speed) +{ + int factor = 11 - speed; + m_playSpeed->setToolTip(tr("Playback speed: %1") + .arg(factor > 1 ? + QString("1/%1").arg(factor) : + tr("Full"))); + m_playSource->setSlowdownFactor(factor); +} + +void +MainWindow::outputLevelsChanged(float left, float right) +{ + m_fader->setPeakLeft(left); + m_fader->setPeakRight(right); +} + +void +MainWindow::sampleRateMismatch(size_t requested, size_t actual, + bool willResample) +{ + if (!willResample) { + //!!! more helpful message needed + QMessageBox::information + (this, tr("Sample rate mismatch"), + tr("The sample rate of this audio file (%1 Hz) does not match\nthe current playback rate (%2 Hz).\n\nThe file will play at the wrong speed.") + .arg(requested).arg(actual)); + } + +/*!!! Let's not do this for now, and see how we go -- now that we're putting + sample rate information in the status bar + + QMessageBox::information + (this, tr("Sample rate mismatch"), + tr("The sample rate of this audio file (%1 Hz) does not match\nthat of the output audio device (%2 Hz).\n\nThe file will be resampled automatically during playback.") + .arg(requested).arg(actual)); +*/ + + updateDescriptionLabel(); +} + +void +MainWindow::layerAdded(Layer *layer) +{ +// std::cerr << "MainWindow::layerAdded(" << layer << ")" << std::endl; +// setupExistingLayersMenu(); + updateMenuStates(); +} + +void +MainWindow::layerRemoved(Layer *layer) +{ +// std::cerr << "MainWindow::layerRemoved(" << layer << ")" << std::endl; + setupExistingLayersMenu(); + updateMenuStates(); +} + +void +MainWindow::layerAboutToBeDeleted(Layer *layer) +{ +// std::cerr << "MainWindow::layerAboutToBeDeleted(" << layer << ")" << std::endl; + if (layer == m_timeRulerLayer) { +// std::cerr << "(this is the time ruler layer)" << std::endl; + m_timeRulerLayer = 0; + } +} + +void +MainWindow::layerInAView(Layer *layer, bool inAView) +{ +// std::cerr << "MainWindow::layerInAView(" << layer << "," << inAView << ")" << std::endl; + + // Check whether we need to add or remove model from play source + Model *model = layer->getModel(); + if (model) { + if (inAView) { + m_playSource->addModel(model); + } else { + bool found = false; + for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { + Pane *pane = m_paneStack->getPane(i); + if (!pane) continue; + for (int j = 0; j < pane->getLayerCount(); ++j) { + Layer *pl = pane->getLayer(j); + if (pl && pl->getModel() == model) { + found = true; + break; + } + } + if (found) break; + } + if (!found) m_playSource->removeModel(model); + } + } + + setupExistingLayersMenu(); + updateMenuStates(); +} + +void +MainWindow::modelAdded(Model *model) +{ +// std::cerr << "MainWindow::modelAdded(" << model << ")" << std::endl; + m_playSource->addModel(model); +} + +void +MainWindow::mainModelChanged(WaveFileModel *model) +{ +// std::cerr << "MainWindow::mainModelChanged(" << model << ")" << std::endl; + updateDescriptionLabel(); + m_panLayer->setModel(model); + if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate()); + if (model && !m_playTarget) createPlayTarget(); +} + +void +MainWindow::modelAboutToBeDeleted(Model *model) +{ +// std::cerr << "MainWindow::modelAboutToBeDeleted(" << model << ")" << std::endl; + m_playSource->removeModel(model); +} + +void +MainWindow::modelGenerationFailed(QString transformName) +{ + QMessageBox::warning + (this, + tr("Failed to generate layer"), + tr("The layer transform \"%1\" failed to run.\nThis probably means that a plugin failed to initialise.") + .arg(transformName), + QMessageBox::Ok, 0); +} + +void +MainWindow::modelRegenerationFailed(QString layerName, QString transformName) +{ + QMessageBox::warning + (this, + tr("Failed to regenerate layer"), + tr("Failed to regenerate derived layer \"%1\".\nThe layer transform \"%2\" failed to run.\nThis probably means the layer used a plugin that is not currently available.") + .arg(layerName).arg(transformName), + QMessageBox::Ok, 0); +} + +void +MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position) +{ +// std::cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << std::endl; + m_paneStack->setCurrentPane(pane); + m_rightButtonMenu->popup(position); +} + +void +MainWindow::showLayerTree() +{ + QTreeView *view = new QTreeView(); + LayerTreeModel *tree = new LayerTreeModel(m_paneStack); + view->expand(tree->index(0, 0, QModelIndex())); + view->setModel(tree); + view->show(); +} + +void +MainWindow::preferenceChanged(PropertyContainer::PropertyName name) +{ + if (name == "Property Box Layout") { + if (Preferences::getInstance()->getPropertyBoxLayout() == + Preferences::VerticallyStacked) { + m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); + } else { + m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); + } + } +} + +void +MainWindow::preferences() +{ + if (!m_preferencesDialog.isNull()) { + m_preferencesDialog->show(); + m_preferencesDialog->raise(); + return; + } + + m_preferencesDialog = new PreferencesDialog(this); + + // DeleteOnClose is safe here, because m_preferencesDialog is a + // QPointer that will be zeroed when the dialog is deleted. We + // use it in preference to leaving the dialog lying around because + // if you Cancel the dialog, it resets the preferences state + // without resetting its own widgets, so its state will be + // incorrect when next shown unless we construct it afresh + m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose); + + m_preferencesDialog->show(); +} + +void +MainWindow::website() +{ + openHelpUrl(tr("http://www.sonicvisualiser.org/")); +} + +void +MainWindow::help() +{ + openHelpUrl(tr("http://www.sonicvisualiser.org/doc/reference/en/")); +} + +void +MainWindow::openHelpUrl(QString url) +{ + // This method mostly lifted from Qt Assistant source code + + QProcess *process = new QProcess(this); + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); + + QStringList args; + +#ifdef Q_OS_MAC + args.append(url); + process->start("open", args); +#else +#ifdef Q_OS_WIN32 + + QString pf(getenv("ProgramFiles")); + QString command = pf + QString("\\Internet Explorer\\IEXPLORE.EXE"); + + args.append(url); + process->start(command, args); + +#else +#ifdef Q_WS_X11 + if (!qgetenv("KDE_FULL_SESSION").isEmpty()) { + args.append("exec"); + args.append(url); + process->start("kfmclient", args); + } else if (!qgetenv("BROWSER").isEmpty()) { + args.append(url); + process->start(qgetenv("BROWSER"), args); + } else { + args.append(url); + process->start("firefox", args); + } +#endif +#endif +#endif +} + +void +MainWindow::about() +{ + bool debug = false; + QString version = "(unknown version)"; + +#ifdef BUILD_DEBUG + debug = true; +#endif +#ifdef SV_VERSION +#ifdef SVNREV + version = tr("Release %1 : Revision %2").arg(SV_VERSION).arg(SVNREV); +#else + version = tr("Release %1").arg(SV_VERSION); +#endif +#else +#ifdef SVNREV + version = tr("Unreleased : Revision %1").arg(SVNREV); +#endif +#endif + + QString aboutText; + + aboutText += tr("<h3>About Sonic Visualiser</h3>"); + aboutText += tr("<p>Sonic Visualiser is a program for viewing and exploring audio data for semantic music analysis and annotation.</p>"); + aboutText += tr("<p>%1 : %2 build</p>") + .arg(version) + .arg(debug ? tr("Debug") : tr("Release")); + +#ifdef BUILD_STATIC + aboutText += tr("<p>Statically linked"); +#ifndef QT_SHARED + aboutText += tr("<br>With Qt (v%1) © Trolltech AS").arg(QT_VERSION_STR); +#endif +#ifdef HAVE_JACK + aboutText += tr("<br>With JACK audio output (v%1) © Paul Davis and Jack O'Quin").arg(JACK_VERSION); +#endif +#ifdef HAVE_PORTAUDIO + aboutText += tr("<br>With PortAudio audio output © Ross Bencina and Phil Burk"); +#endif +#ifdef HAVE_OGGZ + aboutText += tr("<br>With Ogg file decoder (oggz v%1, fishsound v%2) © CSIRO Australia").arg(OGGZ_VERSION).arg(FISHSOUND_VERSION); +#endif +#ifdef HAVE_MAD + aboutText += tr("<br>With MAD mp3 decoder (v%1) © Underbit Technologies Inc").arg(MAD_VERSION); +#endif +#ifdef HAVE_SAMPLERATE + aboutText += tr("<br>With libsamplerate (v%1) © Erik de Castro Lopo").arg(SAMPLERATE_VERSION); +#endif +#ifdef HAVE_SNDFILE + aboutText += tr("<br>With libsndfile (v%1) © Erik de Castro Lopo").arg(SNDFILE_VERSION); +#endif +#ifdef HAVE_FFTW3 + aboutText += tr("<br>With FFTW3 (v%1) © Matteo Frigo and MIT").arg(FFTW3_VERSION); +#endif +#ifdef HAVE_VAMP + aboutText += tr("<br>With Vamp plugin support (API v%1, SDK v%2) © Chris Cannam").arg(VAMP_API_VERSION).arg(VAMP_SDK_VERSION); +#endif + aboutText += tr("<br>With LADSPA plugin support (API v%1) © Richard Furse, Paul Davis, Stefan Westerfeld").arg(LADSPA_VERSION); + aboutText += tr("<br>With DSSI plugin support (API v%1) © Chris Cannam, Steve Harris, Sean Bolton").arg(DSSI_VERSION); + aboutText += "</p>"; +#endif + + aboutText += + "<p>Sonic Visualiser Copyright © 2005 - 2006 Chris Cannam<br>" + "Centre for Digital Music, Queen Mary, University of London.</p>" + "<p>This program is free software; you can redistribute it and/or<br>" + "modify it under the terms of the GNU General Public License as<br>" + "published by the Free Software Foundation; either version 2 of the<br>" + "License, or (at your option) any later version.<br>See the file " + "COPYING included with this distribution for more information.</p>"; + + QMessageBox::about(this, tr("About Sonic Visualiser"), aboutText); +} + + +#ifdef INCLUDE_MOCFILES +#include "MainWindow.moc.cpp" +#endif