Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@0: Sonic Visualiser Chris@0: An audio file viewer and annotation editor. Chris@0: Centre for Digital Music, Queen Mary, University of London. Chris@0: This file copyright 2006 Chris Cannam. Chris@0: Chris@0: This program is free software; you can redistribute it and/or Chris@0: modify it under the terms of the GNU General Public License as Chris@0: published by the Free Software Foundation; either version 2 of the Chris@0: License, or (at your option) any later version. See the file Chris@0: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@1: #include "version.h" Chris@0: Chris@0: #include "MainWindow.h" Chris@1: #include "document/Document.h" Chris@0: #include "PreferencesDialog.h" Chris@0: Chris@1: #include "view/Pane.h" Chris@1: #include "view/PaneStack.h" Chris@1: #include "data/model/WaveFileModel.h" Chris@1: #include "data/model/SparseOneDimensionalModel.h" Chris@1: #include "view/ViewManager.h" Chris@0: #include "base/Preferences.h" Chris@0: #include "layer/WaveformLayer.h" Chris@0: #include "layer/TimeRulerLayer.h" Chris@0: #include "layer/TimeInstantLayer.h" Chris@0: #include "layer/TimeValueLayer.h" Chris@0: #include "layer/Colour3DPlotLayer.h" Chris@0: #include "widgets/Fader.h" Chris@1: #include "view/Panner.h" Chris@0: #include "widgets/PropertyBox.h" Chris@0: #include "widgets/PropertyStack.h" Chris@0: #include "widgets/AudioDial.h" Chris@0: #include "widgets/LayerTree.h" Chris@0: #include "widgets/ListInputDialog.h" Chris@36: #include "widgets/SubdividingMenu.h" Chris@0: #include "audioio/AudioCallbackPlaySource.h" Chris@0: #include "audioio/AudioCallbackPlayTarget.h" Chris@0: #include "audioio/AudioTargetFactory.h" Chris@1: #include "data/fileio/AudioFileReaderFactory.h" Chris@1: #include "data/fileio/DataFileReaderFactory.h" Chris@1: #include "data/fileio/WavFileWriter.h" Chris@1: #include "data/fileio/CSVFileWriter.h" Chris@1: #include "data/fileio/BZipFileDevice.h" Chris@1: #include "base/RecentFiles.h" Chris@0: #include "transform/TransformFactory.h" Chris@0: #include "base/PlayParameterRepository.h" Chris@0: #include "base/XmlExportable.h" Chris@0: #include "base/CommandHistory.h" Chris@0: #include "base/Profiler.h" Chris@0: #include "base/Clipboard.h" Chris@0: Chris@0: // For version information Chris@0: #include "vamp/vamp.h" Chris@0: #include "vamp-sdk/PluginBase.h" Chris@0: #include "plugin/api/ladspa.h" Chris@0: #include "plugin/api/dssi.h" Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@7: #include Chris@5: #include Chris@11: #include Chris@11: #include Chris@16: #include Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: Chris@0: using std::cerr; Chris@0: using std::endl; Chris@0: Chris@33: using std::vector; Chris@33: using std::map; Chris@33: using std::set; Chris@33: Chris@0: Chris@0: MainWindow::MainWindow() : Chris@0: m_document(0), Chris@0: m_paneStack(0), Chris@0: m_viewManager(0), Chris@0: m_panner(0), Chris@0: m_timeRulerLayer(0), Chris@0: m_playSource(0), Chris@0: m_playTarget(0), Chris@34: m_recentFiles("RecentFiles"), Chris@34: m_recentTransforms("RecentTransforms"), Chris@0: m_mainMenusCreated(false), Chris@0: m_paneMenu(0), Chris@0: m_layerMenu(0), Chris@34: m_transformsMenu(0), Chris@0: m_existingLayersMenu(0), Chris@34: m_recentFilesMenu(0), Chris@34: m_recentTransformsMenu(0), Chris@0: m_rightButtonMenu(0), Chris@0: m_rightButtonLayerMenu(0), Chris@34: m_rightButtonTransformsMenu(0), Chris@0: m_documentModified(false), Chris@0: m_preferencesDialog(0) Chris@0: { Chris@0: setWindowTitle(tr("Sonic Visualiser")); Chris@0: Chris@0: UnitDatabase::getInstance()->registerUnit("Hz"); Chris@0: UnitDatabase::getInstance()->registerUnit("dB"); Chris@0: Chris@0: connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()), Chris@0: this, SLOT(documentModified())); Chris@0: connect(CommandHistory::getInstance(), SIGNAL(documentRestored()), Chris@0: this, SLOT(documentRestored())); Chris@0: Chris@0: QFrame *frame = new QFrame; Chris@0: setCentralWidget(frame); Chris@0: Chris@0: QGridLayout *layout = new QGridLayout; Chris@0: Chris@0: m_viewManager = new ViewManager(); Chris@0: connect(m_viewManager, SIGNAL(selectionChanged()), Chris@0: this, SLOT(updateMenuStates())); Chris@0: Chris@0: m_descriptionLabel = new QLabel; Chris@0: Chris@0: m_paneStack = new PaneStack(frame, m_viewManager); Chris@0: connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)), Chris@0: this, SLOT(currentPaneChanged(Pane *))); Chris@0: connect(m_paneStack, SIGNAL(currentLayerChanged(Pane *, Layer *)), Chris@0: this, SLOT(currentLayerChanged(Pane *, Layer *))); Chris@0: connect(m_paneStack, SIGNAL(rightButtonMenuRequested(Pane *, QPoint)), Chris@0: this, SLOT(rightButtonMenuRequested(Pane *, QPoint))); Chris@0: Chris@0: m_panner = new Panner(frame); Chris@0: m_panner->setViewManager(m_viewManager); Chris@0: m_panner->setFixedHeight(40); Chris@0: Chris@0: m_panLayer = new WaveformLayer; Chris@0: m_panLayer->setChannelMode(WaveformLayer::MergeChannels); Chris@0: // m_panLayer->setScale(WaveformLayer::MeterScale); Chris@39: // m_panLayer->setAutoNormalize(true); Chris@0: m_panLayer->setBaseColour(Qt::darkGreen); Chris@0: m_panLayer->setAggressiveCacheing(true); Chris@0: m_panner->addLayer(m_panLayer); Chris@0: Chris@0: m_playSource = new AudioCallbackPlaySource(m_viewManager); Chris@0: Chris@0: connect(m_playSource, SIGNAL(sampleRateMismatch(size_t, size_t, bool)), Chris@0: this, SLOT(sampleRateMismatch(size_t, size_t, bool))); Chris@42: connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()), Chris@42: this, SLOT(audioOverloadPluginDisabled())); Chris@0: Chris@0: m_fader = new Fader(frame, false); Chris@0: Chris@0: m_playSpeed = new AudioDial(frame); Chris@12: m_playSpeed->setMinimum(0); Chris@25: m_playSpeed->setMaximum(199); Chris@25: m_playSpeed->setValue(100); Chris@0: m_playSpeed->setFixedWidth(24); Chris@0: m_playSpeed->setFixedHeight(24); Chris@0: m_playSpeed->setNotchesVisible(true); Chris@25: m_playSpeed->setPageStep(10); Chris@25: m_playSpeed->setToolTip(tr("Playback speed: +0%")); Chris@25: m_playSpeed->setDefaultValue(100); Chris@0: connect(m_playSpeed, SIGNAL(valueChanged(int)), Chris@0: this, SLOT(playSpeedChanged(int))); Chris@0: Chris@26: m_playSharpen = new QPushButton(frame); Chris@17: m_playSharpen->setToolTip(tr("Sharpen percussive transients")); Chris@26: m_playSharpen->setFixedSize(20, 20); Chris@26: // m_playSharpen->setFlat(true); Chris@16: m_playSharpen->setEnabled(false); Chris@26: m_playSharpen->setCheckable(true); Chris@26: m_playSharpen->setChecked(false); Chris@26: m_playSharpen->setIcon(QIcon(":icons/sharpen.png")); Chris@26: connect(m_playSharpen, SIGNAL(clicked()), this, SLOT(playSharpenToggled())); Chris@26: Chris@26: m_playMono = new QPushButton(frame); Chris@26: m_playMono->setToolTip(tr("Run time stretcher in mono only")); Chris@26: m_playMono->setFixedSize(20, 20); Chris@26: // m_playMono->setFlat(true); Chris@26: m_playMono->setEnabled(false); Chris@26: m_playMono->setCheckable(true); Chris@26: m_playMono->setChecked(false); Chris@26: m_playMono->setIcon(QIcon(":icons/mono.png")); Chris@26: connect(m_playMono, SIGNAL(clicked()), this, SLOT(playMonoToggled())); Chris@21: Chris@21: QSettings settings; Chris@21: settings.beginGroup("MainWindow"); Chris@24: m_playSharpen->setChecked(settings.value("playsharpen", true).toBool()); Chris@26: m_playMono->setChecked(settings.value("playmono", false).toBool()); Chris@21: settings.endGroup(); Chris@21: Chris@26: layout->addWidget(m_paneStack, 0, 0, 1, 5); Chris@0: layout->addWidget(m_panner, 1, 0); Chris@0: layout->addWidget(m_fader, 1, 1); Chris@0: layout->addWidget(m_playSpeed, 1, 2); Chris@16: layout->addWidget(m_playSharpen, 1, 3); Chris@26: layout->addWidget(m_playMono, 1, 4); Chris@16: Chris@16: layout->setColumnStretch(0, 10); Chris@16: Chris@0: frame->setLayout(layout); Chris@0: Chris@0: connect(m_viewManager, SIGNAL(outputLevelsChanged(float, float)), Chris@0: this, SLOT(outputLevelsChanged(float, float))); Chris@0: Chris@0: connect(Preferences::getInstance(), Chris@0: SIGNAL(propertyChanged(PropertyContainer::PropertyName)), Chris@0: this, Chris@0: SLOT(preferenceChanged(PropertyContainer::PropertyName))); Chris@0: Chris@0: setupMenus(); Chris@0: setupToolbars(); Chris@0: Chris@7: statusBar()->addWidget(m_descriptionLabel); Chris@0: Chris@0: newSession(); Chris@0: } Chris@0: Chris@0: MainWindow::~MainWindow() Chris@0: { Chris@0: closeSession(); Chris@0: delete m_playTarget; Chris@0: delete m_playSource; Chris@0: delete m_viewManager; Chris@0: Profiles::getInstance()->dump(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupMenus() Chris@0: { Chris@0: QAction *action = 0; Chris@0: QMenu *menu = 0; Chris@0: QToolBar *toolbar = 0; Chris@0: Chris@0: if (!m_mainMenusCreated) { Chris@0: m_rightButtonMenu = new QMenu(); Chris@0: } Chris@0: Chris@0: if (m_rightButtonLayerMenu) { Chris@0: m_rightButtonLayerMenu->clear(); Chris@0: } else { Chris@0: m_rightButtonLayerMenu = m_rightButtonMenu->addMenu(tr("&Layer")); Chris@0: m_rightButtonMenu->addSeparator(); Chris@0: } Chris@0: Chris@34: if (m_rightButtonTransformsMenu) { Chris@34: m_rightButtonTransformsMenu->clear(); Chris@34: } else { Chris@34: m_rightButtonTransformsMenu = m_rightButtonMenu->addMenu(tr("&Transform")); Chris@34: m_rightButtonMenu->addSeparator(); Chris@34: } Chris@34: Chris@0: if (!m_mainMenusCreated) { Chris@0: Chris@0: CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); Chris@0: m_rightButtonMenu->addSeparator(); Chris@0: Chris@0: menu = menuBar()->addMenu(tr("&File")); Chris@0: toolbar = addToolBar(tr("File Toolbar")); Chris@0: Chris@0: QIcon icon(":icons/filenew.png"); Chris@0: icon.addFile(":icons/filenew-22.png"); Chris@0: action = new QAction(icon, tr("&New Session"), this); Chris@0: action->setShortcut(tr("Ctrl+N")); Chris@0: action->setStatusTip(tr("Clear the current Sonic Visualiser session and start a new one")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(newSession())); Chris@0: menu->addAction(action); Chris@0: toolbar->addAction(action); Chris@0: Chris@0: icon = QIcon(":icons/fileopen.png"); Chris@0: icon.addFile(":icons/fileopen-22.png"); Chris@0: Chris@0: action = new QAction(icon, tr("&Open Session..."), this); Chris@0: action->setShortcut(tr("Ctrl+O")); Chris@0: action->setStatusTip(tr("Open a previously saved Sonic Visualiser session file")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(openSession())); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(icon, tr("&Open..."), this); Chris@0: action->setStatusTip(tr("Open a session file, audio file, or layer")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(openSomething())); Chris@0: toolbar->addAction(action); Chris@0: Chris@0: icon = QIcon(":icons/filesave.png"); Chris@0: icon.addFile(":icons/filesave-22.png"); Chris@0: action = new QAction(icon, tr("&Save Session"), this); Chris@0: action->setShortcut(tr("Ctrl+S")); Chris@0: action->setStatusTip(tr("Save the current session into a Sonic Visualiser session file")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); Chris@0: connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: toolbar->addAction(action); Chris@0: Chris@0: icon = QIcon(":icons/filesaveas.png"); Chris@0: icon.addFile(":icons/filesaveas-22.png"); Chris@0: action = new QAction(icon, tr("Save Session &As..."), this); Chris@0: action->setStatusTip(tr("Save the current session into a new Sonic Visualiser session file")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); Chris@0: menu->addAction(action); Chris@0: toolbar->addAction(action); Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(tr("&Import Audio File..."), this); Chris@0: action->setShortcut(tr("Ctrl+I")); Chris@0: action->setStatusTip(tr("Import an existing audio file")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(importAudio())); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Import Secondary Audio File..."), this); Chris@0: action->setShortcut(tr("Ctrl+Shift+I")); Chris@0: action->setStatusTip(tr("Import an extra audio file as a separate layer")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(importMoreAudio())); Chris@0: connect(this, SIGNAL(canImportMoreAudio(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("&Export Audio File..."), this); Chris@0: action->setStatusTip(tr("Export selection as an audio file")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(exportAudio())); Chris@0: connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(tr("Import Annotation &Layer..."), this); Chris@0: action->setShortcut(tr("Ctrl+L")); Chris@0: action->setStatusTip(tr("Import layer data from an existing file")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(importLayer())); Chris@0: connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Export Annotation Layer..."), this); Chris@0: action->setStatusTip(tr("Export layer data to a file")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(exportLayer())); Chris@0: connect(this, SIGNAL(canExportLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: menu->addSeparator(); Chris@0: m_recentFilesMenu = menu->addMenu(tr("&Recent Files")); Chris@0: setupRecentFilesMenu(); Chris@34: connect(&m_recentFiles, SIGNAL(recentChanged()), Chris@0: this, SLOT(setupRecentFilesMenu())); Chris@0: Chris@0: menu->addSeparator(); Chris@0: action = new QAction(tr("&Preferences..."), this); Chris@0: action->setStatusTip(tr("Adjust the application preferences")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(preferences())); Chris@0: menu->addAction(action); Chris@0: Chris@0: /*!!! Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(tr("Play / Pause"), this); Chris@0: action->setShortcut(tr("Space")); Chris@0: action->setStatusTip(tr("Start or stop playback from the current position")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(play())); Chris@0: menu->addAction(action); Chris@0: */ Chris@0: Chris@0: menu->addSeparator(); Chris@0: action = new QAction(QIcon(":/icons/exit.png"), Chris@0: tr("&Quit"), this); Chris@0: action->setShortcut(tr("Ctrl+Q")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(close())); Chris@0: menu->addAction(action); Chris@0: Chris@0: menu = menuBar()->addMenu(tr("&Edit")); Chris@0: CommandHistory::getInstance()->registerMenu(menu); Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(QIcon(":/icons/editcut.png"), Chris@0: tr("Cu&t"), this); Chris@0: action->setShortcut(tr("Ctrl+X")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(cut())); Chris@0: connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: m_rightButtonMenu->addAction(action); Chris@0: Chris@0: action = new QAction(QIcon(":/icons/editcopy.png"), Chris@0: tr("&Copy"), this); Chris@0: action->setShortcut(tr("Ctrl+C")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(copy())); Chris@0: connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: m_rightButtonMenu->addAction(action); Chris@0: Chris@0: action = new QAction(QIcon(":/icons/editpaste.png"), Chris@0: tr("&Paste"), this); Chris@0: action->setShortcut(tr("Ctrl+V")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(paste())); Chris@0: connect(this, SIGNAL(canPaste(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: m_rightButtonMenu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("&Delete Selected Items"), this); Chris@0: action->setShortcut(tr("Del")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(deleteSelected())); Chris@0: connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: m_rightButtonMenu->addAction(action); Chris@0: Chris@0: menu->addSeparator(); Chris@0: m_rightButtonMenu->addSeparator(); Chris@0: Chris@0: action = new QAction(tr("Select &All"), this); Chris@0: action->setShortcut(tr("Ctrl+A")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); Chris@0: connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: m_rightButtonMenu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Select &Visible Range"), this); Chris@0: action->setShortcut(tr("Ctrl+Shift+A")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(selectVisible())); Chris@0: connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Select to &Start"), this); Chris@0: action->setShortcut(tr("Shift+Left")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(selectToStart())); Chris@0: connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Select to &End"), this); Chris@0: action->setShortcut(tr("Shift+Right")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(selectToEnd())); Chris@0: connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("C&lear Selection"), this); Chris@0: action->setShortcut(tr("Esc")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(clearSelection())); Chris@0: connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: m_rightButtonMenu->addAction(action); Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(tr("&Insert Instant at Playback Position"), this); Chris@0: action->setShortcut(tr("Enter")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(insertInstant())); Chris@0: connect(this, SIGNAL(canInsertInstant(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@7: // Laptop shortcut (no keypad Enter key) Chris@7: connect(new QShortcut(tr(";"), this), SIGNAL(activated()), Chris@7: this, SLOT(insertInstant())); Chris@7: Chris@0: menu = menuBar()->addMenu(tr("&View")); Chris@0: Chris@0: QActionGroup *overlayGroup = new QActionGroup(this); Chris@0: Chris@0: action = new QAction(tr("&No Text Overlays"), this); Chris@0: action->setShortcut(tr("0")); Chris@0: action->setStatusTip(tr("Show no texts for frame times, layer names etc")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(showNoOverlays())); Chris@0: action->setCheckable(true); Chris@0: action->setChecked(false); Chris@0: overlayGroup->addAction(action); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Basic &Text Overlays"), this); Chris@0: action->setShortcut(tr("9")); Chris@0: action->setStatusTip(tr("Show texts for frame times etc, but not layer names etc")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(showBasicOverlays())); Chris@0: action->setCheckable(true); Chris@0: action->setChecked(true); Chris@0: overlayGroup->addAction(action); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("&All Text Overlays"), this); Chris@0: action->setShortcut(tr("8")); Chris@0: action->setStatusTip(tr("Show texts for frame times, layer names etc")); Chris@7: connect(action, SIGNAL(triggered()), this, SLOT(showAllTextOverlays())); Chris@0: action->setCheckable(true); Chris@0: action->setChecked(false); Chris@0: overlayGroup->addAction(action); Chris@0: menu->addAction(action); Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(tr("Scroll &Left"), this); Chris@0: action->setShortcut(tr("Left")); Chris@0: action->setStatusTip(tr("Scroll the current pane to the left")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(scrollLeft())); Chris@0: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Scroll &Right"), this); Chris@0: action->setShortcut(tr("Right")); Chris@0: action->setStatusTip(tr("Scroll the current pane to the right")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(scrollRight())); Chris@0: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Jump Left"), this); Chris@0: action->setShortcut(tr("Ctrl+Left")); Chris@0: action->setStatusTip(tr("Scroll the current pane a big step to the left")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft())); Chris@0: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Jump Right"), this); Chris@0: action->setShortcut(tr("Ctrl+Right")); Chris@0: action->setStatusTip(tr("Scroll the current pane a big step to the right")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(jumpRight())); Chris@0: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(QIcon(":/icons/zoom-in.png"), Chris@0: tr("Zoom &In"), this); Chris@0: action->setShortcut(tr("Up")); Chris@0: action->setStatusTip(tr("Increase the zoom level")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(zoomIn())); Chris@0: connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(QIcon(":/icons/zoom-out.png"), Chris@0: tr("Zoom &Out"), this); Chris@0: action->setShortcut(tr("Down")); Chris@0: action->setStatusTip(tr("Decrease the zoom level")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(zoomOut())); Chris@0: connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Restore &Default Zoom"), this); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault())); Chris@0: connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Zoom to &Fit"), this); Chris@0: action->setStatusTip(tr("Zoom to show the whole file")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(zoomToFit())); Chris@0: connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@7: Chris@7: action = new QAction(tr("Show &Zoom Wheels"), this); Chris@7: action->setShortcut(tr("Z")); Chris@7: action->setStatusTip(tr("Show thumbwheels for zooming horizontally and vertically")); Chris@7: connect(action, SIGNAL(triggered()), this, SLOT(toggleZoomWheels())); Chris@7: action->setCheckable(true); Chris@7: action->setChecked(m_viewManager->getZoomWheelsEnabled()); Chris@7: menu->addAction(action); Chris@0: Chris@0: /*!!! This one doesn't work properly yet Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(tr("Show &Layer Hierarchy"), this); Chris@0: action->setShortcut(tr("Alt+L")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(showLayerTree())); Chris@0: menu->addAction(action); Chris@0: */ Chris@0: } Chris@0: Chris@0: if (m_paneMenu) { Chris@0: m_paneActions.clear(); Chris@0: m_paneMenu->clear(); Chris@0: } else { Chris@0: m_paneMenu = menuBar()->addMenu(tr("&Pane")); Chris@0: } Chris@0: Chris@0: if (m_layerMenu) { Chris@0: m_layerActions.clear(); Chris@0: m_layerMenu->clear(); Chris@0: } else { Chris@0: m_layerMenu = menuBar()->addMenu(tr("&Layer")); Chris@0: } Chris@0: Chris@34: if (m_transformsMenu) { Chris@34: m_transformActions.clear(); Chris@34: m_transformActionsReverse.clear(); Chris@34: m_transformsMenu->clear(); Chris@34: } else { Chris@34: m_transformsMenu = menuBar()->addMenu(tr("&Transform")); Chris@34: } Chris@34: Chris@0: TransformFactory::TransformList transforms = Chris@0: TransformFactory::getInstance()->getAllTransforms(); Chris@0: Chris@33: vector types = Chris@0: TransformFactory::getInstance()->getAllTransformTypes(); Chris@0: Chris@37: map > categoryMenus; Chris@37: map > makerMenus; Chris@33: Chris@36: map byPluginNameMenus; Chris@33: map > pluginNameMenus; Chris@33: Chris@37: set pendingMenus; Chris@37: Chris@34: m_recentTransformsMenu = m_transformsMenu->addMenu(tr("&Recent Transforms")); Chris@34: m_rightButtonTransformsMenu->addMenu(m_recentTransformsMenu); Chris@34: connect(&m_recentTransforms, SIGNAL(recentChanged()), Chris@34: this, SLOT(setupRecentTransformsMenu())); Chris@34: Chris@34: m_transformsMenu->addSeparator(); Chris@34: m_rightButtonTransformsMenu->addSeparator(); Chris@34: Chris@33: for (vector::iterator i = types.begin(); i != types.end(); ++i) { Chris@33: Chris@33: if (i != types.begin()) { Chris@34: m_transformsMenu->addSeparator(); Chris@34: m_rightButtonTransformsMenu->addSeparator(); Chris@33: } Chris@33: Chris@33: QString byCategoryLabel = tr("%1 by Category").arg(*i); Chris@37: SubdividingMenu *byCategoryMenu = new SubdividingMenu(byCategoryLabel, Chris@37: 20, 40); Chris@37: m_transformsMenu->addMenu(byCategoryMenu); Chris@34: m_rightButtonTransformsMenu->addMenu(byCategoryMenu); Chris@37: pendingMenus.insert(byCategoryMenu); Chris@33: Chris@33: vector categories = Chris@33: TransformFactory::getInstance()->getTransformCategories(*i); Chris@33: Chris@33: for (vector::iterator j = categories.begin(); Chris@33: j != categories.end(); ++j) { Chris@33: Chris@33: QString category = *j; Chris@33: if (category == "") category = tr("Unclassified"); Chris@33: Chris@33: if (categories.size() < 2) { Chris@33: categoryMenus[*i][category] = byCategoryMenu; Chris@33: continue; Chris@33: } Chris@33: Chris@33: QStringList components = category.split(" > "); Chris@33: QString key; Chris@33: Chris@33: for (QStringList::iterator k = components.begin(); Chris@33: k != components.end(); ++k) { Chris@33: Chris@33: QString parentKey = key; Chris@33: if (key != "") key += " > "; Chris@33: key += *k; Chris@33: Chris@33: if (categoryMenus[*i].find(key) == categoryMenus[*i].end()) { Chris@37: SubdividingMenu *m = new SubdividingMenu(*k, 20, 40); Chris@37: pendingMenus.insert(m); Chris@37: categoryMenus[*i][key] = m; Chris@33: if (parentKey == "") { Chris@37: byCategoryMenu->addMenu(m); Chris@33: } else { Chris@37: categoryMenus[*i][parentKey]->addMenu(m); Chris@33: } Chris@33: } Chris@33: } Chris@33: } Chris@33: Chris@34: QString byPluginNameLabel = tr("%1 by Plugin Name").arg(*i); Chris@36: byPluginNameMenus[*i] = new SubdividingMenu(byPluginNameLabel); Chris@36: m_transformsMenu->addMenu(byPluginNameMenus[*i]); Chris@34: m_rightButtonTransformsMenu->addMenu(byPluginNameMenus[*i]); Chris@37: pendingMenus.insert(byPluginNameMenus[*i]); Chris@34: Chris@33: QString byMakerLabel = tr("%1 by Maker").arg(*i); Chris@37: SubdividingMenu *byMakerMenu = new SubdividingMenu(byMakerLabel, 20, 40); Chris@37: m_transformsMenu->addMenu(byMakerMenu); Chris@34: m_rightButtonTransformsMenu->addMenu(byMakerMenu); Chris@37: pendingMenus.insert(byMakerMenu); Chris@33: Chris@33: vector makers = Chris@33: TransformFactory::getInstance()->getTransformMakers(*i); Chris@37: Chris@33: for (vector::iterator j = makers.begin(); Chris@33: j != makers.end(); ++j) { Chris@33: Chris@33: QString maker = *j; Chris@33: if (maker == "") maker = tr("Unknown"); Chris@33: Chris@37: makerMenus[*i][maker] = new SubdividingMenu(maker, 30, 40); Chris@37: byMakerMenu->addMenu(makerMenus[*i][maker]); Chris@37: pendingMenus.insert(makerMenus[*i][maker]); Chris@33: } Chris@0: } Chris@0: Chris@0: for (unsigned int i = 0; i < transforms.size(); ++i) { Chris@0: Chris@0: QString description = transforms[i].description; Chris@0: if (description == "") description = transforms[i].name; Chris@0: Chris@33: QString type = transforms[i].type; Chris@33: Chris@33: QString category = transforms[i].category; Chris@33: if (category == "") category = tr("Unclassified"); Chris@33: Chris@33: QString maker = transforms[i].maker; Chris@33: if (maker == "") maker = tr("Unknown"); Chris@33: Chris@33: QString pluginName = description.section(": ", 0, 0); Chris@33: QString output = description.section(": ", 1); Chris@33: Chris@33: action = new QAction(tr("%1...").arg(description), this); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@34: m_transformActions[action] = transforms[i].name; Chris@34: m_transformActionsReverse[transforms[i].name] = action; Chris@0: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@33: Chris@33: if (categoryMenus[type].find(category) == categoryMenus[type].end()) { Chris@33: std::cerr << "WARNING: MainWindow::setupMenus: Internal error: " Chris@33: << "No category menu for transform \"" Chris@33: << description.toStdString() << "\" (category = \"" Chris@33: << category.toStdString() << "\")" << std::endl; Chris@33: } else { Chris@33: categoryMenus[type][category]->addAction(action); Chris@33: } Chris@33: Chris@33: if (makerMenus[type].find(maker) == makerMenus[type].end()) { Chris@33: std::cerr << "WARNING: MainWindow::setupMenus: Internal error: " Chris@33: << "No maker menu for transform \"" Chris@33: << description.toStdString() << "\" (maker = \"" Chris@33: << maker.toStdString() << "\")" << std::endl; Chris@33: } else { Chris@37: makerMenus[type][maker]->addAction(pluginName, action); Chris@33: } Chris@33: Chris@33: action = new QAction(tr("%1...").arg(output == "" ? pluginName : output), this); Chris@33: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@34: m_transformActions[action] = transforms[i].name; Chris@33: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@33: Chris@34: // cerr << "Transform: \"" << name.toStdString() << "\": plugin name \"" << pluginName.toStdString() << "\"" << endl; Chris@34: Chris@33: if (pluginNameMenus[type].find(pluginName) == Chris@33: pluginNameMenus[type].end()) { Chris@33: Chris@36: SubdividingMenu *parentMenu = byPluginNameMenus[type]; Chris@34: Chris@33: if (output == "") { Chris@36: parentMenu->addAction(pluginName, action); Chris@33: } else { Chris@34: pluginNameMenus[type][pluginName] = Chris@34: parentMenu->addMenu(pluginName); Chris@33: connect(this, SIGNAL(canAddLayer(bool)), Chris@33: pluginNameMenus[type][pluginName], Chris@33: SLOT(setEnabled(bool))); Chris@33: } Chris@33: } Chris@33: Chris@33: if (pluginNameMenus[type].find(pluginName) != Chris@33: pluginNameMenus[type].end()) { Chris@33: pluginNameMenus[type][pluginName]->addAction(action); Chris@33: } Chris@0: } Chris@0: Chris@37: for (set::iterator i = pendingMenus.begin(); Chris@37: i != pendingMenus.end(); ++i) { Chris@37: (*i)->entriesAdded(); Chris@37: } Chris@37: Chris@34: setupRecentTransformsMenu(); Chris@0: Chris@0: menu = m_paneMenu; Chris@0: Chris@0: action = new QAction(QIcon(":/icons/pane.png"), tr("Add &New Pane"), this); Chris@0: action->setShortcut(tr("Alt+N")); Chris@0: action->setStatusTip(tr("Add a new pane containing only a time ruler")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(addPane())); Chris@0: connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_paneActions[action] = PaneConfiguration(LayerFactory::TimeRuler); Chris@0: menu->addAction(action); Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: menu = m_layerMenu; Chris@0: Chris@34: // menu->addSeparator(); Chris@0: Chris@0: LayerFactory::LayerTypeSet emptyLayerTypes = Chris@0: LayerFactory::getInstance()->getValidEmptyLayerTypes(); Chris@0: Chris@0: for (LayerFactory::LayerTypeSet::iterator i = emptyLayerTypes.begin(); Chris@0: i != emptyLayerTypes.end(); ++i) { Chris@0: Chris@0: QIcon icon; Chris@0: QString mainText, tipText, channelText; Chris@0: LayerFactory::LayerType type = *i; Chris@0: QString name = LayerFactory::getInstance()->getLayerPresentationName(type); Chris@0: Chris@0: icon = QIcon(QString(":/icons/%1.png") Chris@0: .arg(LayerFactory::getInstance()->getLayerIconName(type))); Chris@0: Chris@0: mainText = tr("Add New %1 Layer").arg(name); Chris@0: tipText = tr("Add a new empty layer of type %1").arg(name); Chris@0: Chris@0: action = new QAction(icon, mainText, this); Chris@0: action->setStatusTip(tipText); Chris@0: Chris@0: if (type == LayerFactory::Text) { Chris@0: action->setShortcut(tr("Alt+T")); Chris@0: } Chris@0: Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@0: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_layerActions[action] = type; Chris@0: menu->addAction(action); Chris@0: m_rightButtonLayerMenu->addAction(action); Chris@0: } Chris@0: Chris@0: m_rightButtonLayerMenu->addSeparator(); Chris@0: menu->addSeparator(); Chris@0: Chris@0: int channels = 1; Chris@0: if (getMainModel()) channels = getMainModel()->getChannelCount(); Chris@0: Chris@0: if (channels < 1) channels = 1; Chris@0: Chris@0: LayerFactory::LayerType backgroundTypes[] = { Chris@0: LayerFactory::Waveform, Chris@0: LayerFactory::Spectrogram, Chris@0: LayerFactory::MelodicRangeSpectrogram, Chris@7: LayerFactory::PeakFrequencySpectrogram, Chris@7: LayerFactory::Spectrum Chris@0: }; Chris@0: Chris@0: for (unsigned int i = 0; Chris@0: i < sizeof(backgroundTypes)/sizeof(backgroundTypes[0]); ++i) { Chris@0: Chris@0: for (int menuType = 0; menuType <= 1; ++menuType) { // pane, layer Chris@0: Chris@0: if (menuType == 0) menu = m_paneMenu; Chris@0: else menu = m_layerMenu; Chris@0: Chris@0: QMenu *submenu = 0; Chris@0: Chris@0: for (int c = 0; c <= channels; ++c) { Chris@0: Chris@0: if (c == 1 && channels == 1) continue; Chris@0: bool isDefault = (c == 0); Chris@0: bool isOnly = (isDefault && (channels == 1)); Chris@0: Chris@0: if (menuType == 1) { Chris@0: if (isDefault) isOnly = true; Chris@0: else continue; Chris@0: } Chris@0: Chris@0: QIcon icon; Chris@0: QString mainText, shortcutText, tipText, channelText; Chris@0: LayerFactory::LayerType type = backgroundTypes[i]; Chris@0: bool mono = true; Chris@0: Chris@0: switch (type) { Chris@0: Chris@0: case LayerFactory::Waveform: Chris@0: icon = QIcon(":/icons/waveform.png"); Chris@0: mainText = tr("Add &Waveform"); Chris@0: if (menuType == 0) { Chris@0: shortcutText = tr("Alt+W"); Chris@0: tipText = tr("Add a new pane showing a waveform view"); Chris@0: } else { Chris@0: tipText = tr("Add a new layer showing a waveform view"); Chris@0: } Chris@0: mono = false; Chris@0: break; Chris@0: Chris@0: case LayerFactory::Spectrogram: Chris@0: mainText = tr("Add &Spectrogram"); Chris@0: if (menuType == 0) { Chris@0: shortcutText = tr("Alt+S"); Chris@0: tipText = tr("Add a new pane showing a dB spectrogram"); Chris@0: } else { Chris@0: tipText = tr("Add a new layer showing a dB spectrogram"); Chris@0: } Chris@0: break; Chris@0: Chris@0: case LayerFactory::MelodicRangeSpectrogram: Chris@0: mainText = tr("Add &Melodic Range Spectrogram"); Chris@0: if (menuType == 0) { Chris@0: shortcutText = tr("Alt+M"); Chris@0: tipText = tr("Add a new pane showing a spectrogram set up for a pitch overview"); Chris@0: } else { Chris@0: tipText = tr("Add a new layer showing a spectrogram set up for a pitch overview"); Chris@0: } Chris@0: break; Chris@0: Chris@0: case LayerFactory::PeakFrequencySpectrogram: Chris@0: mainText = tr("Add &Peak Frequency Spectrogram"); Chris@0: if (menuType == 0) { Chris@0: shortcutText = tr("Alt+P"); Chris@0: tipText = tr("Add a new pane showing a spectrogram set up for tracking frequencies"); Chris@0: } else { Chris@0: tipText = tr("Add a new layer showing a spectrogram set up for tracking frequencies"); Chris@0: } Chris@0: break; Chris@0: Chris@7: case LayerFactory::Spectrum: Chris@7: mainText = tr("Add Spectr&um"); Chris@7: if (menuType == 0) { Chris@7: shortcutText = tr("Alt+U"); Chris@7: tipText = tr("Add a new pane showing a frequency spectrum"); Chris@7: } else { Chris@7: tipText = tr("Add a new layer showing a frequency spectrum"); Chris@7: } Chris@7: break; Chris@7: Chris@0: default: break; Chris@0: } Chris@0: Chris@0: if (isOnly) { Chris@0: Chris@0: action = new QAction(icon, mainText, this); Chris@0: action->setShortcut(shortcutText); Chris@0: action->setStatusTip(tipText); Chris@0: if (menuType == 0) { Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(addPane())); Chris@0: connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_paneActions[action] = PaneConfiguration(type); Chris@0: } else { Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@0: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_layerActions[action] = type; Chris@0: } Chris@0: menu->addAction(action); Chris@0: Chris@0: } else { Chris@0: Chris@0: QString actionText; Chris@0: if (c == 0) Chris@0: if (mono) actionText = tr("&All Channels Mixed"); Chris@0: else actionText = tr("&All Channels"); Chris@0: else actionText = tr("Channel &%1").arg(c); Chris@0: Chris@0: if (!submenu) { Chris@0: submenu = menu->addMenu(mainText); Chris@0: } Chris@0: Chris@0: action = new QAction(icon, actionText, this); Chris@0: if (isDefault) action->setShortcut(shortcutText); Chris@0: action->setStatusTip(tipText); Chris@0: if (menuType == 0) { Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(addPane())); Chris@0: connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_paneActions[action] = PaneConfiguration(type, c - 1); Chris@0: } else { Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@0: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_layerActions[action] = type; Chris@0: } Chris@0: submenu->addAction(action); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: menu = m_paneMenu; Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Pane"), this); Chris@0: action->setShortcut(tr("Alt+D")); Chris@0: action->setStatusTip(tr("Delete the currently selected pane")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentPane())); Chris@0: connect(this, SIGNAL(canDeleteCurrentPane(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: Chris@0: menu = m_layerMenu; Chris@0: Chris@0: action = new QAction(QIcon(":/icons/timeruler.png"), tr("Add &Time Ruler"), this); Chris@0: action->setStatusTip(tr("Add a new layer showing a time ruler")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@0: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_layerActions[action] = LayerFactory::TimeRuler; Chris@0: menu->addAction(action); Chris@0: Chris@0: menu->addSeparator(); Chris@0: Chris@0: m_existingLayersMenu = menu->addMenu(tr("Add &Existing Layer")); Chris@0: m_rightButtonLayerMenu->addMenu(m_existingLayersMenu); Chris@0: setupExistingLayersMenu(); Chris@0: Chris@0: m_rightButtonLayerMenu->addSeparator(); Chris@0: menu->addSeparator(); Chris@0: Chris@0: action = new QAction(tr("&Rename Layer..."), this); Chris@0: action->setShortcut(tr("Alt+R")); Chris@0: action->setStatusTip(tr("Rename the currently active layer")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(renameCurrentLayer())); Chris@0: connect(this, SIGNAL(canRenameLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: m_rightButtonLayerMenu->addAction(action); Chris@0: Chris@0: action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Layer"), this); Chris@0: action->setShortcut(tr("Alt+Shift+D")); Chris@0: action->setStatusTip(tr("Delete the currently active layer")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentLayer())); Chris@0: connect(this, SIGNAL(canDeleteCurrentLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: menu->addAction(action); Chris@0: m_rightButtonLayerMenu->addAction(action); Chris@0: Chris@0: if (!m_mainMenusCreated) { Chris@0: Chris@0: menu = menuBar()->addMenu(tr("&Help")); Chris@0: Chris@0: action = new QAction(tr("&Help Reference"), this); Chris@0: action->setStatusTip(tr("Open the Sonic Visualiser reference manual")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(help())); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("Sonic Visualiser on the &Web"), this); Chris@0: action->setStatusTip(tr("Open the Sonic Visualiser website")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(website())); Chris@0: menu->addAction(action); Chris@0: Chris@0: action = new QAction(tr("&About Sonic Visualiser"), this); Chris@0: action->setStatusTip(tr("Show information about Sonic Visualiser")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(about())); Chris@0: menu->addAction(action); Chris@0: /* Chris@0: action = new QAction(tr("About &Qt"), this); Chris@0: action->setStatusTip(tr("Show information about Qt")); Chris@0: connect(action, SIGNAL(triggered()), Chris@0: QApplication::getInstance(), SLOT(aboutQt())); Chris@0: menu->addAction(action); Chris@0: */ Chris@0: } Chris@0: Chris@0: m_mainMenusCreated = true; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupRecentFilesMenu() Chris@0: { Chris@0: m_recentFilesMenu->clear(); Chris@34: vector files = m_recentFiles.getRecent(); Chris@0: for (size_t i = 0; i < files.size(); ++i) { Chris@0: QAction *action = new QAction(files[i], this); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); Chris@0: m_recentFilesMenu->addAction(action); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@34: MainWindow::setupRecentTransformsMenu() Chris@34: { Chris@34: m_recentTransformsMenu->clear(); Chris@34: vector transforms = m_recentTransforms.getRecent(); Chris@34: for (size_t i = 0; i < transforms.size(); ++i) { Chris@34: TransformActionReverseMap::iterator ti = Chris@34: m_transformActionsReverse.find(transforms[i]); Chris@34: if (ti == m_transformActionsReverse.end()) { Chris@34: std::cerr << "WARNING: MainWindow::setupRecentTransformsMenu: " Chris@34: << "Unknown transform \"" << transforms[i].toStdString() Chris@34: << "\" in recent transforms list" << std::endl; Chris@34: continue; Chris@34: } Chris@34: m_recentTransformsMenu->addAction(ti->second); Chris@34: } Chris@34: } Chris@34: Chris@34: void Chris@0: MainWindow::setupExistingLayersMenu() Chris@0: { Chris@0: if (!m_existingLayersMenu) return; // should have been created by setupMenus Chris@0: Chris@0: // std::cerr << "MainWindow::setupExistingLayersMenu" << std::endl; Chris@0: Chris@0: m_existingLayersMenu->clear(); Chris@0: m_existingLayerActions.clear(); Chris@0: Chris@33: vector orderedLayers; Chris@33: set observedLayers; Chris@0: Chris@0: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@0: Chris@0: Pane *pane = m_paneStack->getPane(i); Chris@0: if (!pane) continue; Chris@0: Chris@0: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@0: Chris@0: Layer *layer = pane->getLayer(j); Chris@0: if (!layer) continue; Chris@0: if (observedLayers.find(layer) != observedLayers.end()) { Chris@0: std::cerr << "found duplicate layer " << layer << std::endl; Chris@0: continue; Chris@0: } Chris@0: Chris@0: // std::cerr << "found new layer " << layer << " (name = " Chris@0: // << layer->getLayerPresentationName().toStdString() << ")" << std::endl; Chris@0: Chris@0: orderedLayers.push_back(layer); Chris@0: observedLayers.insert(layer); Chris@0: } Chris@0: } Chris@0: Chris@33: map observedNames; Chris@0: Chris@0: for (int i = 0; i < orderedLayers.size(); ++i) { Chris@0: Chris@0: QString name = orderedLayers[i]->getLayerPresentationName(); Chris@0: int n = ++observedNames[name]; Chris@0: if (n > 1) name = QString("%1 <%2>").arg(name).arg(n); Chris@0: Chris@0: QAction *action = new QAction(name, this); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@0: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_existingLayerActions[action] = orderedLayers[i]; Chris@0: Chris@0: m_existingLayersMenu->addAction(action); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupToolbars() Chris@0: { Chris@0: QToolBar *toolbar = addToolBar(tr("Transport Toolbar")); Chris@0: Chris@0: QAction *action = toolbar->addAction(QIcon(":/icons/rewind-start.png"), Chris@0: tr("Rewind to Start")); Chris@0: action->setShortcut(tr("Home")); Chris@0: action->setStatusTip(tr("Rewind to the start")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(rewindStart())); Chris@0: connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/rewind.png"), Chris@0: tr("Rewind")); Chris@0: action->setShortcut(tr("PageUp")); Chris@0: action->setStatusTip(tr("Rewind to the previous time instant in the current layer")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(rewind())); Chris@0: connect(this, SIGNAL(canRewind(bool)), action, SLOT(setEnabled(bool))); Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/playpause.png"), Chris@0: tr("Play / Pause")); Chris@0: action->setCheckable(true); Chris@0: action->setShortcut(tr("Space")); Chris@0: action->setStatusTip(tr("Start or stop playback from the current position")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(play())); Chris@0: connect(m_playSource, SIGNAL(playStatusChanged(bool)), Chris@0: action, SLOT(setChecked(bool))); Chris@0: connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/ffwd.png"), Chris@0: tr("Fast Forward")); Chris@0: action->setShortcut(tr("PageDown")); Chris@0: action->setStatusTip(tr("Fast forward to the next time instant in the current layer")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(ffwd())); Chris@0: connect(this, SIGNAL(canFfwd(bool)), action, SLOT(setEnabled(bool))); Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/ffwd-end.png"), Chris@0: tr("Fast Forward to End")); Chris@0: action->setShortcut(tr("End")); Chris@0: action->setStatusTip(tr("Fast-forward to the end")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(ffwdEnd())); Chris@0: connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); Chris@0: Chris@0: toolbar = addToolBar(tr("Play Mode Toolbar")); Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/playselection.png"), Chris@0: tr("Constrain Playback to Selection")); Chris@0: action->setCheckable(true); Chris@0: action->setChecked(m_viewManager->getPlaySelectionMode()); Chris@0: action->setShortcut(tr("s")); Chris@0: action->setStatusTip(tr("Constrain playback to the selected area")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); Chris@0: connect(this, SIGNAL(canPlaySelection(bool)), action, SLOT(setEnabled(bool))); Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/playloop.png"), Chris@0: tr("Loop Playback")); Chris@0: action->setCheckable(true); Chris@0: action->setChecked(m_viewManager->getPlayLoopMode()); Chris@0: action->setShortcut(tr("l")); Chris@0: action->setStatusTip(tr("Loop playback")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(playLoopToggled())); Chris@0: connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); Chris@0: Chris@0: toolbar = addToolBar(tr("Edit Toolbar")); Chris@0: CommandHistory::getInstance()->registerToolbar(toolbar); Chris@0: Chris@0: toolbar = addToolBar(tr("Tools Toolbar")); Chris@0: QActionGroup *group = new QActionGroup(this); Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/navigate.png"), Chris@0: tr("Navigate")); Chris@0: action->setCheckable(true); Chris@0: action->setChecked(true); Chris@0: action->setShortcut(tr("1")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); Chris@0: group->addAction(action); Chris@0: m_toolActions[ViewManager::NavigateMode] = action; Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/select.png"), Chris@0: tr("Select")); Chris@0: action->setCheckable(true); Chris@0: action->setShortcut(tr("2")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(toolSelectSelected())); Chris@0: group->addAction(action); Chris@0: m_toolActions[ViewManager::SelectMode] = action; Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/move.png"), Chris@0: tr("Edit")); Chris@0: action->setCheckable(true); Chris@0: action->setShortcut(tr("3")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); Chris@0: connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: group->addAction(action); Chris@0: m_toolActions[ViewManager::EditMode] = action; Chris@0: Chris@0: action = toolbar->addAction(QIcon(":/icons/draw.png"), Chris@0: tr("Draw")); Chris@0: action->setCheckable(true); Chris@0: action->setShortcut(tr("4")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(toolDrawSelected())); Chris@0: connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: group->addAction(action); Chris@0: m_toolActions[ViewManager::DrawMode] = action; Chris@0: Chris@0: // action = toolbar->addAction(QIcon(":/icons/text.png"), Chris@0: // tr("Text")); Chris@0: // action->setCheckable(true); Chris@0: // action->setShortcut(tr("5")); Chris@0: // connect(action, SIGNAL(triggered()), this, SLOT(toolTextSelected())); Chris@0: // group->addAction(action); Chris@0: // m_toolActions[ViewManager::TextMode] = action; Chris@0: Chris@0: toolNavigateSelected(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::updateMenuStates() Chris@0: { Chris@0: bool haveCurrentPane = Chris@0: (m_paneStack && Chris@0: (m_paneStack->getCurrentPane() != 0)); Chris@0: bool haveCurrentLayer = Chris@0: (haveCurrentPane && Chris@0: (m_paneStack->getCurrentPane()->getSelectedLayer())); Chris@0: bool haveMainModel = Chris@0: (getMainModel() != 0); Chris@0: bool havePlayTarget = Chris@0: (m_playTarget != 0); Chris@0: bool haveSelection = Chris@0: (m_viewManager && Chris@0: !m_viewManager->getSelections().empty()); Chris@0: bool haveCurrentEditableLayer = Chris@0: (haveCurrentLayer && Chris@0: m_paneStack->getCurrentPane()->getSelectedLayer()-> Chris@0: isLayerEditable()); Chris@0: bool haveCurrentTimeInstantsLayer = Chris@0: (haveCurrentLayer && Chris@0: dynamic_cast Chris@0: (m_paneStack->getCurrentPane()->getSelectedLayer())); Chris@0: bool haveCurrentTimeValueLayer = Chris@0: (haveCurrentLayer && Chris@0: dynamic_cast Chris@0: (m_paneStack->getCurrentPane()->getSelectedLayer())); Chris@0: bool haveCurrentColour3DPlot = Chris@0: (haveCurrentLayer && Chris@0: dynamic_cast Chris@0: (m_paneStack->getCurrentPane()->getSelectedLayer())); Chris@0: bool haveClipboardContents = Chris@0: (m_viewManager && Chris@0: !m_viewManager->getClipboard().empty()); Chris@0: Chris@0: emit canAddPane(haveMainModel); Chris@0: emit canDeleteCurrentPane(haveCurrentPane); Chris@0: emit canZoom(haveMainModel && haveCurrentPane); Chris@0: emit canScroll(haveMainModel && haveCurrentPane); Chris@0: emit canAddLayer(haveMainModel && haveCurrentPane); Chris@0: emit canImportMoreAudio(haveMainModel); Chris@0: emit canImportLayer(haveMainModel && haveCurrentPane); Chris@0: emit canExportAudio(haveMainModel); Chris@0: emit canExportLayer(haveMainModel && Chris@0: (haveCurrentEditableLayer || haveCurrentColour3DPlot)); Chris@0: emit canDeleteCurrentLayer(haveCurrentLayer); Chris@0: emit canRenameLayer(haveCurrentLayer); Chris@0: emit canEditLayer(haveCurrentEditableLayer); Chris@0: emit canSelect(haveMainModel && haveCurrentPane); Chris@0: emit canPlay(/*!!! haveMainModel && */ havePlayTarget); Chris@0: emit canFfwd(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer); Chris@0: emit canRewind(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer); Chris@0: emit canPaste(haveCurrentEditableLayer && haveClipboardContents); Chris@0: emit canInsertInstant(haveCurrentPane); Chris@0: emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection); Chris@0: emit canClearSelection(haveSelection); Chris@0: emit canEditSelection(haveSelection && haveCurrentEditableLayer); Chris@0: emit canSave(m_sessionFile != "" && m_documentModified); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::updateDescriptionLabel() Chris@0: { Chris@0: if (!getMainModel()) { Chris@0: m_descriptionLabel->setText(tr("No audio file loaded.")); Chris@0: return; Chris@0: } Chris@0: Chris@0: QString description; Chris@0: Chris@0: size_t ssr = getMainModel()->getSampleRate(); Chris@0: size_t tsr = ssr; Chris@0: if (m_playSource) tsr = m_playSource->getTargetSampleRate(); Chris@0: Chris@0: if (ssr != tsr) { Chris@0: description = tr("%1Hz (resampling to %2Hz)").arg(ssr).arg(tsr); Chris@0: } else { Chris@0: description = QString("%1Hz").arg(ssr); Chris@0: } Chris@0: Chris@0: description = QString("%1 - %2") Chris@0: .arg(RealTime::frame2RealTime(getMainModel()->getEndFrame(), ssr) Chris@0: .toText(false).c_str()) Chris@0: .arg(description); Chris@0: Chris@0: m_descriptionLabel->setText(description); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::documentModified() Chris@0: { Chris@0: // std::cerr << "MainWindow::documentModified" << std::endl; Chris@0: Chris@0: if (!m_documentModified) { Chris@0: setWindowTitle(tr("%1 (modified)").arg(windowTitle())); Chris@0: } Chris@0: Chris@0: m_documentModified = true; Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::documentRestored() Chris@0: { Chris@0: // std::cerr << "MainWindow::documentRestored" << std::endl; Chris@0: Chris@0: if (m_documentModified) { Chris@0: QString wt(windowTitle()); Chris@0: wt.replace(tr(" (modified)"), ""); Chris@0: setWindowTitle(wt); Chris@0: } Chris@0: Chris@0: m_documentModified = false; Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::playLoopToggled() Chris@0: { Chris@0: QAction *action = dynamic_cast(sender()); Chris@0: Chris@0: if (action) { Chris@0: m_viewManager->setPlayLoopMode(action->isChecked()); Chris@0: } else { Chris@0: m_viewManager->setPlayLoopMode(!m_viewManager->getPlayLoopMode()); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::playSelectionToggled() Chris@0: { Chris@0: QAction *action = dynamic_cast(sender()); Chris@0: Chris@0: if (action) { Chris@0: m_viewManager->setPlaySelectionMode(action->isChecked()); Chris@0: } else { Chris@0: m_viewManager->setPlaySelectionMode(!m_viewManager->getPlaySelectionMode()); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::currentPaneChanged(Pane *) Chris@0: { Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::currentLayerChanged(Pane *, Layer *) Chris@0: { Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::toolNavigateSelected() Chris@0: { Chris@0: m_viewManager->setToolMode(ViewManager::NavigateMode); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::toolSelectSelected() Chris@0: { Chris@0: m_viewManager->setToolMode(ViewManager::SelectMode); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::toolEditSelected() Chris@0: { Chris@0: m_viewManager->setToolMode(ViewManager::EditMode); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::toolDrawSelected() Chris@0: { Chris@0: m_viewManager->setToolMode(ViewManager::DrawMode); Chris@0: } Chris@0: Chris@0: //void Chris@0: //MainWindow::toolTextSelected() Chris@0: //{ Chris@0: // m_viewManager->setToolMode(ViewManager::TextMode); Chris@0: //} Chris@0: Chris@0: void Chris@0: MainWindow::selectAll() Chris@0: { Chris@0: if (!getMainModel()) return; Chris@0: m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), Chris@0: getMainModel()->getEndFrame())); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::selectToStart() Chris@0: { Chris@0: if (!getMainModel()) return; Chris@0: m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), Chris@0: m_viewManager->getGlobalCentreFrame())); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::selectToEnd() Chris@0: { Chris@0: if (!getMainModel()) return; Chris@0: m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(), Chris@0: getMainModel()->getEndFrame())); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::selectVisible() Chris@0: { Chris@0: Model *model = getMainModel(); Chris@0: if (!model) return; Chris@0: Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (!currentPane) return; Chris@0: Chris@0: size_t startFrame, endFrame; Chris@0: Chris@0: if (currentPane->getStartFrame() < 0) startFrame = 0; Chris@0: else startFrame = currentPane->getStartFrame(); Chris@0: Chris@0: if (currentPane->getEndFrame() > model->getEndFrame()) endFrame = model->getEndFrame(); Chris@0: else endFrame = currentPane->getEndFrame(); Chris@0: Chris@0: m_viewManager->setSelection(Selection(startFrame, endFrame)); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::clearSelection() Chris@0: { Chris@0: m_viewManager->clearSelections(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::cut() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (!currentPane) return; Chris@0: Chris@0: Layer *layer = currentPane->getSelectedLayer(); Chris@0: if (!layer) return; Chris@0: Chris@0: Clipboard &clipboard = m_viewManager->getClipboard(); Chris@0: clipboard.clear(); Chris@0: Chris@0: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@0: Chris@0: CommandHistory::getInstance()->startCompoundOperation(tr("Cut"), true); Chris@0: Chris@0: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@0: i != selections.end(); ++i) { Chris@0: layer->copy(*i, clipboard); Chris@0: layer->deleteSelection(*i); Chris@0: } Chris@0: Chris@0: CommandHistory::getInstance()->endCompoundOperation(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::copy() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (!currentPane) return; Chris@0: Chris@0: Layer *layer = currentPane->getSelectedLayer(); Chris@0: if (!layer) return; Chris@0: Chris@0: Clipboard &clipboard = m_viewManager->getClipboard(); Chris@0: clipboard.clear(); Chris@0: Chris@0: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@0: Chris@0: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@0: i != selections.end(); ++i) { Chris@0: layer->copy(*i, clipboard); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::paste() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (!currentPane) return; Chris@0: Chris@0: //!!! if we have no current layer, we should create one of the most Chris@0: // appropriate type Chris@0: Chris@0: Layer *layer = currentPane->getSelectedLayer(); Chris@0: if (!layer) return; Chris@0: Chris@0: Clipboard &clipboard = m_viewManager->getClipboard(); Chris@0: Clipboard::PointList contents = clipboard.getPoints(); Chris@0: /* Chris@0: long minFrame = 0; Chris@0: bool have = false; Chris@0: for (int i = 0; i < contents.size(); ++i) { Chris@0: if (!contents[i].haveFrame()) continue; Chris@0: if (!have || contents[i].getFrame() < minFrame) { Chris@0: minFrame = contents[i].getFrame(); Chris@0: have = true; Chris@0: } Chris@0: } Chris@0: Chris@0: long frameOffset = long(m_viewManager->getGlobalCentreFrame()) - minFrame; Chris@0: Chris@0: layer->paste(clipboard, frameOffset); Chris@0: */ Chris@0: layer->paste(clipboard, 0, true); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::deleteSelected() Chris@0: { Chris@0: if (m_paneStack->getCurrentPane() && Chris@0: m_paneStack->getCurrentPane()->getSelectedLayer()) { Chris@0: Chris@0: MultiSelection::SelectionList selections = Chris@0: m_viewManager->getSelections(); Chris@0: Chris@0: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@0: i != selections.end(); ++i) { Chris@0: Chris@0: m_paneStack->getCurrentPane()->getSelectedLayer()->deleteSelection(*i); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::insertInstant() Chris@0: { Chris@0: int frame = m_viewManager->getPlaybackFrame(); Chris@0: Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: if (!pane) { Chris@0: return; Chris@0: } Chris@0: Chris@0: Layer *layer = dynamic_cast Chris@0: (pane->getSelectedLayer()); Chris@0: Chris@0: if (!layer) { Chris@0: for (int i = pane->getLayerCount(); i > 0; --i) { Chris@0: layer = dynamic_cast(pane->getLayer(i - 1)); Chris@0: if (layer) break; Chris@0: } Chris@0: Chris@0: if (!layer) { Chris@0: CommandHistory::getInstance()->startCompoundOperation Chris@0: (tr("Add Point"), true); Chris@0: layer = m_document->createEmptyLayer(LayerFactory::TimeInstants); Chris@0: if (layer) { Chris@0: m_document->addLayerToView(pane, layer); Chris@0: m_paneStack->setCurrentLayer(pane, layer); Chris@0: } Chris@0: CommandHistory::getInstance()->endCompoundOperation(); Chris@0: } Chris@0: } Chris@0: Chris@0: if (layer) { Chris@0: Chris@0: Model *model = layer->getModel(); Chris@0: SparseOneDimensionalModel *sodm = dynamic_cast Chris@0: (model); Chris@0: Chris@0: if (sodm) { Chris@0: SparseOneDimensionalModel::Point point Chris@0: (frame, QString("%1").arg(sodm->getPointCount() + 1)); Chris@0: CommandHistory::getInstance()->addCommand Chris@0: (new SparseOneDimensionalModel::AddPointCommand(sodm, point, Chris@0: tr("Add Points")), Chris@0: true, true); // bundled Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::importAudio() Chris@0: { Chris@0: QString orig = m_audioFile; Chris@0: Chris@0: // std::cerr << "orig = " << orig.toStdString() << std::endl; Chris@0: Chris@0: if (orig == "") orig = "."; Chris@0: Chris@0: QString path = QFileDialog::getOpenFileName Chris@0: (this, tr("Select an audio file"), orig, Chris@0: tr("Audio files (%1)\nAll files (*.*)") Chris@0: .arg(AudioFileReaderFactory::getKnownExtensions())); Chris@0: Chris@0: if (path != "") { Chris@0: if (!openAudioFile(path, ReplaceMainModel)) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("Audio file \"%1\" could not be opened").arg(path)); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::importMoreAudio() Chris@0: { Chris@0: QString orig = m_audioFile; Chris@0: Chris@0: // std::cerr << "orig = " << orig.toStdString() << std::endl; Chris@0: Chris@0: if (orig == "") orig = "."; Chris@0: Chris@0: QString path = QFileDialog::getOpenFileName Chris@0: (this, tr("Select an audio file"), orig, Chris@0: tr("Audio files (%1)\nAll files (*.*)") Chris@0: .arg(AudioFileReaderFactory::getKnownExtensions())); Chris@0: Chris@0: if (path != "") { Chris@0: if (!openAudioFile(path, CreateAdditionalModel)) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("Audio file \"%1\" could not be opened").arg(path)); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::exportAudio() Chris@0: { Chris@0: if (!getMainModel()) return; Chris@0: Chris@0: QString path = QFileDialog::getSaveFileName Chris@0: (this, tr("Select a file to export to"), ".", Chris@0: tr("WAV audio files (*.wav)\nAll files (*.*)")); Chris@0: Chris@0: if (path == "") return; Chris@0: Chris@0: if (!path.endsWith(".wav")) path = path + ".wav"; Chris@0: Chris@0: bool ok = false; Chris@0: QString error; Chris@0: Chris@0: MultiSelection ms = m_viewManager->getSelection(); Chris@0: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@0: Chris@0: bool multiple = false; Chris@0: Chris@38: MultiSelection *selectionToWrite = 0; Chris@38: Chris@38: if (selections.size() == 1) { Chris@0: Chris@0: QStringList items; Chris@0: items << tr("Export the selected region only") Chris@0: << tr("Export the whole audio file"); Chris@0: Chris@0: bool ok = false; Chris@0: QString item = ListInputDialog::getItem Chris@0: (this, tr("Select region to export"), Chris@0: tr("Which region from the original audio file do you want to export?"), Chris@0: items, 0, &ok); Chris@0: Chris@0: if (!ok || item.isEmpty()) return; Chris@0: Chris@38: if (item == items[0]) selectionToWrite = &ms; Chris@38: Chris@38: } else if (selections.size() > 1) { Chris@0: Chris@0: QStringList items; Chris@0: items << tr("Export the selected regions into a single audio file") Chris@0: << tr("Export the selected regions into separate files") Chris@0: << tr("Export the whole audio file"); Chris@0: Chris@0: QString item = ListInputDialog::getItem Chris@0: (this, tr("Select region to export"), Chris@0: tr("Multiple regions of the original audio file are selected.\nWhat do you want to export?"), Chris@0: items, 0, &ok); Chris@0: Chris@0: if (!ok || item.isEmpty()) return; Chris@0: Chris@0: if (item == items[0]) { Chris@0: Chris@38: selectionToWrite = &ms; Chris@38: Chris@38: } else if (item == items[1]) { Chris@0: Chris@0: multiple = true; Chris@0: Chris@0: int n = 1; Chris@0: QString base = path; Chris@0: base.replace(".wav", ""); Chris@0: Chris@0: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@0: i != selections.end(); ++i) { Chris@0: Chris@0: MultiSelection subms; Chris@0: subms.setSelection(*i); Chris@0: Chris@0: QString subpath = QString("%1.%2.wav").arg(base).arg(n); Chris@0: ++n; Chris@0: Chris@0: if (QFileInfo(subpath).exists()) { Chris@0: error = tr("Fragment file %1 already exists, aborting").arg(subpath); Chris@0: break; Chris@0: } Chris@0: Chris@38: WavFileWriter subwriter(subpath, Chris@38: getMainModel()->getSampleRate(), Chris@38: getMainModel()->getChannelCount()); Chris@38: subwriter.writeModel(getMainModel(), &subms); Chris@0: ok = subwriter.isOK(); Chris@0: Chris@0: if (!ok) { Chris@0: error = subwriter.getError(); Chris@0: break; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@38: if (!multiple) { Chris@38: WavFileWriter writer(path, Chris@38: getMainModel()->getSampleRate(), Chris@38: getMainModel()->getChannelCount()); Chris@38: writer.writeModel(getMainModel(), selectionToWrite); Chris@38: ok = writer.isOK(); Chris@38: error = writer.getError(); Chris@0: } Chris@0: Chris@0: if (ok) { Chris@0: if (!multiple) { Chris@34: m_recentFiles.addFile(path); Chris@0: } Chris@0: } else { Chris@0: QMessageBox::critical(this, tr("Failed to write file"), error); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::importLayer() Chris@0: { Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: Chris@0: if (!pane) { Chris@0: // shouldn't happen, as the menu action should have been disabled Chris@0: std::cerr << "WARNING: MainWindow::importLayer: no current pane" << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: if (!getMainModel()) { Chris@0: // shouldn't happen, as the menu action should have been disabled Chris@0: std::cerr << "WARNING: MainWindow::importLayer: No main model -- hence no default sample rate available" << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: QString path = QFileDialog::getOpenFileName Chris@0: (this, tr("Select file"), ".", Chris@0: 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())); Chris@0: Chris@0: if (path != "") { Chris@0: Chris@0: if (!openLayerFile(path)) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("File %1 could not be opened.").arg(path)); Chris@0: return; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: bool Chris@0: MainWindow::openLayerFile(QString path) Chris@0: { Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: Chris@0: if (!pane) { Chris@0: // shouldn't happen, as the menu action should have been disabled Chris@0: std::cerr << "WARNING: MainWindow::openLayerFile: no current pane" << std::endl; Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (!getMainModel()) { Chris@0: // shouldn't happen, as the menu action should have been disabled Chris@0: std::cerr << "WARNING: MainWindow::openLayerFile: No main model -- hence no default sample rate available" << std::endl; Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (path.endsWith(".svl") || path.endsWith(".xml")) { Chris@0: Chris@0: PaneCallback callback(this); Chris@0: QFile file(path); Chris@0: Chris@0: if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { Chris@0: std::cerr << "ERROR: MainWindow::openLayerFile(" Chris@0: << path.toStdString() Chris@0: << "): Failed to open file for reading" << std::endl; Chris@0: return false; Chris@0: } Chris@0: Chris@0: SVFileReader reader(m_document, callback); Chris@0: reader.setCurrentPane(pane); Chris@0: Chris@0: QXmlInputSource inputSource(&file); Chris@0: reader.parse(inputSource); Chris@0: Chris@0: if (!reader.isOK()) { Chris@0: std::cerr << "ERROR: MainWindow::openLayerFile(" Chris@0: << path.toStdString() Chris@0: << "): Failed to read XML file: " Chris@0: << reader.getErrorString().toStdString() << std::endl; Chris@0: return false; Chris@0: } Chris@0: Chris@34: m_recentFiles.addFile(path); Chris@0: return true; Chris@0: Chris@0: } else { Chris@0: Chris@0: Model *model = DataFileReaderFactory::load(path, getMainModel()->getSampleRate()); Chris@0: Chris@0: if (model) { Chris@0: Layer *newLayer = m_document->createImportedLayer(model); Chris@0: if (newLayer) { Chris@0: m_document->addLayerToView(pane, newLayer); Chris@34: m_recentFiles.addFile(path); Chris@0: return true; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::exportLayer() Chris@0: { Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: if (!pane) return; Chris@0: Chris@0: Layer *layer = pane->getSelectedLayer(); Chris@0: if (!layer) return; Chris@0: Chris@0: Model *model = layer->getModel(); Chris@0: if (!model) return; Chris@0: Chris@0: QString path = QFileDialog::getSaveFileName Chris@0: (this, tr("Select a file to export to"), ".", Chris@0: tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)")); Chris@0: Chris@0: if (path == "") return; Chris@0: Chris@0: if (QFileInfo(path).suffix() == "") path += ".svl"; Chris@0: Chris@0: QString error; Chris@0: Chris@0: if (path.endsWith(".xml") || path.endsWith(".svl")) { Chris@0: Chris@0: QFile file(path); Chris@0: if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { Chris@0: error = tr("Failed to open file %1 for writing").arg(path); Chris@0: } else { Chris@0: QTextStream out(&file); Chris@0: out << "\n" Chris@0: << "\n" Chris@0: << "\n" Chris@0: << " \n"; Chris@0: Chris@0: model->toXml(out, " "); Chris@0: Chris@0: out << " \n" Chris@0: << " \n"; Chris@0: Chris@0: layer->toXml(out, " "); Chris@0: Chris@0: out << " \n" Chris@0: << "\n"; Chris@0: } Chris@0: Chris@0: } else { Chris@0: Chris@0: CSVFileWriter writer(path, model, Chris@0: (path.endsWith(".csv") ? "," : "\t")); Chris@0: writer.write(); Chris@0: Chris@0: if (!writer.isOK()) { Chris@0: error = writer.getError(); Chris@0: } Chris@0: } Chris@0: Chris@0: if (error != "") { Chris@0: QMessageBox::critical(this, tr("Failed to write file"), error); Chris@0: } else { Chris@34: m_recentFiles.addFile(path); Chris@0: } Chris@0: } Chris@0: Chris@0: bool Chris@0: MainWindow::openAudioFile(QString path, AudioFileOpenMode mode) Chris@0: { Chris@0: if (!(QFileInfo(path).exists() && Chris@0: QFileInfo(path).isFile() && Chris@0: QFileInfo(path).isReadable())) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: WaveFileModel *newModel = new WaveFileModel(path); Chris@0: Chris@0: if (!newModel->isOK()) { Chris@0: delete newModel; Chris@0: return false; Chris@0: } Chris@0: Chris@0: bool setAsMain = true; Chris@0: static bool prevSetAsMain = true; Chris@0: Chris@0: if (mode == CreateAdditionalModel) setAsMain = false; Chris@0: else if (mode == AskUser) { Chris@0: if (m_document->getMainModel()) { Chris@0: Chris@0: QStringList items; Chris@0: items << tr("Replace the existing main waveform") Chris@0: << tr("Load this file into a new waveform pane"); Chris@0: Chris@0: bool ok = false; Chris@0: QString item = ListInputDialog::getItem Chris@0: (this, tr("Select target for import"), Chris@0: tr("You already have an audio waveform loaded.\nWhat would you like to do with the new audio file?"), Chris@0: items, prevSetAsMain ? 0 : 1, &ok); Chris@0: Chris@0: if (!ok || item.isEmpty()) { Chris@0: delete newModel; Chris@0: return false; Chris@0: } Chris@0: Chris@0: setAsMain = (item == items[0]); Chris@0: prevSetAsMain = setAsMain; Chris@0: } Chris@0: } Chris@0: Chris@0: if (setAsMain) { Chris@0: Chris@0: Model *prevMain = getMainModel(); Chris@0: if (prevMain) m_playSource->removeModel(prevMain); Chris@0: Chris@0: PlayParameterRepository::getInstance()->clear(); Chris@0: Chris@0: // The clear() call will have removed the parameters for the Chris@0: // main model. Re-add them with the new one. Chris@0: PlayParameterRepository::getInstance()->addModel(newModel); Chris@0: Chris@0: m_document->setMainModel(newModel); Chris@0: setupMenus(); Chris@0: Chris@0: if (m_sessionFile == "") { Chris@0: setWindowTitle(tr("Sonic Visualiser: %1") Chris@0: .arg(QFileInfo(path).fileName())); Chris@0: CommandHistory::getInstance()->clear(); Chris@0: CommandHistory::getInstance()->documentSaved(); Chris@0: m_documentModified = false; Chris@0: } else { Chris@0: setWindowTitle(tr("Sonic Visualiser: %1 [%2]") Chris@0: .arg(QFileInfo(m_sessionFile).fileName()) Chris@0: .arg(QFileInfo(path).fileName())); Chris@0: if (m_documentModified) { Chris@0: m_documentModified = false; Chris@0: documentModified(); // so as to restore "(modified)" window title Chris@0: } Chris@0: } Chris@0: Chris@0: m_audioFile = path; Chris@0: Chris@0: } else { // !setAsMain Chris@0: Chris@0: CommandHistory::getInstance()->startCompoundOperation Chris@0: (tr("Import \"%1\"").arg(QFileInfo(path).fileName()), true); Chris@0: Chris@0: m_document->addImportedModel(newModel); Chris@0: Chris@0: AddPaneCommand *command = new AddPaneCommand(this); Chris@0: CommandHistory::getInstance()->addCommand(command); Chris@0: Chris@0: Pane *pane = command->getPane(); Chris@0: Chris@0: if (!m_timeRulerLayer) { Chris@0: m_timeRulerLayer = m_document->createMainModelLayer Chris@0: (LayerFactory::TimeRuler); Chris@0: } Chris@0: Chris@0: m_document->addLayerToView(pane, m_timeRulerLayer); Chris@0: Chris@0: Layer *newLayer = m_document->createImportedLayer(newModel); Chris@0: Chris@0: if (newLayer) { Chris@0: m_document->addLayerToView(pane, newLayer); Chris@0: } Chris@0: Chris@0: CommandHistory::getInstance()->endCompoundOperation(); Chris@0: } Chris@0: Chris@0: updateMenuStates(); Chris@34: m_recentFiles.addFile(path); Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::createPlayTarget() Chris@0: { Chris@0: if (m_playTarget) return; Chris@0: Chris@0: m_playTarget = AudioTargetFactory::createCallbackTarget(m_playSource); Chris@0: if (!m_playTarget) { Chris@0: QMessageBox::warning Chris@0: (this, tr("Couldn't open audio device"), Chris@0: tr("Could not open an audio device for playback.\nAudio playback will not be available during this session.\n"), Chris@0: QMessageBox::Ok, 0); Chris@0: } Chris@0: connect(m_fader, SIGNAL(valueChanged(float)), Chris@0: m_playTarget, SLOT(setOutputGain(float))); Chris@0: } Chris@0: Chris@0: WaveFileModel * Chris@0: MainWindow::getMainModel() Chris@0: { Chris@0: if (!m_document) return 0; Chris@0: return m_document->getMainModel(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::newSession() Chris@0: { Chris@0: if (!checkSaveModified()) return; Chris@0: Chris@0: closeSession(); Chris@0: createDocument(); Chris@0: Chris@0: Pane *pane = m_paneStack->addPane(); Chris@0: Chris@0: if (!m_timeRulerLayer) { Chris@0: m_timeRulerLayer = m_document->createMainModelLayer Chris@0: (LayerFactory::TimeRuler); Chris@0: } Chris@0: Chris@0: m_document->addLayerToView(pane, m_timeRulerLayer); Chris@0: Chris@0: Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); Chris@0: m_document->addLayerToView(pane, waveform); Chris@0: Chris@0: m_panner->registerView(pane); Chris@0: Chris@0: CommandHistory::getInstance()->clear(); Chris@0: CommandHistory::getInstance()->documentSaved(); Chris@0: documentRestored(); Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::createDocument() Chris@0: { Chris@0: m_document = new Document; Chris@0: Chris@0: connect(m_document, SIGNAL(layerAdded(Layer *)), Chris@0: this, SLOT(layerAdded(Layer *))); Chris@0: connect(m_document, SIGNAL(layerRemoved(Layer *)), Chris@0: this, SLOT(layerRemoved(Layer *))); Chris@0: connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)), Chris@0: this, SLOT(layerAboutToBeDeleted(Layer *))); Chris@0: connect(m_document, SIGNAL(layerInAView(Layer *, bool)), Chris@0: this, SLOT(layerInAView(Layer *, bool))); Chris@0: Chris@0: connect(m_document, SIGNAL(modelAdded(Model *)), Chris@0: this, SLOT(modelAdded(Model *))); Chris@0: connect(m_document, SIGNAL(mainModelChanged(WaveFileModel *)), Chris@0: this, SLOT(mainModelChanged(WaveFileModel *))); Chris@0: connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)), Chris@0: this, SLOT(modelAboutToBeDeleted(Model *))); Chris@0: Chris@0: connect(m_document, SIGNAL(modelGenerationFailed(QString)), Chris@0: this, SLOT(modelGenerationFailed(QString))); Chris@0: connect(m_document, SIGNAL(modelRegenerationFailed(QString)), Chris@0: this, SLOT(modelRegenerationFailed(QString))); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::closeSession() Chris@0: { Chris@0: if (!checkSaveModified()) return; Chris@0: Chris@0: while (m_paneStack->getPaneCount() > 0) { Chris@0: Chris@0: Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); Chris@0: Chris@0: while (pane->getLayerCount() > 0) { Chris@0: m_document->removeLayerFromView Chris@0: (pane, pane->getLayer(pane->getLayerCount() - 1)); Chris@0: } Chris@0: Chris@0: m_panner->unregisterView(pane); Chris@0: m_paneStack->deletePane(pane); Chris@0: } Chris@0: Chris@0: while (m_paneStack->getHiddenPaneCount() > 0) { Chris@0: Chris@0: Pane *pane = m_paneStack->getHiddenPane Chris@0: (m_paneStack->getHiddenPaneCount() - 1); Chris@0: Chris@0: while (pane->getLayerCount() > 0) { Chris@0: m_document->removeLayerFromView Chris@0: (pane, pane->getLayer(pane->getLayerCount() - 1)); Chris@0: } Chris@0: Chris@0: m_panner->unregisterView(pane); Chris@0: m_paneStack->deletePane(pane); Chris@0: } Chris@0: Chris@0: delete m_document; Chris@0: m_document = 0; Chris@0: m_viewManager->clearSelections(); Chris@0: m_timeRulerLayer = 0; // document owned this Chris@0: Chris@0: m_sessionFile = ""; Chris@0: setWindowTitle(tr("Sonic Visualiser")); Chris@0: Chris@0: CommandHistory::getInstance()->clear(); Chris@0: CommandHistory::getInstance()->documentSaved(); Chris@0: documentRestored(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::openSession() Chris@0: { Chris@0: if (!checkSaveModified()) return; Chris@0: Chris@0: QString orig = m_audioFile; Chris@0: if (orig == "") orig = "."; Chris@0: else orig = QFileInfo(orig).absoluteDir().canonicalPath(); Chris@0: Chris@0: QString path = QFileDialog::getOpenFileName Chris@0: (this, tr("Select a session file"), orig, Chris@0: tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)")); Chris@0: Chris@0: if (path.isEmpty()) return; Chris@0: Chris@0: if (!(QFileInfo(path).exists() && Chris@0: QFileInfo(path).isFile() && Chris@0: QFileInfo(path).isReadable())) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("File \"%1\" does not exist or is not a readable file").arg(path)); Chris@0: return; Chris@0: } Chris@0: Chris@0: if (!openSessionFile(path)) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("Session file \"%1\" could not be opened").arg(path)); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::openSomething() Chris@0: { Chris@0: QString orig = m_audioFile; Chris@0: if (orig == "") orig = "."; Chris@0: else orig = QFileInfo(orig).absoluteDir().canonicalPath(); Chris@0: Chris@0: bool canImportLayer = (getMainModel() != 0 && Chris@0: m_paneStack != 0 && Chris@0: m_paneStack->getCurrentPane() != 0); Chris@0: Chris@0: QString importSpec; Chris@0: Chris@0: if (canImportLayer) { Chris@0: importSpec = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)") Chris@0: .arg(AudioFileReaderFactory::getKnownExtensions()) Chris@0: .arg(DataFileReaderFactory::getKnownExtensions()); Chris@0: } else { Chris@0: importSpec = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)") Chris@0: .arg(AudioFileReaderFactory::getKnownExtensions()); Chris@0: } Chris@0: Chris@0: QString path = QFileDialog::getOpenFileName Chris@0: (this, tr("Select a file to open"), orig, importSpec); Chris@0: Chris@0: if (path.isEmpty()) return; Chris@0: Chris@0: if (!(QFileInfo(path).exists() && Chris@0: QFileInfo(path).isFile() && Chris@0: QFileInfo(path).isReadable())) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("File \"%1\" does not exist or is not a readable file").arg(path)); Chris@0: return; Chris@0: } Chris@0: Chris@0: if (path.endsWith(".sv")) { Chris@0: Chris@0: if (!checkSaveModified()) return; Chris@0: Chris@0: if (!openSessionFile(path)) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("Session file \"%1\" could not be opened").arg(path)); Chris@0: } Chris@0: Chris@0: } else { Chris@0: Chris@0: if (!openAudioFile(path, AskUser)) { Chris@0: Chris@0: if (!canImportLayer || !openLayerFile(path)) { Chris@0: Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("File \"%1\" could not be opened").arg(path)); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::openRecentFile() Chris@0: { Chris@0: QObject *obj = sender(); Chris@0: QAction *action = dynamic_cast(obj); Chris@0: Chris@0: if (!action) { Chris@0: std::cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" Chris@0: << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: QString path = action->text(); Chris@0: if (path == "") return; Chris@0: Chris@0: if (path.endsWith("sv")) { Chris@0: Chris@0: if (!checkSaveModified()) return ; Chris@0: Chris@0: if (!openSessionFile(path)) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("Session file \"%1\" could not be opened").arg(path)); Chris@0: } Chris@0: Chris@0: } else { Chris@0: Chris@0: if (!openAudioFile(path, AskUser)) { Chris@0: Chris@0: bool canImportLayer = (getMainModel() != 0 && Chris@0: m_paneStack != 0 && Chris@0: m_paneStack->getCurrentPane() != 0); Chris@0: Chris@0: if (!canImportLayer || !openLayerFile(path)) { Chris@0: Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("File \"%1\" could not be opened").arg(path)); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: bool Chris@0: MainWindow::openSomeFile(QString path) Chris@0: { Chris@0: if (openAudioFile(path)) { Chris@0: return true; Chris@0: } else if (openSessionFile(path)) { Chris@0: return true; Chris@0: } else { Chris@0: return false; Chris@0: } Chris@0: } Chris@0: Chris@0: bool Chris@0: MainWindow::openSessionFile(QString path) Chris@0: { Chris@0: BZipFileDevice bzFile(path); Chris@0: if (!bzFile.open(QIODevice::ReadOnly)) { Chris@0: std::cerr << "Failed to open session file \"" << path.toStdString() Chris@0: << "\": " << bzFile.errorString().toStdString() << std::endl; Chris@0: return false; Chris@0: } Chris@0: Chris@0: QString error; Chris@0: closeSession(); Chris@0: createDocument(); Chris@0: Chris@0: PaneCallback callback(this); Chris@0: m_viewManager->clearSelections(); Chris@0: Chris@0: SVFileReader reader(m_document, callback); Chris@0: QXmlInputSource inputSource(&bzFile); Chris@0: reader.parse(inputSource); Chris@0: Chris@0: if (!reader.isOK()) { Chris@0: error = tr("SV XML file read error:\n%1").arg(reader.getErrorString()); Chris@0: } Chris@0: Chris@0: bzFile.close(); Chris@0: Chris@0: bool ok = (error == ""); Chris@0: Chris@0: if (ok) { Chris@0: setWindowTitle(tr("Sonic Visualiser: %1") Chris@0: .arg(QFileInfo(path).fileName())); Chris@0: m_sessionFile = path; Chris@0: setupMenus(); Chris@0: CommandHistory::getInstance()->clear(); Chris@0: CommandHistory::getInstance()->documentSaved(); Chris@0: m_documentModified = false; Chris@0: updateMenuStates(); Chris@34: m_recentFiles.addFile(path); Chris@0: } else { Chris@0: setWindowTitle(tr("Sonic Visualiser")); Chris@0: } Chris@0: Chris@0: return ok; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::closeEvent(QCloseEvent *e) Chris@0: { Chris@0: if (!checkSaveModified()) { Chris@0: e->ignore(); Chris@0: return; Chris@0: } Chris@0: Chris@5: QSettings settings; Chris@5: settings.beginGroup("MainWindow"); Chris@5: settings.setValue("size", size()); Chris@5: settings.setValue("position", pos()); Chris@5: settings.endGroup(); Chris@5: Chris@0: e->accept(); Chris@0: return; Chris@0: } Chris@0: Chris@0: bool Chris@11: MainWindow::commitData(bool mayAskUser) Chris@11: { Chris@11: if (mayAskUser) { Chris@11: return checkSaveModified(); Chris@11: } else { Chris@11: if (!m_documentModified) return true; Chris@11: Chris@11: // If we can't check with the user first, then we can't save Chris@11: // to the original session file (even if we have it) -- have Chris@11: // to use a temporary file Chris@11: Chris@11: QString svDirBase = ".sv1"; Chris@11: QString svDir = QDir::home().filePath(svDirBase); Chris@11: Chris@11: if (!QFileInfo(svDir).exists()) { Chris@11: if (!QDir::home().mkdir(svDirBase)) return false; Chris@11: } else { Chris@11: if (!QFileInfo(svDir).isDir()) return false; Chris@11: } Chris@11: Chris@11: // This name doesn't have to be unguessable Chris@11: Chris@11: QString fname = QString("tmp-%1-%2.sv") Chris@11: .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")) Chris@11: .arg(QProcess().pid()); Chris@11: QString fpath = QDir(svDir).filePath(fname); Chris@11: if (saveSessionFile(fpath)) { Chris@34: m_recentFiles.addFile(fpath); Chris@11: return true; Chris@11: } else { Chris@11: return false; Chris@11: } Chris@11: } Chris@11: } Chris@11: Chris@11: bool Chris@0: MainWindow::checkSaveModified() Chris@0: { Chris@0: // Called before some destructive operation (e.g. new session, Chris@0: // exit program). Return true if we can safely proceed, false to Chris@0: // cancel. Chris@0: Chris@0: if (!m_documentModified) return true; Chris@0: Chris@0: int button = Chris@0: QMessageBox::warning(this, Chris@0: tr("Session modified"), Chris@0: tr("The current session has been modified.\nDo you want to save it?"), Chris@0: QMessageBox::Yes, Chris@0: QMessageBox::No, Chris@0: QMessageBox::Cancel); Chris@0: Chris@0: if (button == QMessageBox::Yes) { Chris@0: saveSession(); Chris@0: if (m_documentModified) { // save failed -- don't proceed! Chris@0: return false; Chris@0: } else { Chris@0: return true; // saved, so it's safe to continue now Chris@0: } Chris@0: } else if (button == QMessageBox::No) { Chris@0: m_documentModified = false; // so we know to abandon it Chris@0: return true; Chris@0: } Chris@0: Chris@0: // else cancel Chris@0: return false; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::saveSession() Chris@0: { Chris@0: if (m_sessionFile != "") { Chris@0: if (!saveSessionFile(m_sessionFile)) { Chris@0: QMessageBox::critical(this, tr("Failed to save file"), Chris@0: tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); Chris@0: } else { Chris@0: CommandHistory::getInstance()->documentSaved(); Chris@0: documentRestored(); Chris@0: } Chris@0: } else { Chris@0: saveSessionAs(); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::saveSessionAs() Chris@0: { Chris@0: QString orig = m_audioFile; Chris@0: if (orig == "") orig = "."; Chris@0: else orig = QFileInfo(orig).absoluteDir().canonicalPath(); Chris@0: Chris@0: QString path; Chris@0: bool good = false; Chris@0: Chris@0: while (!good) { Chris@0: Chris@0: path = QFileDialog::getSaveFileName Chris@0: (this, tr("Select a file to save to"), orig, Chris@0: tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"), 0, Chris@0: QFileDialog::DontConfirmOverwrite); // we'll do that Chris@0: Chris@0: if (path.isEmpty()) return; Chris@0: Chris@0: if (!path.endsWith(".sv")) path = path + ".sv"; Chris@0: Chris@0: QFileInfo fi(path); Chris@0: Chris@0: if (fi.isDir()) { Chris@0: QMessageBox::critical(this, tr("Directory selected"), Chris@0: tr("File \"%1\" is a directory").arg(path)); Chris@0: continue; Chris@0: } Chris@0: Chris@0: if (fi.exists()) { Chris@0: if (QMessageBox::question(this, tr("File exists"), Chris@0: tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), Chris@0: QMessageBox::Ok, Chris@0: QMessageBox::Cancel) == QMessageBox::Ok) { Chris@0: good = true; Chris@0: } else { Chris@0: continue; Chris@0: } Chris@0: } Chris@0: Chris@0: good = true; Chris@0: } Chris@0: Chris@0: if (!saveSessionFile(path)) { Chris@0: QMessageBox::critical(this, tr("Failed to save file"), Chris@0: tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); Chris@0: } else { Chris@0: setWindowTitle(tr("Sonic Visualiser: %1") Chris@0: .arg(QFileInfo(path).fileName())); Chris@0: m_sessionFile = path; Chris@0: CommandHistory::getInstance()->documentSaved(); Chris@0: documentRestored(); Chris@34: m_recentFiles.addFile(path); Chris@0: } Chris@0: } Chris@0: Chris@0: bool Chris@0: MainWindow::saveSessionFile(QString path) Chris@0: { Chris@0: BZipFileDevice bzFile(path); Chris@0: if (!bzFile.open(QIODevice::WriteOnly)) { Chris@0: std::cerr << "Failed to open session file \"" << path.toStdString() Chris@0: << "\" for writing: " Chris@0: << bzFile.errorString().toStdString() << std::endl; Chris@0: return false; Chris@0: } Chris@0: Chris@0: QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); Chris@0: Chris@0: QTextStream out(&bzFile); Chris@0: toXml(out); Chris@0: out.flush(); Chris@0: Chris@0: QApplication::restoreOverrideCursor(); Chris@0: Chris@0: if (bzFile.errorString() != "") { Chris@0: QMessageBox::critical(this, tr("Failed to write file"), Chris@0: tr("Failed to write to file \"%1\": %2") Chris@0: .arg(path).arg(bzFile.errorString())); Chris@0: bzFile.close(); Chris@0: return false; Chris@0: } Chris@0: Chris@0: bzFile.close(); Chris@0: return true; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::toXml(QTextStream &out) Chris@0: { Chris@0: QString indent(" "); Chris@0: Chris@0: out << "\n"; Chris@0: out << "\n"; Chris@0: out << "\n"; Chris@0: Chris@0: m_document->toXml(out, "", ""); Chris@0: Chris@0: out << "\n"; Chris@0: Chris@0: out << QString(" \n") Chris@0: .arg(width()).arg(height()); Chris@0: Chris@0: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@0: Chris@0: Pane *pane = m_paneStack->getPane(i); Chris@0: Chris@0: if (pane) { Chris@0: pane->toXml(out, indent); Chris@0: } Chris@0: } Chris@0: Chris@0: out << "\n"; Chris@0: Chris@0: m_viewManager->getSelection().toXml(out); Chris@0: Chris@0: out << "\n"; Chris@0: } Chris@0: Chris@0: Pane * Chris@0: MainWindow::addPaneToStack() Chris@0: { Chris@0: AddPaneCommand *command = new AddPaneCommand(this); Chris@0: CommandHistory::getInstance()->addCommand(command); Chris@0: return command->getPane(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::zoomIn() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (currentPane) currentPane->zoom(true); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::zoomOut() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (currentPane) currentPane->zoom(false); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::zoomToFit() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (!currentPane) return; Chris@0: Chris@0: Model *model = getMainModel(); Chris@0: if (!model) return; Chris@0: Chris@0: size_t start = model->getStartFrame(); Chris@0: size_t end = model->getEndFrame(); Chris@0: size_t pixels = currentPane->width(); Chris@0: size_t zoomLevel = (end - start) / pixels; Chris@0: Chris@0: currentPane->setZoomLevel(zoomLevel); Chris@0: currentPane->setStartFrame(start); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::zoomDefault() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (currentPane) currentPane->setZoomLevel(1024); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::scrollLeft() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (currentPane) currentPane->scroll(false, false); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::jumpLeft() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (currentPane) currentPane->scroll(false, true); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::scrollRight() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (currentPane) currentPane->scroll(true, false); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::jumpRight() Chris@0: { Chris@0: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@0: if (currentPane) currentPane->scroll(true, true); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::showNoOverlays() Chris@0: { Chris@0: m_viewManager->setOverlayMode(ViewManager::NoOverlays); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::showBasicOverlays() Chris@0: { Chris@0: m_viewManager->setOverlayMode(ViewManager::BasicOverlays); Chris@0: } Chris@0: Chris@0: void Chris@7: MainWindow::showAllTextOverlays() Chris@0: { Chris@0: m_viewManager->setOverlayMode(ViewManager::AllOverlays); Chris@0: } Chris@0: Chris@0: void Chris@7: MainWindow::toggleZoomWheels() Chris@7: { Chris@7: if (m_viewManager->getZoomWheelsEnabled()) { Chris@7: m_viewManager->setZoomWheelsEnabled(false); Chris@7: } else { Chris@7: m_viewManager->setZoomWheelsEnabled(true); Chris@7: } Chris@7: } Chris@7: Chris@7: void Chris@0: MainWindow::play() Chris@0: { Chris@0: if (m_playSource->isPlaying()) { Chris@0: m_playSource->stop(); Chris@0: } else { Chris@0: m_playSource->play(m_viewManager->getPlaybackFrame()); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::ffwd() Chris@0: { Chris@0: if (!getMainModel()) return; Chris@0: Chris@0: int frame = m_viewManager->getPlaybackFrame(); Chris@0: ++frame; Chris@0: Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: if (!pane) return; Chris@0: Chris@0: Layer *layer = pane->getSelectedLayer(); Chris@0: Chris@0: if (!dynamic_cast(layer) && Chris@0: !dynamic_cast(layer)) return; Chris@0: Chris@0: size_t resolution = 0; Chris@0: if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapRight)) { Chris@0: frame = getMainModel()->getEndFrame(); Chris@0: } Chris@0: Chris@0: m_viewManager->setPlaybackFrame(frame); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::ffwdEnd() Chris@0: { Chris@0: if (!getMainModel()) return; Chris@0: m_viewManager->setPlaybackFrame(getMainModel()->getEndFrame()); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::rewind() Chris@0: { Chris@0: if (!getMainModel()) return; Chris@0: Chris@0: int frame = m_viewManager->getPlaybackFrame(); Chris@0: if (frame > 0) --frame; Chris@0: Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: if (!pane) return; Chris@0: Chris@0: Layer *layer = pane->getSelectedLayer(); Chris@0: Chris@0: if (!dynamic_cast(layer) && Chris@0: !dynamic_cast(layer)) return; Chris@0: Chris@0: size_t resolution = 0; Chris@0: if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapLeft)) { Chris@0: frame = getMainModel()->getEndFrame(); Chris@0: } Chris@0: Chris@0: m_viewManager->setPlaybackFrame(frame); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::rewindStart() Chris@0: { Chris@0: if (!getMainModel()) return; Chris@0: m_viewManager->setPlaybackFrame(getMainModel()->getStartFrame()); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::stop() Chris@0: { Chris@0: m_playSource->stop(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::addPane() Chris@0: { Chris@0: QObject *s = sender(); Chris@0: QAction *action = dynamic_cast(s); Chris@0: Chris@0: if (!action) { Chris@0: std::cerr << "WARNING: MainWindow::addPane: sender is not an action" Chris@0: << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: PaneActionMap::iterator i = m_paneActions.find(action); Chris@0: Chris@0: if (i == m_paneActions.end()) { Chris@0: std::cerr << "WARNING: MainWindow::addPane: unknown action " Chris@0: << action->objectName().toStdString() << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: CommandHistory::getInstance()->startCompoundOperation Chris@0: (action->text(), true); Chris@0: Chris@0: AddPaneCommand *command = new AddPaneCommand(this); Chris@0: CommandHistory::getInstance()->addCommand(command); Chris@0: Chris@0: Pane *pane = command->getPane(); Chris@0: Chris@7: if (i->second.layer == LayerFactory::Spectrum) { Chris@7: pane->setPlaybackFollow(View::PlaybackScrollContinuous); Chris@7: } Chris@7: Chris@8: if (i->second.layer != LayerFactory::TimeRuler && Chris@8: i->second.layer != LayerFactory::Spectrum) { Chris@8: Chris@0: if (!m_timeRulerLayer) { Chris@0: // std::cerr << "no time ruler layer, creating one" << std::endl; Chris@0: m_timeRulerLayer = m_document->createMainModelLayer Chris@0: (LayerFactory::TimeRuler); Chris@0: } Chris@0: Chris@0: // std::cerr << "adding time ruler layer " << m_timeRulerLayer << std::endl; Chris@0: Chris@0: m_document->addLayerToView(pane, m_timeRulerLayer); Chris@0: } Chris@0: Chris@0: Layer *newLayer = m_document->createLayer(i->second.layer); Chris@0: m_document->setModel(newLayer, m_document->getMainModel()); Chris@0: m_document->setChannel(newLayer, i->second.channel); Chris@0: m_document->addLayerToView(pane, newLayer); Chris@0: Chris@0: m_paneStack->setCurrentPane(pane); Chris@0: Chris@0: CommandHistory::getInstance()->endCompoundOperation(); Chris@0: Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: MainWindow::AddPaneCommand::AddPaneCommand(MainWindow *mw) : Chris@0: m_mw(mw), Chris@0: m_pane(0), Chris@0: m_prevCurrentPane(0), Chris@0: m_added(false) Chris@0: { Chris@0: } Chris@0: Chris@0: MainWindow::AddPaneCommand::~AddPaneCommand() Chris@0: { Chris@0: if (m_pane && !m_added) { Chris@0: m_mw->m_paneStack->deletePane(m_pane); Chris@0: } Chris@0: } Chris@0: Chris@0: QString Chris@0: MainWindow::AddPaneCommand::getName() const Chris@0: { Chris@0: return tr("Add Pane"); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::AddPaneCommand::execute() Chris@0: { Chris@0: if (!m_pane) { Chris@0: m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); Chris@0: m_pane = m_mw->m_paneStack->addPane(); Chris@0: } else { Chris@0: m_mw->m_paneStack->showPane(m_pane); Chris@0: } Chris@0: Chris@0: m_mw->m_paneStack->setCurrentPane(m_pane); Chris@0: m_mw->m_panner->registerView(m_pane); Chris@0: m_added = true; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::AddPaneCommand::unexecute() Chris@0: { Chris@0: m_mw->m_paneStack->hidePane(m_pane); Chris@0: m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); Chris@0: m_mw->m_panner->unregisterView(m_pane); Chris@0: m_added = false; Chris@0: } Chris@0: Chris@0: MainWindow::RemovePaneCommand::RemovePaneCommand(MainWindow *mw, Pane *pane) : Chris@0: m_mw(mw), Chris@0: m_pane(pane), Chris@0: m_added(true) Chris@0: { Chris@0: } Chris@0: Chris@0: MainWindow::RemovePaneCommand::~RemovePaneCommand() Chris@0: { Chris@0: if (m_pane && !m_added) { Chris@0: m_mw->m_paneStack->deletePane(m_pane); Chris@0: } Chris@0: } Chris@0: Chris@0: QString Chris@0: MainWindow::RemovePaneCommand::getName() const Chris@0: { Chris@0: return tr("Remove Pane"); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::RemovePaneCommand::execute() Chris@0: { Chris@0: m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); Chris@0: m_mw->m_paneStack->hidePane(m_pane); Chris@0: m_mw->m_panner->unregisterView(m_pane); Chris@0: m_added = false; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::RemovePaneCommand::unexecute() Chris@0: { Chris@0: m_mw->m_paneStack->showPane(m_pane); Chris@0: m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); Chris@0: m_mw->m_panner->registerView(m_pane); Chris@0: m_added = true; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::addLayer() Chris@0: { Chris@0: QObject *s = sender(); Chris@0: QAction *action = dynamic_cast(s); Chris@0: Chris@0: if (!action) { Chris@0: std::cerr << "WARNING: MainWindow::addLayer: sender is not an action" Chris@0: << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: Chris@0: if (!pane) { Chris@0: std::cerr << "WARNING: MainWindow::addLayer: no current pane" << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: ExistingLayerActionMap::iterator ei = m_existingLayerActions.find(action); Chris@0: Chris@0: if (ei != m_existingLayerActions.end()) { Chris@0: Layer *newLayer = ei->second; Chris@0: m_document->addLayerToView(pane, newLayer); Chris@0: m_paneStack->setCurrentLayer(pane, newLayer); Chris@0: return; Chris@0: } Chris@0: Chris@34: TransformActionMap::iterator i = m_transformActions.find(action); Chris@34: Chris@34: if (i == m_transformActions.end()) { Chris@0: Chris@0: LayerActionMap::iterator i = m_layerActions.find(action); Chris@0: Chris@0: if (i == m_layerActions.end()) { Chris@0: std::cerr << "WARNING: MainWindow::addLayer: unknown action " Chris@0: << action->objectName().toStdString() << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: LayerFactory::LayerType type = i->second; Chris@0: Chris@0: LayerFactory::LayerTypeSet emptyTypes = Chris@0: LayerFactory::getInstance()->getValidEmptyLayerTypes(); Chris@0: Chris@0: Layer *newLayer; Chris@0: Chris@0: if (emptyTypes.find(type) != emptyTypes.end()) { Chris@0: Chris@0: newLayer = m_document->createEmptyLayer(type); Chris@0: m_toolActions[ViewManager::DrawMode]->trigger(); Chris@0: Chris@0: } else { Chris@0: Chris@0: newLayer = m_document->createMainModelLayer(type); Chris@0: } Chris@0: Chris@0: m_document->addLayerToView(pane, newLayer); Chris@0: m_paneStack->setCurrentLayer(pane, newLayer); Chris@0: Chris@0: return; Chris@0: } Chris@0: Chris@0: TransformName transform = i->second; Chris@0: TransformFactory *factory = TransformFactory::getInstance(); Chris@0: Chris@0: QString configurationXml; Chris@0: Chris@0: int channel = -1; Chris@0: // pick up the default channel from any existing layers on the same pane Chris@0: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@0: int c = LayerFactory::getInstance()->getChannel(pane->getLayer(j)); Chris@0: if (c != -1) { Chris@0: channel = c; Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@33: // We always ask for configuration, even if the plugin isn't Chris@33: // supposed to be configurable, because we need to let the user Chris@33: // change the execution context (block size etc). Chris@0: Chris@27: PluginTransform::ExecutionContext context(channel); Chris@27: Chris@41: bool ok = factory->getConfigurationForTransform(transform, Chris@41: m_document->getMainModel(), Chris@41: context, Chris@41: configurationXml, Chris@41: m_playSource); Chris@33: if (!ok) return; Chris@0: Chris@0: Layer *newLayer = m_document->createDerivedLayer(transform, Chris@0: m_document->getMainModel(), Chris@27: context, Chris@0: configurationXml); Chris@0: Chris@0: if (newLayer) { Chris@0: m_document->addLayerToView(pane, newLayer); Chris@27: m_document->setChannel(newLayer, context.channel); Chris@34: m_recentTransforms.add(transform); Chris@0: } Chris@0: Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::deleteCurrentPane() Chris@0: { Chris@0: CommandHistory::getInstance()->startCompoundOperation Chris@0: (tr("Delete Pane"), true); Chris@0: Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: if (pane) { Chris@0: while (pane->getLayerCount() > 0) { Chris@0: Layer *layer = pane->getLayer(0); Chris@0: if (layer) { Chris@0: m_document->removeLayerFromView(pane, layer); Chris@0: } else { Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@0: RemovePaneCommand *command = new RemovePaneCommand(this, pane); Chris@0: CommandHistory::getInstance()->addCommand(command); Chris@0: } Chris@0: Chris@0: CommandHistory::getInstance()->endCompoundOperation(); Chris@0: Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::deleteCurrentLayer() Chris@0: { Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: if (pane) { Chris@0: Layer *layer = pane->getSelectedLayer(); Chris@0: if (layer) { Chris@0: m_document->removeLayerFromView(pane, layer); Chris@0: } Chris@0: } Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::renameCurrentLayer() Chris@0: { Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: if (pane) { Chris@0: Layer *layer = pane->getSelectedLayer(); Chris@0: if (layer) { Chris@0: bool ok = false; Chris@0: QString newName = QInputDialog::getText Chris@0: (this, tr("Rename Layer"), Chris@0: tr("New name for this layer:"), Chris@0: QLineEdit::Normal, layer->objectName(), &ok); Chris@0: if (ok) { Chris@0: layer->setObjectName(newName); Chris@0: setupExistingLayersMenu(); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::playSpeedChanged(int speed) Chris@0: { Chris@25: bool slow = false; Chris@25: bool something = false; Chris@25: float factor; Chris@25: Chris@25: if (speed < 100) { Chris@25: slow = true; Chris@25: speed = 100 - speed; Chris@25: } else { Chris@25: speed = speed - 100; Chris@25: } Chris@25: Chris@25: // speed is 0 -> 100 Chris@25: Chris@25: if (speed == 0) { Chris@25: factor = 1.0; Chris@25: } else { Chris@25: factor = speed; Chris@25: factor = 1.0 + (factor * factor) / 1000.f; Chris@25: something = true; Chris@25: } Chris@21: Chris@21: int pc = lrintf((factor - 1.0) * 100); Chris@21: Chris@25: if (!slow) factor = 1.0 / factor; Chris@25: Chris@25: std::cerr << "speed = " << speed << " factor = " << factor << std::endl; Chris@21: Chris@21: m_playSpeed->setToolTip(tr("Playback speed: %1%2%") Chris@25: .arg(!slow ? "+" : "-") Chris@21: .arg(pc)); Chris@21: Chris@25: m_playSharpen->setEnabled(something); Chris@26: m_playMono->setEnabled(something); Chris@25: bool sharpen = (something && m_playSharpen->isChecked()); Chris@26: bool mono = (something && m_playMono->isChecked()); Chris@26: m_playSource->setTimeStretch(factor, sharpen, mono); Chris@16: } Chris@16: Chris@16: void Chris@16: MainWindow::playSharpenToggled() Chris@16: { Chris@21: QSettings settings; Chris@21: settings.beginGroup("MainWindow"); Chris@21: settings.setValue("playsharpen", m_playSharpen->isChecked()); Chris@21: settings.endGroup(); Chris@21: Chris@16: playSpeedChanged(m_playSpeed->value()); Chris@0: } Chris@0: Chris@0: void Chris@26: MainWindow::playMonoToggled() Chris@26: { Chris@26: QSettings settings; Chris@26: settings.beginGroup("MainWindow"); Chris@26: settings.setValue("playmono", m_playMono->isChecked()); Chris@26: settings.endGroup(); Chris@26: Chris@26: playSpeedChanged(m_playSpeed->value()); Chris@26: } Chris@26: Chris@26: void Chris@0: MainWindow::outputLevelsChanged(float left, float right) Chris@0: { Chris@0: m_fader->setPeakLeft(left); Chris@0: m_fader->setPeakRight(right); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::sampleRateMismatch(size_t requested, size_t actual, Chris@0: bool willResample) Chris@0: { Chris@0: if (!willResample) { Chris@0: //!!! more helpful message needed Chris@0: QMessageBox::information Chris@0: (this, tr("Sample rate mismatch"), Chris@0: 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.") Chris@0: .arg(requested).arg(actual)); Chris@0: } Chris@0: Chris@0: /*!!! Let's not do this for now, and see how we go -- now that we're putting Chris@0: sample rate information in the status bar Chris@0: Chris@0: QMessageBox::information Chris@0: (this, tr("Sample rate mismatch"), Chris@0: 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.") Chris@0: .arg(requested).arg(actual)); Chris@0: */ Chris@0: Chris@0: updateDescriptionLabel(); Chris@0: } Chris@0: Chris@0: void Chris@42: MainWindow::audioOverloadPluginDisabled() Chris@42: { Chris@42: QMessageBox::information Chris@42: (this, tr("Audio processing overload"), Chris@42: tr("Audio effects plugin auditioning has been disabled\ndue to a processing overload.")); Chris@42: } Chris@42: Chris@42: void Chris@0: MainWindow::layerAdded(Layer *layer) Chris@0: { Chris@0: // std::cerr << "MainWindow::layerAdded(" << layer << ")" << std::endl; Chris@0: // setupExistingLayersMenu(); Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::layerRemoved(Layer *layer) Chris@0: { Chris@0: // std::cerr << "MainWindow::layerRemoved(" << layer << ")" << std::endl; Chris@0: setupExistingLayersMenu(); Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::layerAboutToBeDeleted(Layer *layer) Chris@0: { Chris@0: // std::cerr << "MainWindow::layerAboutToBeDeleted(" << layer << ")" << std::endl; Chris@0: if (layer == m_timeRulerLayer) { Chris@0: // std::cerr << "(this is the time ruler layer)" << std::endl; Chris@0: m_timeRulerLayer = 0; Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::layerInAView(Layer *layer, bool inAView) Chris@0: { Chris@0: // std::cerr << "MainWindow::layerInAView(" << layer << "," << inAView << ")" << std::endl; Chris@0: Chris@0: // Check whether we need to add or remove model from play source Chris@0: Model *model = layer->getModel(); Chris@0: if (model) { Chris@0: if (inAView) { Chris@0: m_playSource->addModel(model); Chris@0: } else { Chris@0: bool found = false; Chris@0: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@0: Pane *pane = m_paneStack->getPane(i); Chris@0: if (!pane) continue; Chris@0: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@0: Layer *pl = pane->getLayer(j); Chris@0: if (pl && pl->getModel() == model) { Chris@0: found = true; Chris@0: break; Chris@0: } Chris@0: } Chris@0: if (found) break; Chris@0: } Chris@0: if (!found) m_playSource->removeModel(model); Chris@0: } Chris@0: } Chris@0: Chris@0: setupExistingLayersMenu(); Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::modelAdded(Model *model) Chris@0: { Chris@0: // std::cerr << "MainWindow::modelAdded(" << model << ")" << std::endl; Chris@0: m_playSource->addModel(model); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::mainModelChanged(WaveFileModel *model) Chris@0: { Chris@0: // std::cerr << "MainWindow::mainModelChanged(" << model << ")" << std::endl; Chris@0: updateDescriptionLabel(); Chris@0: m_panLayer->setModel(model); Chris@0: if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate()); Chris@0: if (model && !m_playTarget) createPlayTarget(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::modelAboutToBeDeleted(Model *model) Chris@0: { Chris@0: // std::cerr << "MainWindow::modelAboutToBeDeleted(" << model << ")" << std::endl; Chris@0: m_playSource->removeModel(model); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::modelGenerationFailed(QString transformName) Chris@0: { Chris@0: QMessageBox::warning Chris@0: (this, Chris@0: tr("Failed to generate layer"), Chris@34: tr("Failed to generate a derived layer.\n\nThe layer transform \"%1\" failed.\n\nThis probably means that a plugin failed to initialise, perhaps because it\nrejected the processing block size that was requested.") Chris@0: .arg(transformName), Chris@0: QMessageBox::Ok, 0); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::modelRegenerationFailed(QString layerName, QString transformName) Chris@0: { Chris@0: QMessageBox::warning Chris@0: (this, Chris@0: tr("Failed to regenerate layer"), Chris@34: tr("Failed to regenerate derived layer \"%1\".\n\nThe layer transform \"%2\" failed to run.\n\nThis probably means the layer used a plugin that is not currently available.") Chris@0: .arg(layerName).arg(transformName), Chris@0: QMessageBox::Ok, 0); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position) Chris@0: { Chris@0: // std::cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << std::endl; Chris@0: m_paneStack->setCurrentPane(pane); Chris@0: m_rightButtonMenu->popup(position); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::showLayerTree() Chris@0: { Chris@0: QTreeView *view = new QTreeView(); Chris@0: LayerTreeModel *tree = new LayerTreeModel(m_paneStack); Chris@0: view->expand(tree->index(0, 0, QModelIndex())); Chris@0: view->setModel(tree); Chris@0: view->show(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::preferenceChanged(PropertyContainer::PropertyName name) Chris@0: { Chris@0: if (name == "Property Box Layout") { Chris@0: if (Preferences::getInstance()->getPropertyBoxLayout() == Chris@0: Preferences::VerticallyStacked) { Chris@0: m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); Chris@0: } else { Chris@0: m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::preferences() Chris@0: { Chris@0: if (!m_preferencesDialog.isNull()) { Chris@0: m_preferencesDialog->show(); Chris@0: m_preferencesDialog->raise(); Chris@0: return; Chris@0: } Chris@0: Chris@0: m_preferencesDialog = new PreferencesDialog(this); Chris@0: Chris@0: // DeleteOnClose is safe here, because m_preferencesDialog is a Chris@0: // QPointer that will be zeroed when the dialog is deleted. We Chris@0: // use it in preference to leaving the dialog lying around because Chris@0: // if you Cancel the dialog, it resets the preferences state Chris@0: // without resetting its own widgets, so its state will be Chris@0: // incorrect when next shown unless we construct it afresh Chris@0: m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose); Chris@0: Chris@0: m_preferencesDialog->show(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::website() Chris@0: { Chris@0: openHelpUrl(tr("http://www.sonicvisualiser.org/")); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::help() Chris@0: { Chris@0: openHelpUrl(tr("http://www.sonicvisualiser.org/doc/reference/en/")); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::openHelpUrl(QString url) Chris@0: { Chris@0: // This method mostly lifted from Qt Assistant source code Chris@0: Chris@0: QProcess *process = new QProcess(this); Chris@0: connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); Chris@0: Chris@0: QStringList args; Chris@0: Chris@0: #ifdef Q_OS_MAC Chris@0: args.append(url); Chris@0: process->start("open", args); Chris@0: #else Chris@0: #ifdef Q_OS_WIN32 Chris@0: Chris@0: QString pf(getenv("ProgramFiles")); Chris@0: QString command = pf + QString("\\Internet Explorer\\IEXPLORE.EXE"); Chris@0: Chris@0: args.append(url); Chris@0: process->start(command, args); Chris@0: Chris@0: #else Chris@0: #ifdef Q_WS_X11 Chris@0: if (!qgetenv("KDE_FULL_SESSION").isEmpty()) { Chris@0: args.append("exec"); Chris@0: args.append(url); Chris@0: process->start("kfmclient", args); Chris@0: } else if (!qgetenv("BROWSER").isEmpty()) { Chris@0: args.append(url); Chris@0: process->start(qgetenv("BROWSER"), args); Chris@0: } else { Chris@0: args.append(url); Chris@0: process->start("firefox", args); Chris@0: } Chris@0: #endif Chris@0: #endif Chris@0: #endif Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::about() Chris@0: { Chris@0: bool debug = false; Chris@0: QString version = "(unknown version)"; Chris@0: Chris@0: #ifdef BUILD_DEBUG Chris@0: debug = true; Chris@0: #endif Chris@0: #ifdef SV_VERSION Chris@0: #ifdef SVNREV Chris@0: version = tr("Release %1 : Revision %2").arg(SV_VERSION).arg(SVNREV); Chris@0: #else Chris@0: version = tr("Release %1").arg(SV_VERSION); Chris@0: #endif Chris@0: #else Chris@0: #ifdef SVNREV Chris@0: version = tr("Unreleased : Revision %1").arg(SVNREV); Chris@0: #endif Chris@0: #endif Chris@0: Chris@0: QString aboutText; Chris@0: Chris@0: aboutText += tr("

About Sonic Visualiser

"); Chris@0: aboutText += tr("

Sonic Visualiser is a program for viewing and exploring audio data for semantic music analysis and annotation.

"); Chris@0: aboutText += tr("

%1 : %2 build

") Chris@0: .arg(version) Chris@0: .arg(debug ? tr("Debug") : tr("Release")); Chris@0: Chris@0: #ifdef BUILD_STATIC Chris@0: aboutText += tr("

Statically linked"); Chris@0: #ifndef QT_SHARED Chris@0: aboutText += tr("
With Qt (v%1) © Trolltech AS").arg(QT_VERSION_STR); Chris@0: #endif Chris@0: #ifdef HAVE_JACK Chris@0: aboutText += tr("
With JACK audio output (v%1) © Paul Davis and Jack O'Quin").arg(JACK_VERSION); Chris@0: #endif Chris@0: #ifdef HAVE_PORTAUDIO Chris@0: aboutText += tr("
With PortAudio audio output © Ross Bencina and Phil Burk"); Chris@0: #endif Chris@0: #ifdef HAVE_OGGZ Chris@0: aboutText += tr("
With Ogg file decoder (oggz v%1, fishsound v%2) © CSIRO Australia").arg(OGGZ_VERSION).arg(FISHSOUND_VERSION); Chris@0: #endif Chris@0: #ifdef HAVE_MAD Chris@0: aboutText += tr("
With MAD mp3 decoder (v%1) © Underbit Technologies Inc").arg(MAD_VERSION); Chris@0: #endif Chris@0: #ifdef HAVE_SAMPLERATE Chris@0: aboutText += tr("
With libsamplerate (v%1) © Erik de Castro Lopo").arg(SAMPLERATE_VERSION); Chris@0: #endif Chris@0: #ifdef HAVE_SNDFILE Chris@0: aboutText += tr("
With libsndfile (v%1) © Erik de Castro Lopo").arg(SNDFILE_VERSION); Chris@0: #endif Chris@0: #ifdef HAVE_FFTW3 Chris@0: aboutText += tr("
With FFTW3 (v%1) © Matteo Frigo and MIT").arg(FFTW3_VERSION); Chris@0: #endif Chris@0: #ifdef HAVE_VAMP Chris@0: aboutText += tr("
With Vamp plugin support (API v%1, SDK v%2) © Chris Cannam").arg(VAMP_API_VERSION).arg(VAMP_SDK_VERSION); Chris@0: #endif Chris@0: aboutText += tr("
With LADSPA plugin support (API v%1) © Richard Furse, Paul Davis, Stefan Westerfeld").arg(LADSPA_VERSION); Chris@0: aboutText += tr("
With DSSI plugin support (API v%1) © Chris Cannam, Steve Harris, Sean Bolton").arg(DSSI_VERSION); Chris@0: aboutText += "

"; Chris@0: #endif Chris@0: Chris@0: aboutText += Chris@0: "

Sonic Visualiser Copyright © 2005 - 2006 Chris Cannam
" Chris@0: "Centre for Digital Music, Queen Mary, University of London.

" Chris@0: "

This program is free software; you can redistribute it and/or
" Chris@0: "modify it under the terms of the GNU General Public License as
" Chris@0: "published by the Free Software Foundation; either version 2 of the
" Chris@0: "License, or (at your option) any later version.
See the file " Chris@0: "COPYING included with this distribution for more information.

"; Chris@0: Chris@0: QMessageBox::about(this, tr("About Sonic Visualiser"), aboutText); Chris@0: } Chris@0: Chris@0: Chris@0: #ifdef INCLUDE_MOCFILES Chris@0: #include "MainWindow.moc.cpp" Chris@0: #endif