Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@0: Tony Chris@0: An intonation analysis and annotation tool Chris@0: Centre for Digital Music, Queen Mary, University of London. Chris@0: This file copyright 2006-2012 Chris Cannam and QMUL. 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@0: #include "../version.h" Chris@0: Chris@0: #include "MainWindow.h" Chris@95: #include "NetworkPermissionTester.h" Chris@6: #include "Analyser.h" Chris@6: Chris@0: #include "framework/Document.h" Chris@95: #include "framework/VersionTester.h" Chris@0: Chris@0: #include "view/Pane.h" Chris@0: #include "view/PaneStack.h" Chris@0: #include "data/model/WaveFileModel.h" matthiasm@26: #include "data/model/NoteModel.h" matthiasm@42: #include "layer/FlexiNoteLayer.h" Chris@0: #include "view/ViewManager.h" Chris@0: #include "base/Preferences.h" Chris@539: #include "base/RecordDirectory.h" Chris@404: #include "base/AudioLevel.h" Chris@0: #include "layer/WaveformLayer.h" Chris@0: #include "layer/TimeInstantLayer.h" Chris@0: #include "layer/TimeValueLayer.h" Chris@192: #include "layer/SpectrogramLayer.h" Chris@0: #include "widgets/Fader.h" Chris@0: #include "view/Overview.h" Chris@0: #include "widgets/AudioDial.h" Chris@0: #include "widgets/IconLoader.h" Chris@0: #include "widgets/KeyReference.h" Chris@414: #include "widgets/LevelPanToolButton.h" Chris@474: #include "audio/AudioCallbackPlaySource.h" Chris@521: #include "audio/AudioCallbackRecordTarget.h" Chris@474: #include "audio/PlaySpeedRangeMapper.h" Chris@0: #include "base/Profiler.h" Chris@0: #include "base/UnitDatabase.h" Chris@0: #include "layer/ColourDatabase.h" Chris@139: #include "base/Selection.h" Chris@0: Chris@174: #include "rdf/RDFImporter.h" Chris@174: #include "data/fileio/DataFileReaderFactory.h" Chris@174: #include "data/fileio/CSVFormat.h" matthiasm@26: #include "data/fileio/CSVFileWriter.h" matthiasm@26: #include "data/fileio/MIDIFileWriter.h" matthiasm@26: #include "rdf/RDFExporter.h" matthiasm@26: Chris@227: #include "widgets/RangeInputDialog.h" Chris@244: #include "widgets/ActivityLog.h" Chris@227: 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@474: #include Chris@479: #include Chris@474: 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@2: #include Chris@0: #include Chris@0: #include Chris@128: #include Chris@413: #include Chris@585: #include Chris@585: #include Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: Chris@0: using std::vector; Chris@0: Chris@0: Chris@635: MainWindow::MainWindow(AudioMode audioMode, Chris@635: bool withSonification, Chris@635: bool withSpectrogram) : Chris@635: MainWindowBase(audioMode, Chris@635: MainWindowBase::MIDI_NONE, Chris@604: int(PaneStack::Option::NoPropertyStacks) | Chris@604: int(PaneStack::Option::NoPaneAccessories)), Chris@0: m_overview(0), Chris@0: m_mainMenusCreated(false), Chris@0: m_playbackMenu(0), gyorgyf@45: m_recentFilesMenu(0), Chris@0: m_rightButtonMenu(0), Chris@0: m_rightButtonPlaybackMenu(0), Chris@0: m_deleteSelectedAction(0), Chris@0: m_ffwdAction(0), Chris@0: m_rwdAction(0), Chris@168: m_intelligentActionOn(true), //GF: !!! temporary Chris@244: m_activityLog(new ActivityLog()), matthiasm@296: m_keyReference(new KeyReference()), matthiasm@364: m_selectionAnchor(0), matthiasm@364: m_withSonification(withSonification), matthiasm@364: m_withSpectrogram(withSpectrogram) Chris@0: { Chris@0: setWindowTitle(QApplication::applicationName()); Chris@0: Chris@206: #ifdef Q_OS_MAC Chris@206: #if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) Chris@206: setUnifiedTitleAndToolBarOnMac(true); Chris@206: #endif Chris@206: #endif Chris@206: Chris@0: UnitDatabase *udb = UnitDatabase::getInstance(); Chris@0: udb->registerUnit("Hz"); Chris@0: udb->registerUnit("dB"); Chris@0: udb->registerUnit("s"); Chris@0: Chris@0: ColourDatabase *cdb = ColourDatabase::getInstance(); Chris@0: cdb->addColour(Qt::black, tr("Black")); Chris@0: cdb->addColour(Qt::darkRed, tr("Red")); Chris@0: cdb->addColour(Qt::darkBlue, tr("Blue")); Chris@0: cdb->addColour(Qt::darkGreen, tr("Green")); Chris@0: cdb->addColour(QColor(200, 50, 255), tr("Purple")); Chris@0: cdb->addColour(QColor(255, 150, 50), tr("Orange")); Chris@120: cdb->addColour(QColor(180, 180, 180), tr("Grey")); Chris@0: cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true); Chris@0: cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true); Chris@0: cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true); Chris@0: cdb->setUseDarkBackground(cdb->addColour(Qt::green, tr("Bright Green")), true); Chris@0: cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true); Chris@0: cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true); Chris@0: Chris@0: Preferences::getInstance()->setResampleOnLoad(true); Chris@315: Preferences::getInstance()->setFixedSampleRate(44100); Chris@0: Preferences::getInstance()->setSpectrogramSmoothing Chris@0: (Preferences::SpectrogramInterpolated); Chris@332: Preferences::getInstance()->setNormaliseAudio(true); Chris@0: Chris@0: QSettings settings; Chris@0: Chris@0: settings.beginGroup("MainWindow"); Chris@0: settings.setValue("showstatusbar", false); Chris@0: settings.endGroup(); Chris@0: Chris@96: settings.beginGroup("Transformer"); Chris@96: settings.setValue("use-flexi-note-model", true); Chris@96: settings.endGroup(); Chris@96: Chris@73: settings.beginGroup("LayerDefaults"); Chris@73: settings.setValue("waveform", Chris@73: QString("") Chris@73: .arg(int(WaveformLayer::LinearScale)) Chris@73: .arg(int(WaveformLayer::MixChannels))); Chris@73: settings.endGroup(); Chris@73: Chris@6: m_viewManager->setAlignMode(false); Chris@6: m_viewManager->setPlaySoloMode(false); Chris@392: m_viewManager->setToolMode(ViewManager::NavigateMode); Chris@0: m_viewManager->setZoomWheelsEnabled(false); Chris@6: m_viewManager->setIlluminateLocalFeatures(true); matthiasm@368: m_viewManager->setShowWorkTitle(false); Chris@6: m_viewManager->setShowCentreLine(false); matthiasm@368: m_viewManager->setShowDuration(false); Chris@213: m_viewManager->setOverlayMode(ViewManager::GlobalOverlays); Chris@0: Chris@192: connect(m_viewManager, SIGNAL(selectionChangedByUser()), Chris@192: this, SLOT(selectionChangedByUser())); Chris@164: Chris@0: QFrame *frame = new QFrame; Chris@0: setCentralWidget(frame); Chris@0: Chris@0: QGridLayout *layout = new QGridLayout; Chris@0: Chris@0: QScrollArea *scroll = new QScrollArea(frame); Chris@0: scroll->setWidgetResizable(true); Chris@0: scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); Chris@0: scroll->setFrameShape(QFrame::NoFrame); Chris@0: Chris@6: // We have a pane stack: it comes with the territory. However, we Chris@120: // have a fixed and known number of panes in it -- it isn't Chris@120: // variable Chris@399: connect(m_paneStack, SIGNAL(doubleClickSelectInvoked(sv_frame_t)), Chris@399: this, SLOT(doubleClickSelectInvoked(sv_frame_t))); Chris@0: scroll->setWidget(m_paneStack); Chris@6: Chris@0: m_overview = new Overview(frame); Chris@362: m_overview->setPlaybackFollow(PlaybackScrollPage); Chris@0: m_overview->setViewManager(m_viewManager); Chris@393: m_overview->setFixedHeight(60); Chris@0: #ifndef _WIN32 Chris@0: // For some reason, the contents of the overview never appear if we Chris@0: // make this setting on Windows. I have no inclination at the moment Chris@0: // to track down the reason why. Chris@0: m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); Chris@0: #endif Chris@0: connect(m_overview, SIGNAL(contextHelpChanged(const QString &)), Chris@0: this, SLOT(contextHelpChanged(const QString &))); Chris@0: Chris@0: m_panLayer = new WaveformLayer; Chris@0: m_panLayer->setChannelMode(WaveformLayer::MergeChannels); Chris@0: m_panLayer->setAggressiveCacheing(true); Chris@393: m_panLayer->setGain(0.5); Chris@0: m_overview->addLayer(m_panLayer); Chris@0: Chris@0: if (m_viewManager->getGlobalDarkBackground()) { Chris@0: m_panLayer->setBaseColour Chris@0: (ColourDatabase::getInstance()->getColourIndex(tr("Bright Green"))); Chris@0: } else { Chris@0: m_panLayer->setBaseColour matthiasm@13: (ColourDatabase::getInstance()->getColourIndex(tr("Blue"))); Chris@0: } Chris@0: Chris@0: m_fader = new Fader(frame, false); Chris@0: connect(m_fader, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); Chris@0: connect(m_fader, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); Chris@0: Chris@0: m_playSpeed = new AudioDial(frame); Chris@343: m_playSpeed->setMeterColor(Qt::darkBlue); Chris@0: m_playSpeed->setMinimum(0); Chris@477: m_playSpeed->setMaximum(120); Chris@502: m_playSpeed->setValue(60); Chris@0: m_playSpeed->setFixedWidth(24); Chris@0: m_playSpeed->setFixedHeight(24); Chris@0: m_playSpeed->setNotchesVisible(true); Chris@0: m_playSpeed->setPageStep(10); Chris@477: m_playSpeed->setObjectName(tr("Playback Speed")); Chris@477: m_playSpeed->setDefaultValue(60); Chris@474: m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper); Chris@0: m_playSpeed->setShowToolTip(true); Chris@0: connect(m_playSpeed, SIGNAL(valueChanged(int)), gyorgyf@27: this, SLOT(playSpeedChanged(int))); Chris@0: connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); Chris@0: connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); Chris@0: Chris@414: m_audioLPW = new LevelPanToolButton(frame); Chris@434: m_audioLPW->setIncludeMute(false); Chris@404: m_audioLPW->setObjectName(tr("Audio Track Level and Pan")); Chris@404: connect(m_audioLPW, SIGNAL(levelChanged(float)), this, SLOT(audioGainChanged(float))); Chris@404: connect(m_audioLPW, SIGNAL(panChanged(float)), this, SLOT(audioPanChanged(float))); Chris@404: Chris@404: if (m_withSonification) { Chris@404: Chris@414: m_pitchLPW = new LevelPanToolButton(frame); Chris@434: m_pitchLPW->setIncludeMute(false); Chris@404: m_pitchLPW->setObjectName(tr("Pitch Track Level and Pan")); Chris@404: connect(m_pitchLPW, SIGNAL(levelChanged(float)), this, SLOT(pitchGainChanged(float))); Chris@404: connect(m_pitchLPW, SIGNAL(panChanged(float)), this, SLOT(pitchPanChanged(float))); Chris@404: Chris@414: m_notesLPW = new LevelPanToolButton(frame); Chris@434: m_notesLPW->setIncludeMute(false); Chris@404: m_notesLPW->setObjectName(tr("Note Track Level and Pan")); Chris@404: connect(m_notesLPW, SIGNAL(levelChanged(float)), this, SLOT(notesGainChanged(float))); Chris@404: connect(m_notesLPW, SIGNAL(panChanged(float)), this, SLOT(notesPanChanged(float))); matthiasm@366: } justin@160: Chris@0: layout->setSpacing(4); Chris@6: layout->addWidget(m_overview, 0, 1); Chris@6: layout->addWidget(scroll, 1, 1); Chris@0: Chris@0: layout->setColumnStretch(1, 10); Chris@0: Chris@0: frame->setLayout(layout); Chris@0: gyorgyf@21: m_analyser = new Analyser(); Chris@128: connect(m_analyser, SIGNAL(layersChanged()), Chris@128: this, SLOT(updateLayerStatuses())); Chris@187: connect(m_analyser, SIGNAL(layersChanged()), Chris@187: this, SLOT(updateMenuStates())); gyorgyf@21: Chris@0: setupMenus(); Chris@0: setupToolbars(); Chris@0: setupHelpMenu(); Chris@0: Chris@0: statusBar(); Chris@0: Chris@288: finaliseMenus(); Chris@288: Chris@244: connect(m_viewManager, SIGNAL(activity(QString)), Chris@244: m_activityLog, SLOT(activityHappened(QString))); Chris@244: connect(m_playSource, SIGNAL(activity(QString)), Chris@244: m_activityLog, SLOT(activityHappened(QString))); Chris@244: connect(CommandHistory::getInstance(), SIGNAL(activity(QString)), Chris@244: m_activityLog, SLOT(activityHappened(QString))); Chris@244: connect(this, SIGNAL(activity(QString)), Chris@244: m_activityLog, SLOT(activityHappened(QString))); Chris@244: connect(this, SIGNAL(replacedDocument()), this, SLOT(documentReplaced())); Chris@260: connect(this, SIGNAL(sessionLoaded()), this, SLOT(analyseNewMainModel())); Chris@260: connect(this, SIGNAL(audioFileLoaded()), this, SLOT(analyseNewMainModel())); Chris@244: m_activityLog->hide(); Chris@244: Chris@485: setAudioRecordMode(RecordReplaceSession); Chris@485: Chris@95: newSession(); Chris@6: Chris@95: settings.beginGroup("MainWindow"); Chris@95: settings.setValue("zoom-default", 512); Chris@95: settings.endGroup(); Chris@95: zoomDefault(); Chris@95: Chris@95: NetworkPermissionTester tester; Chris@95: bool networkPermission = tester.havePermission(); Chris@95: if (networkPermission) { Chris@95: m_versionTester = new VersionTester Chris@95: ("sonicvisualiser.org", "latest-tony-version.txt", TONY_VERSION); Chris@95: connect(m_versionTester, SIGNAL(newerVersionAvailable(QString)), Chris@95: this, SLOT(newerVersionAvailable(QString))); Chris@95: } else { Chris@95: m_versionTester = 0; Chris@95: } Chris@0: } Chris@0: Chris@0: MainWindow::~MainWindow() Chris@0: { Chris@6: delete m_analyser; Chris@0: delete m_keyReference; Chris@0: Profiles::getInstance()->dump(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupMenus() Chris@0: { Chris@0: if (!m_mainMenusCreated) { Chris@385: Chris@385: #ifdef Q_OS_LINUX Chris@385: // In Ubuntu 14.04 the window's menu bar goes missing entirely Chris@385: // if the user is running any desktop environment other than Unity Chris@385: // (in which the faux single-menubar appears). The user has a Chris@385: // workaround, to remove the appmenu-qt5 package, but that is Chris@385: // awkward and the problem is so severe that it merits disabling Chris@385: // the system menubar integration altogether. Like this: Chris@385: menuBar()->setNativeMenuBar(false); Chris@385: #endif Chris@385: Chris@0: m_rightButtonMenu = new QMenu(); Chris@0: } Chris@0: Chris@0: if (!m_mainMenusCreated) { Chris@0: CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); Chris@0: m_rightButtonMenu->addSeparator(); Chris@0: } Chris@0: Chris@0: setupFileMenu(); Chris@0: setupEditMenu(); Chris@0: setupViewMenu(); matthiasm@317: setupAnalysisMenu(); Chris@0: Chris@0: m_mainMenusCreated = true; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupFileMenu() Chris@0: { Chris@0: if (m_mainMenusCreated) return; Chris@0: Chris@0: QMenu *menu = menuBar()->addMenu(tr("&File")); Chris@0: menu->setTearOffEnabled(true); Chris@0: QToolBar *toolbar = addToolBar(tr("File Toolbar")); Chris@0: Chris@0: m_keyReference->setCategory(tr("File and Session Management")); Chris@0: Chris@0: IconLoader il; Chris@1: QIcon icon; Chris@1: QAction *action; Chris@0: Chris@0: icon = il.load("fileopen"); Chris@1: action = new QAction(icon, tr("&Open..."), this); Chris@0: action->setShortcut(tr("Ctrl+O")); Chris@257: action->setStatusTip(tr("Open a session or audio file")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(openFile())); Chris@0: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); Chris@0: toolbar->addAction(action); Chris@0: Chris@1: action = new QAction(tr("Open Lo&cation..."), this); Chris@0: action->setShortcut(tr("Ctrl+Shift+O")); Chris@1: action->setStatusTip(tr("Open a file from a remote URL")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(openLocation())); Chris@0: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); Chris@0: Chris@1: m_recentFilesMenu = menu->addMenu(tr("Open &Recent")); Chris@0: m_recentFilesMenu->setTearOffEnabled(true); Chris@0: setupRecentFilesMenu(); Chris@0: connect(&m_recentFiles, SIGNAL(recentChanged()), Chris@0: this, SLOT(setupRecentFilesMenu())); Chris@0: Chris@0: menu->addSeparator(); Chris@257: Chris@257: icon = il.load("filesave"); Chris@257: action = new QAction(icon, tr("&Save Session"), this); Chris@257: action->setShortcut(tr("Ctrl+S")); Chris@257: action->setStatusTip(tr("Save the current session into a %1 session file").arg(QApplication::applicationName())); Chris@257: connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); Chris@257: connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); Chris@257: m_keyReference->registerShortcut(action); Chris@257: menu->addAction(action); Chris@257: toolbar->addAction(action); Chris@257: Chris@257: icon = il.load("filesaveas"); Chris@257: action = new QAction(icon, tr("Save Session &As..."), this); Chris@257: action->setShortcut(tr("Ctrl+Shift+S")); Chris@257: action->setStatusTip(tr("Save the current session into a new %1 session file").arg(QApplication::applicationName())); Chris@257: connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); Chris@313: connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); Chris@257: menu->addAction(action); Chris@257: toolbar->addAction(action); Chris@257: Chris@486: action = new QAction(tr("Save Session to Audio File &Path"), this); matthiasm@310: action->setShortcut(tr("Ctrl+Alt+S")); Chris@313: action->setStatusTip(tr("Save the current session into a %1 session file with the same filename as the audio but a .ton extension.").arg(QApplication::applicationName())); matthiasm@310: connect(action, SIGNAL(triggered()), this, SLOT(saveSessionInAudioPath())); Chris@313: connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); matthiasm@310: menu->addAction(action); matthiasm@310: Chris@257: menu->addSeparator(); Chris@257: Chris@253: action = new QAction(tr("I&mport Pitch Track Data..."), this); Chris@253: action->setStatusTip(tr("Import pitch-track data from a CSV, RDF, or layer XML file")); Chris@174: connect(action, SIGNAL(triggered()), this, SLOT(importPitchLayer())); Chris@174: connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); Chris@174: menu->addAction(action); Chris@174: Chris@253: action = new QAction(tr("E&xport Pitch Track Data..."), this); Chris@253: action->setStatusTip(tr("Export pitch-track data to a CSV, RDF, or layer XML file")); Chris@85: connect(action, SIGNAL(triggered()), this, SLOT(exportPitchLayer())); Chris@224: connect(this, SIGNAL(canExportPitchTrack(bool)), action, SLOT(setEnabled(bool))); Chris@85: menu->addAction(action); Chris@85: Chris@253: action = new QAction(tr("&Export Note Data..."), this); Chris@253: action->setStatusTip(tr("Export note data to a CSV, RDF, layer XML, or MIDI file")); Chris@85: connect(action, SIGNAL(triggered()), this, SLOT(exportNoteLayer())); Chris@224: connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); matthiasm@26: menu->addAction(action); matthiasm@26: matthiasm@26: menu->addSeparator(); Chris@485: Chris@485: action = new QAction(tr("Browse Recorded Audio"), this); Chris@485: action->setStatusTip(tr("Open the Recorded Audio folder in the system file browser")); Chris@485: connect(action, SIGNAL(triggered()), this, SLOT(browseRecordedAudio())); Chris@485: menu->addAction(action); Chris@485: Chris@485: menu->addSeparator(); Chris@485: Chris@0: action = new QAction(il.load("exit"), tr("&Quit"), this); Chris@0: action->setShortcut(tr("Ctrl+Q")); Chris@0: action->setStatusTip(tr("Exit %1").arg(QApplication::applicationName())); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(close())); Chris@0: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupEditMenu() Chris@0: { Chris@0: if (m_mainMenusCreated) return; Chris@0: Chris@0: QMenu *menu = menuBar()->addMenu(tr("&Edit")); Chris@0: menu->setTearOffEnabled(true); Chris@0: CommandHistory::getInstance()->registerMenu(menu); Chris@126: menu->addSeparator(); Chris@126: Chris@254: m_keyReference->setCategory Chris@254: (tr("Selection Strip Mouse Actions")); Chris@254: m_keyReference->registerShortcut Chris@254: (tr("Jump"), tr("Left"), Chris@254: tr("Click left button to move the playback position to a time")); Chris@254: m_keyReference->registerShortcut Chris@254: (tr("Select"), tr("Left"), Chris@254: tr("Click left button and drag to select a region of time")); Chris@254: m_keyReference->registerShortcut Chris@254: (tr("Select Note Duration"), tr("Double-Click Left"), Chris@254: tr("Double-click left button to select the region of time corresponding to a note")); Chris@254: Chris@126: QToolBar *toolbar = addToolBar(tr("Tools Toolbar")); Chris@126: Chris@126: CommandHistory::getInstance()->registerToolbar(toolbar); Chris@126: Chris@250: QActionGroup *group = new QActionGroup(this); Chris@250: Chris@250: IconLoader il; Chris@250: Chris@126: m_keyReference->setCategory(tr("Tool Selection")); Chris@126: QAction *action = toolbar->addAction(il.load("navigate"), Chris@126: tr("Navigate")); Chris@126: action->setCheckable(true); Chris@126: action->setChecked(true); Chris@126: action->setShortcut(tr("1")); Chris@126: action->setStatusTip(tr("Navigate")); Chris@126: connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); Chris@126: connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger())); Chris@126: group->addAction(action); Chris@126: menu->addAction(action); Chris@126: m_keyReference->registerShortcut(action); Chris@126: Chris@250: m_keyReference->setCategory Chris@250: (tr("Navigate Tool Mouse Actions")); Chris@250: m_keyReference->registerShortcut Chris@250: (tr("Navigate"), tr("Left"), Chris@250: tr("Click left button and drag to move around")); Chris@250: m_keyReference->registerShortcut Chris@250: (tr("Re-Analyse Area"), tr("Shift+Left"), Chris@250: tr("Shift-click left button and drag to define a specific pitch and time range to re-analyse")); Chris@250: m_keyReference->registerShortcut Chris@250: (tr("Edit"), tr("Double-Click Left"), Chris@250: tr("Double-click left button on an item to edit it")); Chris@392: Chris@250: m_keyReference->setCategory(tr("Tool Selection")); Chris@126: action = toolbar->addAction(il.load("move"), Chris@126: tr("Edit")); Chris@126: action->setCheckable(true); Chris@126: action->setShortcut(tr("2")); Chris@126: action->setStatusTip(tr("Edit with Note Intelligence")); Chris@126: connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); Chris@126: group->addAction(action); Chris@126: menu->addAction(action); Chris@126: m_keyReference->registerShortcut(action); Chris@126: Chris@250: m_keyReference->setCategory Chris@250: (tr("Note Edit Tool Mouse Actions")); Chris@250: m_keyReference->registerShortcut Chris@250: (tr("Adjust Pitch"), tr("Left"), Chris@250: tr("Click left button on the main part of a note and drag to move it up or down")); Chris@250: m_keyReference->registerShortcut Chris@250: (tr("Split"), tr("Left"), Chris@250: tr("Click left button on the bottom edge of a note to split it at the click point")); Chris@250: m_keyReference->registerShortcut Chris@250: (tr("Resize"), tr("Left"), Chris@250: tr("Click left button on the left or right edge of a note and drag to change the time or duration of the note")); Chris@250: m_keyReference->registerShortcut Chris@250: (tr("Erase"), tr("Shift+Left"), Chris@250: tr("Shift-click left button on a note to remove it")); Chris@250: Chris@250: Chris@126: /* Remove for now... Chris@126: Chris@250: m_keyReference->setCategory(tr("Tool Selection")); Chris@126: action = toolbar->addAction(il.load("notes"), Chris@126: tr("Free Edit")); Chris@126: action->setCheckable(true); Chris@126: action->setShortcut(tr("3")); Chris@126: action->setStatusTip(tr("Free Edit")); Chris@126: connect(action, SIGNAL(triggered()), this, SLOT(toolFreeEditSelected())); Chris@126: group->addAction(action); Chris@126: m_keyReference->registerShortcut(action); Chris@126: */ Chris@142: Chris@142: menu->addSeparator(); Chris@142: Chris@143: m_keyReference->setCategory(tr("Selection")); Chris@143: Chris@143: action = new QAction(tr("Select &All"), this); Chris@143: action->setShortcut(tr("Ctrl+A")); Chris@143: action->setStatusTip(tr("Select the whole duration of the current session")); Chris@143: connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); Chris@143: connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); Chris@143: m_keyReference->registerShortcut(action); Chris@143: menu->addAction(action); Chris@143: m_rightButtonMenu->addAction(action); Chris@143: Chris@287: action = new QAction(tr("C&lear Selection"), this); Chris@211: action->setShortcuts(QList() Chris@211: << QKeySequence(tr("Esc")) Chris@211: << QKeySequence(tr("Ctrl+Esc"))); Chris@211: action->setStatusTip(tr("Clear the selection and abandon any pending pitch choices in it")); Chris@194: connect(action, SIGNAL(triggered()), this, SLOT(abandonSelection())); Chris@143: connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); Chris@143: m_keyReference->registerShortcut(action); Chris@211: m_keyReference->registerAlternativeShortcut(action, QKeySequence(tr("Ctrl+Esc"))); Chris@143: menu->addAction(action); Chris@143: m_rightButtonMenu->addAction(action); Chris@143: Chris@143: menu->addSeparator(); Chris@189: m_rightButtonMenu->addSeparator(); Chris@143: Chris@189: m_keyReference->setCategory(tr("Pitch Track")); Chris@211: Chris@211: action = new QAction(tr("Choose Higher Pitch"), this); Chris@211: action->setShortcut(tr("Ctrl+Up")); Chris@211: action->setStatusTip(tr("Move pitches up an octave, or to the next higher pitch candidate")); Chris@211: m_keyReference->registerShortcut(action); Chris@211: connect(action, SIGNAL(triggered()), this, SLOT(switchPitchUp())); Chris@211: connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); Chris@211: menu->addAction(action); Chris@211: m_rightButtonMenu->addAction(action); Chris@211: Chris@211: action = new QAction(tr("Choose Lower Pitch"), this); Chris@211: action->setShortcut(tr("Ctrl+Down")); Chris@211: action->setStatusTip(tr("Move pitches down an octave, or to the next lower pitch candidate")); Chris@211: m_keyReference->registerShortcut(action); Chris@211: connect(action, SIGNAL(triggered()), this, SLOT(switchPitchDown())); Chris@211: connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); Chris@211: menu->addAction(action); Chris@211: m_rightButtonMenu->addAction(action); Chris@189: Chris@187: m_showCandidatesAction = new QAction(tr("Show Pitch Candidates"), this); Chris@187: m_showCandidatesAction->setShortcut(tr("Ctrl+Return")); Chris@189: m_showCandidatesAction->setStatusTip(tr("Toggle the display of alternative pitch candidates for the selected region")); Chris@189: m_keyReference->registerShortcut(m_showCandidatesAction); Chris@187: connect(m_showCandidatesAction, SIGNAL(triggered()), this, SLOT(togglePitchCandidates())); Chris@187: connect(this, SIGNAL(canClearSelection(bool)), m_showCandidatesAction, SLOT(setEnabled(bool))); Chris@187: menu->addAction(m_showCandidatesAction); Chris@189: m_rightButtonMenu->addAction(m_showCandidatesAction); Chris@187: Chris@287: action = new QAction(tr("Remove Pitches"), this); matthiasm@327: action->setShortcut(tr("Ctrl+Backspace")); Chris@211: action->setStatusTip(tr("Remove all pitch estimates within the selected region, making it unvoiced")); Chris@189: m_keyReference->registerShortcut(action); Chris@187: connect(action, SIGNAL(triggered()), this, SLOT(clearPitches())); Chris@167: connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); Chris@167: menu->addAction(action); Chris@189: m_rightButtonMenu->addAction(action); Chris@237: Chris@237: menu->addSeparator(); Chris@237: m_rightButtonMenu->addSeparator(); Chris@237: matthiasm@327: m_keyReference->setCategory(tr("Note Track")); matthiasm@327: Chris@287: action = new QAction(tr("Split Note"), this); matthiasm@327: action->setShortcut(tr("/")); Chris@240: action->setStatusTip(tr("Split the note at the current playback position into two")); Chris@237: m_keyReference->registerShortcut(action); Chris@240: connect(action, SIGNAL(triggered()), this, SLOT(splitNote())); Chris@240: connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); Chris@237: menu->addAction(action); Chris@237: m_rightButtonMenu->addAction(action); Chris@238: Chris@287: action = new QAction(tr("Merge Notes"), this); matthiasm@327: action->setShortcut(tr("\\")); Chris@238: action->setStatusTip(tr("Merge all notes within the selected region into a single note")); Chris@238: m_keyReference->registerShortcut(action); Chris@238: connect(action, SIGNAL(triggered()), this, SLOT(mergeNotes())); Chris@238: connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); Chris@238: menu->addAction(action); Chris@238: m_rightButtonMenu->addAction(action); matthiasm@292: matthiasm@292: action = new QAction(tr("Delete Notes"), this); matthiasm@327: action->setShortcut(tr("Backspace")); matthiasm@327: action->setStatusTip(tr("Delete all notes within the selected region")); matthiasm@292: m_keyReference->registerShortcut(action); matthiasm@292: connect(action, SIGNAL(triggered()), this, SLOT(deleteNotes())); matthiasm@292: connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); matthiasm@292: menu->addAction(action); matthiasm@292: m_rightButtonMenu->addAction(action); Chris@238: Chris@240: action = new QAction(tr("Form Note from Selection"), this); matthiasm@327: action->setShortcut(tr("=")); Chris@240: action->setStatusTip(tr("Form a note spanning the selected region, splitting any existing notes at its boundaries")); Chris@240: m_keyReference->registerShortcut(action); Chris@240: connect(action, SIGNAL(triggered()), this, SLOT(formNoteFromSelection())); Chris@240: connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); Chris@240: menu->addAction(action); Chris@240: m_rightButtonMenu->addAction(action); Chris@247: Chris@238: action = new QAction(tr("Snap Notes to Pitch Track"), this); Chris@238: action->setStatusTip(tr("Set notes within the selected region to the median frequency of their underlying pitches, or remove them if there are no underlying pitches")); matthiasm@327: // m_keyReference->registerShortcut(action); Chris@238: connect(action, SIGNAL(triggered()), this, SLOT(snapNotesToPitches())); Chris@238: connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); Chris@238: menu->addAction(action); Chris@238: m_rightButtonMenu->addAction(action); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupViewMenu() Chris@0: { Chris@0: if (m_mainMenusCreated) return; Chris@0: Chris@0: IconLoader il; Chris@0: Chris@0: QAction *action = 0; Chris@0: Chris@0: m_keyReference->setCategory(tr("Panning and Navigation")); Chris@0: Chris@0: QMenu *menu = menuBar()->addMenu(tr("&View")); Chris@0: menu->setTearOffEnabled(true); Chris@300: action = new QAction(tr("Peek &Left"), this); Chris@300: action->setShortcut(tr("Alt+Left")); Chris@300: action->setStatusTip(tr("Scroll the current pane to the left without changing the play position")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(scrollLeft())); Chris@0: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); gyorgyf@27: Chris@300: action = new QAction(tr("Peek &Right"), this); Chris@300: action->setShortcut(tr("Alt+Right")); Chris@300: action->setStatusTip(tr("Scroll the current pane to the right without changing the play position")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(scrollRight())); Chris@0: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@0: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); matthiasm@283: Chris@0: menu->addSeparator(); Chris@0: Chris@0: m_keyReference->setCategory(tr("Zoom")); Chris@0: Chris@0: action = new QAction(il.load("zoom-in"), 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: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); gyorgyf@27: Chris@0: action = new QAction(il.load("zoom-out"), 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: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); gyorgyf@27: Chris@0: action = new QAction(tr("Restore &Default Zoom"), this); Chris@0: action->setStatusTip(tr("Restore the zoom level to the default")); 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(il.load("zoom-fit"), Chris@0: tr("Zoom to &Fit"), this); Chris@0: action->setShortcut(tr("F")); 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: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); Chris@227: Chris@227: menu->addSeparator(); Chris@227: Chris@227: action = new QAction(tr("Set Displayed Fre&quency Range..."), this); Chris@227: action->setStatusTip(tr("Set the minimum and maximum frequencies in the visible display")); Chris@227: connect(action, SIGNAL(triggered()), this, SLOT(editDisplayExtents())); Chris@227: menu->addAction(action); Chris@0: } Chris@0: Chris@0: void matthiasm@317: MainWindow::setupAnalysisMenu() matthiasm@317: { matthiasm@317: if (m_mainMenusCreated) return; matthiasm@317: matthiasm@317: IconLoader il; matthiasm@317: matthiasm@317: QAction *action = 0; matthiasm@317: matthiasm@317: QMenu *menu = menuBar()->addMenu(tr("&Analysis")); matthiasm@317: menu->setTearOffEnabled(true); matthiasm@317: Chris@427: m_autoAnalyse = new QAction(tr("Auto-Analyse &New Audio"), this); Chris@427: m_autoAnalyse->setStatusTip(tr("Automatically trigger analysis upon opening of a new audio file.")); Chris@427: m_autoAnalyse->setCheckable(true); Chris@427: connect(m_autoAnalyse, SIGNAL(triggered()), this, SLOT(autoAnalysisToggled())); Chris@427: menu->addAction(m_autoAnalyse); Chris@427: Chris@427: action = new QAction(tr("&Analyse Now!"), this); Chris@427: action->setStatusTip(tr("Trigger analysis of pitches and notes. (This will delete all existing pitches and notes.)")); Chris@427: connect(action, SIGNAL(triggered()), this, SLOT(analyseNow())); Chris@427: menu->addAction(action); Chris@427: m_keyReference->registerShortcut(action); Chris@427: Chris@427: menu->addSeparator(); Chris@427: Chris@427: m_precise = new QAction(tr("&Unbiased Timing (slow)"), this); Chris@427: m_precise->setStatusTip(tr("Use a symmetric window in YIN to remove frequency-dependent timing bias. (This is slow!)")); Chris@427: m_precise->setCheckable(true); Chris@427: connect(m_precise, SIGNAL(triggered()), this, SLOT(precisionAnalysisToggled())); Chris@427: menu->addAction(m_precise); Chris@427: Chris@427: m_lowamp = new QAction(tr("&Penalise Soft Pitches"), this); Chris@427: m_lowamp->setStatusTip(tr("Reduce the likelihood of detecting a pitch when the signal has low amplitude.")); Chris@427: m_lowamp->setCheckable(true); Chris@427: connect(m_lowamp, SIGNAL(triggered()), this, SLOT(lowampAnalysisToggled())); Chris@427: menu->addAction(m_lowamp); Chris@427: Chris@427: m_onset = new QAction(tr("&High Onset Sensitivity"), this); Chris@427: m_onset->setStatusTip(tr("Increase likelihood of separating notes, especially consecutive notes at the same pitch.")); Chris@427: m_onset->setCheckable(true); Chris@427: connect(m_onset, SIGNAL(triggered()), this, SLOT(onsetAnalysisToggled())); Chris@427: menu->addAction(m_onset); Chris@427: Chris@427: m_prune = new QAction(tr("&Drop Short Notes"), this); Chris@427: m_prune->setStatusTip(tr("Duration-based pruning: automatic note estimator will not output notes of less than 100ms duration.")); Chris@427: m_prune->setCheckable(true); Chris@427: connect(m_prune, SIGNAL(triggered()), this, SLOT(pruneAnalysisToggled())); Chris@427: menu->addAction(m_prune); Chris@427: Chris@427: menu->addSeparator(); Chris@427: Chris@427: action = new QAction(tr("Reset Options to Defaults"), this); Chris@427: action->setStatusTip(tr("Reset all of the Analyse menu options to their default settings.")); Chris@427: connect(action, SIGNAL(triggered()), this, SLOT(resetAnalyseOptions())); Chris@427: menu->addAction(action); Chris@427: Chris@427: updateAnalyseStates(); Chris@427: } Chris@427: Chris@427: void Chris@427: MainWindow::resetAnalyseOptions() Chris@427: { Chris@427: QSettings settings; Chris@427: settings.beginGroup("Analyser"); Chris@673: Chris@427: settings.setValue("auto-analysis", true); Chris@673: Chris@673: auto keyMap = Analyser::getAnalysisSettings(); Chris@673: for (auto p: keyMap) { Chris@673: settings.setValue(p.first, p.second); Chris@673: } Chris@673: Chris@427: settings.endGroup(); Chris@427: updateAnalyseStates(); Chris@427: } Chris@427: Chris@427: void Chris@427: MainWindow::updateAnalyseStates() Chris@427: { Chris@323: QSettings settings; Chris@323: settings.beginGroup("Analyser"); Chris@673: Chris@323: bool autoAnalyse = settings.value("auto-analysis", true).toBool(); Chris@673: m_autoAnalyse->setChecked(autoAnalyse); Chris@673: Chris@673: std::map actions { Chris@673: { "precision-analysis", m_precise }, Chris@673: { "lowamp-analysis", m_lowamp }, Chris@673: { "onset-analysis", m_onset }, Chris@673: { "prune-analysis", m_prune } Chris@673: }; Chris@673: Chris@673: auto keyMap = Analyser::getAnalysisSettings(); Chris@673: Chris@673: for (auto p: actions) { Chris@673: auto ki = keyMap.find(p.first); Chris@673: if (ki != keyMap.end()) { Chris@673: p.second->setChecked(settings.value Chris@673: (ki->first, ki->second).toBool()); Chris@673: } else { Chris@673: throw std::logic_error("Internal error: One or more analysis settings keys not found in map returned by Analyser: check updateAnalyseStates and getAnalysisSettings"); Chris@673: } Chris@673: } Chris@673: Chris@323: settings.endGroup(); Chris@323: } Chris@323: Chris@323: void Chris@323: MainWindow::autoAnalysisToggled() Chris@323: { Chris@323: QAction *a = qobject_cast(sender()); Chris@323: if (!a) return; Chris@323: Chris@323: bool set = a->isChecked(); Chris@323: Chris@323: QSettings settings; Chris@323: settings.beginGroup("Analyser"); Chris@323: settings.setValue("auto-analysis", set); Chris@323: settings.endGroup(); Chris@673: Chris@673: // make result visible explicitly, in case e.g. we just set the wrong key Chris@673: updateAnalyseStates(); Chris@323: } Chris@323: Chris@323: void Chris@323: MainWindow::precisionAnalysisToggled() Chris@323: { Chris@323: QAction *a = qobject_cast(sender()); Chris@323: if (!a) return; Chris@323: Chris@323: bool set = a->isChecked(); Chris@323: Chris@323: QSettings settings; Chris@323: settings.beginGroup("Analyser"); Chris@323: settings.setValue("precision-analysis", set); Chris@323: settings.endGroup(); Chris@323: Chris@326: // don't run analyseNow() automatically -- it's a destructive operation Chris@673: Chris@673: // make result visible explicitly, in case e.g. we just set the wrong key Chris@673: updateAnalyseStates(); matthiasm@317: } matthiasm@317: matthiasm@317: void matthiasm@345: MainWindow::lowampAnalysisToggled() matthiasm@345: { matthiasm@345: QAction *a = qobject_cast(sender()); matthiasm@345: if (!a) return; matthiasm@345: matthiasm@345: bool set = a->isChecked(); matthiasm@345: matthiasm@345: QSettings settings; matthiasm@345: settings.beginGroup("Analyser"); matthiasm@345: settings.setValue("lowamp-analysis", set); matthiasm@345: settings.endGroup(); matthiasm@345: matthiasm@345: // don't run analyseNow() automatically -- it's a destructive operation Chris@673: Chris@673: // make result visible explicitly, in case e.g. we just set the wrong key Chris@673: updateAnalyseStates(); matthiasm@345: } matthiasm@345: matthiasm@345: void matthiasm@423: MainWindow::onsetAnalysisToggled() matthiasm@423: { matthiasm@423: QAction *a = qobject_cast(sender()); matthiasm@423: if (!a) return; matthiasm@423: matthiasm@423: bool set = a->isChecked(); matthiasm@423: matthiasm@423: QSettings settings; matthiasm@423: settings.beginGroup("Analyser"); matthiasm@423: settings.setValue("onset-analysis", set); matthiasm@423: settings.endGroup(); matthiasm@423: matthiasm@423: // don't run analyseNow() automatically -- it's a destructive operation Chris@673: Chris@673: // make result visible explicitly, in case e.g. we just set the wrong key Chris@673: updateAnalyseStates(); matthiasm@423: } matthiasm@423: matthiasm@423: void matthiasm@423: MainWindow::pruneAnalysisToggled() matthiasm@423: { matthiasm@423: QAction *a = qobject_cast(sender()); matthiasm@423: if (!a) return; matthiasm@423: matthiasm@423: bool set = a->isChecked(); matthiasm@423: matthiasm@423: QSettings settings; matthiasm@423: settings.beginGroup("Analyser"); matthiasm@423: settings.setValue("prune-analysis", set); matthiasm@423: settings.endGroup(); matthiasm@423: matthiasm@423: // don't run analyseNow() automatically -- it's a destructive operation Chris@673: Chris@673: // make result visible explicitly, in case e.g. we just set the wrong key Chris@673: updateAnalyseStates(); matthiasm@423: } matthiasm@423: matthiasm@423: void Chris@0: MainWindow::setupHelpMenu() Chris@0: { Chris@0: QMenu *menu = menuBar()->addMenu(tr("&Help")); Chris@0: menu->setTearOffEnabled(true); Chris@0: Chris@0: m_keyReference->setCategory(tr("Help")); Chris@0: Chris@0: IconLoader il; Chris@0: Chris@1: QString name = QApplication::applicationName(); matthiasm@367: QAction *action; matthiasm@367: Chris@585: action = new QAction(il.load("help"), Chris@585: tr("&Help Reference"), this); Chris@585: action->setShortcut(tr("F1")); Chris@585: action->setStatusTip(tr("Open the %1 reference manual").arg(name)); Chris@585: connect(action, SIGNAL(triggered()), this, SLOT(help())); Chris@585: m_keyReference->registerShortcut(action); Chris@585: menu->addAction(action); Chris@585: Chris@0: action = new QAction(tr("&Key and Mouse Reference"), this); Chris@0: action->setShortcut(tr("F2")); Chris@1: action->setStatusTip(tr("Open a window showing the keystrokes you can use in %1").arg(name)); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(keyReference())); Chris@0: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); Chris@0: Chris@585: action = new QAction(tr("What's &New In This Release?"), this); Chris@585: action->setStatusTip(tr("List the changes in this release (and every previous release) of %1").arg(name)); Chris@585: connect(action, SIGNAL(triggered()), this, SLOT(whatsNew())); Chris@0: menu->addAction(action); Chris@0: Chris@1: action = new QAction(tr("&About %1").arg(name), this); Chris@1: action->setStatusTip(tr("Show information about %1").arg(name)); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(about())); Chris@0: menu->addAction(action); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupRecentFilesMenu() Chris@0: { Chris@0: m_recentFilesMenu->clear(); Chris@0: vector files = m_recentFiles.getRecent(); Chris@0: for (size_t i = 0; i < files.size(); ++i) { Chris@50: QAction *action = new QAction(files[i], this); Chris@50: connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); Chris@0: if (i == 0) { Chris@0: action->setShortcut(tr("Ctrl+R")); Chris@0: m_keyReference->registerShortcut Chris@0: (tr("Re-open"), Chris@50: action->shortcut().toString(), Chris@0: tr("Re-open the current or most recently opened file")); Chris@0: } Chris@50: m_recentFilesMenu->addAction(action); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupToolbars() Chris@0: { Chris@0: m_keyReference->setCategory(tr("Playback and Transport Controls")); Chris@0: Chris@0: IconLoader il; Chris@0: Chris@0: QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back")); Chris@0: menu->setTearOffEnabled(true); Chris@0: m_rightButtonMenu->addSeparator(); Chris@0: m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback")); Chris@0: Chris@0: QToolBar *toolbar = addToolBar(tr("Playback Toolbar")); Chris@0: Chris@0: QAction *rwdStartAction = toolbar->addAction(il.load("rewind-start"), Chris@0: tr("Rewind to Start")); Chris@0: rwdStartAction->setShortcut(tr("Home")); Chris@0: rwdStartAction->setStatusTip(tr("Rewind to the start")); Chris@0: connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart())); Chris@0: connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool))); Chris@0: Chris@0: QAction *m_rwdAction = toolbar->addAction(il.load("rewind"), Chris@0: tr("Rewind")); Chris@300: m_rwdAction->setShortcut(tr("Left")); Chris@300: m_rwdAction->setStatusTip(tr("Rewind to the previous one-second boundary")); Chris@0: connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind())); Chris@0: connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool))); Chris@0: Chris@300: setDefaultFfwdRwdStep(RealTime(1, 0)); Chris@300: Chris@0: QAction *playAction = toolbar->addAction(il.load("playpause"), Chris@0: tr("Play / Pause")); Chris@0: playAction->setCheckable(true); Chris@0: playAction->setShortcut(tr("Space")); Chris@0: playAction->setStatusTip(tr("Start or stop playback from the current position")); Chris@0: connect(playAction, SIGNAL(triggered()), this, SLOT(play())); Chris@0: connect(m_playSource, SIGNAL(playStatusChanged(bool)), gyorgyf@27: playAction, SLOT(setChecked(bool))); Chris@0: connect(this, SIGNAL(canPlay(bool)), playAction, SLOT(setEnabled(bool))); Chris@0: Chris@0: m_ffwdAction = toolbar->addAction(il.load("ffwd"), Chris@0: tr("Fast Forward")); Chris@300: m_ffwdAction->setShortcut(tr("Right")); Chris@300: m_ffwdAction->setStatusTip(tr("Fast-forward to the next one-second boundary")); Chris@0: connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd())); Chris@0: connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool))); Chris@0: Chris@0: QAction *ffwdEndAction = toolbar->addAction(il.load("ffwd-end"), Chris@0: tr("Fast Forward to End")); Chris@0: ffwdEndAction->setShortcut(tr("End")); Chris@0: ffwdEndAction->setStatusTip(tr("Fast-forward to the end")); Chris@0: connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd())); Chris@0: connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool))); Chris@6: Chris@476: QAction *recordAction = toolbar->addAction(il.load("record"), Chris@476: tr("Record")); Chris@476: recordAction->setCheckable(true); Chris@476: recordAction->setShortcut(tr("Ctrl+Space")); Chris@476: recordAction->setStatusTip(tr("Record a new audio file")); Chris@476: connect(recordAction, SIGNAL(triggered()), this, SLOT(record())); Chris@476: connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)), Chris@476: recordAction, SLOT(setChecked(bool))); Chris@497: connect(m_recordTarget, SIGNAL(recordCompleted()), Chris@497: this, SLOT(analyseNow())); Chris@476: connect(this, SIGNAL(canRecord(bool)), Chris@476: recordAction, SLOT(setEnabled(bool))); Chris@476: Chris@0: toolbar = addToolBar(tr("Play Mode Toolbar")); Chris@0: Chris@0: QAction *psAction = toolbar->addAction(il.load("playselection"), Chris@0: tr("Constrain Playback to Selection")); Chris@0: psAction->setCheckable(true); Chris@0: psAction->setChecked(m_viewManager->getPlaySelectionMode()); Chris@0: psAction->setShortcut(tr("s")); Chris@0: psAction->setStatusTip(tr("Constrain playback to the selected regions")); Chris@0: connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)), Chris@0: psAction, SLOT(setChecked(bool))); Chris@0: connect(psAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); Chris@0: connect(this, SIGNAL(canPlaySelection(bool)), psAction, SLOT(setEnabled(bool))); Chris@0: Chris@0: QAction *plAction = toolbar->addAction(il.load("playloop"), Chris@0: tr("Loop Playback")); Chris@0: plAction->setCheckable(true); Chris@0: plAction->setChecked(m_viewManager->getPlayLoopMode()); Chris@0: plAction->setShortcut(tr("l")); Chris@0: plAction->setStatusTip(tr("Loop playback")); Chris@0: connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)), Chris@0: plAction, SLOT(setChecked(bool))); Chris@0: connect(plAction, SIGNAL(triggered()), this, SLOT(playLoopToggled())); Chris@0: connect(this, SIGNAL(canPlay(bool)), plAction, SLOT(setEnabled(bool))); Chris@0: Chris@300: QAction *oneLeftAction = new QAction(tr("&One Note Left"), this); Chris@300: oneLeftAction->setShortcut(tr("Ctrl+Left")); Chris@300: oneLeftAction->setStatusTip(tr("Move cursor to the preceding note (or silence) onset.")); Chris@300: connect(oneLeftAction, SIGNAL(triggered()), this, SLOT(moveOneNoteLeft())); Chris@300: connect(this, SIGNAL(canScroll(bool)), oneLeftAction, SLOT(setEnabled(bool))); Chris@300: Chris@300: QAction *oneRightAction = new QAction(tr("O&ne Note Right"), this); Chris@300: oneRightAction->setShortcut(tr("Ctrl+Right")); Chris@300: oneRightAction->setStatusTip(tr("Move cursor to the succeeding note (or silence).")); Chris@300: connect(oneRightAction, SIGNAL(triggered()), this, SLOT(moveOneNoteRight())); Chris@300: connect(this, SIGNAL(canScroll(bool)), oneRightAction, SLOT(setEnabled(bool))); Chris@300: Chris@300: QAction *selectOneLeftAction = new QAction(tr("&Select One Note Left"), this); Chris@300: selectOneLeftAction->setShortcut(tr("Ctrl+Shift+Left")); Chris@300: selectOneLeftAction->setStatusTip(tr("Select to the preceding note (or silence) onset.")); Chris@300: connect(selectOneLeftAction, SIGNAL(triggered()), this, SLOT(selectOneNoteLeft())); Chris@300: connect(this, SIGNAL(canScroll(bool)), selectOneLeftAction, SLOT(setEnabled(bool))); Chris@300: Chris@300: QAction *selectOneRightAction = new QAction(tr("S&elect One Note Right"), this); Chris@300: selectOneRightAction->setShortcut(tr("Ctrl+Shift+Right")); Chris@300: selectOneRightAction->setStatusTip(tr("Select to the succeeding note (or silence).")); Chris@300: connect(selectOneRightAction, SIGNAL(triggered()), this, SLOT(selectOneNoteRight())); Chris@300: connect(this, SIGNAL(canScroll(bool)), selectOneRightAction, SLOT(setEnabled(bool))); Chris@300: Chris@0: m_keyReference->registerShortcut(psAction); Chris@0: m_keyReference->registerShortcut(plAction); Chris@0: m_keyReference->registerShortcut(playAction); Chris@476: m_keyReference->registerShortcut(recordAction); Chris@0: m_keyReference->registerShortcut(m_rwdAction); Chris@0: m_keyReference->registerShortcut(m_ffwdAction); Chris@0: m_keyReference->registerShortcut(rwdStartAction); Chris@0: m_keyReference->registerShortcut(ffwdEndAction); Chris@476: m_keyReference->registerShortcut(recordAction); Chris@300: m_keyReference->registerShortcut(oneLeftAction); Chris@300: m_keyReference->registerShortcut(oneRightAction); Chris@300: m_keyReference->registerShortcut(selectOneLeftAction); Chris@300: m_keyReference->registerShortcut(selectOneRightAction); Chris@0: Chris@6: menu->addAction(playAction); Chris@0: menu->addAction(psAction); Chris@0: menu->addAction(plAction); Chris@0: menu->addSeparator(); Chris@0: menu->addAction(m_rwdAction); Chris@0: menu->addAction(m_ffwdAction); Chris@0: menu->addSeparator(); Chris@0: menu->addAction(rwdStartAction); Chris@0: menu->addAction(ffwdEndAction); Chris@0: menu->addSeparator(); Chris@300: menu->addAction(oneLeftAction); Chris@300: menu->addAction(oneRightAction); Chris@300: menu->addAction(selectOneLeftAction); Chris@300: menu->addAction(selectOneRightAction); Chris@300: menu->addSeparator(); Chris@476: menu->addAction(recordAction); Chris@476: menu->addSeparator(); Chris@0: Chris@0: m_rightButtonPlaybackMenu->addAction(playAction); Chris@0: m_rightButtonPlaybackMenu->addAction(psAction); Chris@0: m_rightButtonPlaybackMenu->addAction(plAction); Chris@0: m_rightButtonPlaybackMenu->addSeparator(); Chris@0: m_rightButtonPlaybackMenu->addAction(m_rwdAction); Chris@0: m_rightButtonPlaybackMenu->addAction(m_ffwdAction); Chris@0: m_rightButtonPlaybackMenu->addSeparator(); Chris@0: m_rightButtonPlaybackMenu->addAction(rwdStartAction); Chris@0: m_rightButtonPlaybackMenu->addAction(ffwdEndAction); Chris@0: m_rightButtonPlaybackMenu->addSeparator(); Chris@300: m_rightButtonPlaybackMenu->addAction(oneLeftAction); Chris@300: m_rightButtonPlaybackMenu->addAction(oneRightAction); Chris@300: m_rightButtonPlaybackMenu->addAction(selectOneLeftAction); Chris@300: m_rightButtonPlaybackMenu->addAction(selectOneRightAction); Chris@300: m_rightButtonPlaybackMenu->addSeparator(); Chris@476: m_rightButtonPlaybackMenu->addAction(recordAction); Chris@476: m_rightButtonPlaybackMenu->addSeparator(); Chris@0: Chris@0: QAction *fastAction = menu->addAction(tr("Speed Up")); Chris@0: fastAction->setShortcut(tr("Ctrl+PgUp")); Chris@0: fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch")); Chris@0: connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback())); Chris@0: connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool))); Chris@0: Chris@0: QAction *slowAction = menu->addAction(tr("Slow Down")); Chris@0: slowAction->setShortcut(tr("Ctrl+PgDown")); Chris@0: slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch")); Chris@0: connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback())); Chris@0: connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool))); Chris@0: Chris@0: QAction *normalAction = menu->addAction(tr("Restore Normal Speed")); Chris@0: normalAction->setShortcut(tr("Ctrl+Home")); Chris@0: normalAction->setStatusTip(tr("Restore non-time-stretched playback")); Chris@0: connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback())); Chris@0: connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool))); Chris@0: Chris@0: m_keyReference->registerShortcut(fastAction); Chris@0: m_keyReference->registerShortcut(slowAction); Chris@0: m_keyReference->registerShortcut(normalAction); Chris@0: Chris@0: m_rightButtonPlaybackMenu->addAction(fastAction); Chris@0: m_rightButtonPlaybackMenu->addAction(slowAction); Chris@0: m_rightButtonPlaybackMenu->addAction(normalAction); Chris@6: Chris@195: toolbar = new QToolBar(tr("Playback Controls")); Chris@195: addToolBar(Qt::BottomToolBarArea, toolbar); Chris@195: Chris@6: toolbar->addWidget(m_playSpeed); Chris@6: toolbar->addWidget(m_fader); Chris@0: Chris@128: toolbar = addToolBar(tr("Show and Play")); matthiasm@258: addToolBar(Qt::BottomToolBarArea, toolbar); Chris@144: Chris@144: m_showAudio = toolbar->addAction(il.load("waveform"), tr("Show Audio")); Chris@144: m_showAudio->setCheckable(true); Chris@144: connect(m_showAudio, SIGNAL(triggered()), this, SLOT(showAudioToggled())); Chris@144: connect(this, SIGNAL(canPlay(bool)), m_showAudio, SLOT(setEnabled(bool))); Chris@144: Chris@424: m_playAudio = toolbar->addAction(il.load("speaker"), tr("Play Audio")); Chris@424: m_playAudio->setCheckable(true); Chris@424: connect(m_playAudio, SIGNAL(triggered()), this, SLOT(playAudioToggled())); Chris@424: connect(this, SIGNAL(canPlayWaveform(bool)), m_playAudio, SLOT(setEnabled(bool))); Chris@424: Chris@440: int lpwSize, bigLpwSize; Chris@440: #ifdef Q_OS_MAC Chris@440: lpwSize = m_viewManager->scalePixelSize(32); // Mac toolbars are fatter Chris@442: bigLpwSize = int(lpwSize * 2.2); Chris@440: #else Chris@440: lpwSize = m_viewManager->scalePixelSize(26); Chris@442: bigLpwSize = int(lpwSize * 2.8); Chris@440: #endif Chris@440: Chris@440: m_audioLPW->setImageSize(lpwSize); Chris@440: m_audioLPW->setBigImageSize(bigLpwSize); Chris@414: toolbar->addWidget(m_audioLPW); Chris@414: Chris@290: // Pitch (f0) Chris@290: QLabel *spacer = new QLabel; // blank Chris@419: spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); Chris@290: toolbar->addWidget(spacer); Chris@145: Chris@144: m_showPitch = toolbar->addAction(il.load("values"), tr("Show Pitch Track")); Chris@144: m_showPitch->setCheckable(true); Chris@144: connect(m_showPitch, SIGNAL(triggered()), this, SLOT(showPitchToggled())); Chris@144: connect(this, SIGNAL(canPlay(bool)), m_showPitch, SLOT(setEnabled(bool))); Chris@144: Chris@405: if (m_withSonification) { Chris@424: m_playPitch = toolbar->addAction(il.load("speaker"), tr("Play Pitch Track")); Chris@424: m_playPitch->setCheckable(true); Chris@424: connect(m_playPitch, SIGNAL(triggered()), this, SLOT(playPitchToggled())); Chris@424: connect(this, SIGNAL(canPlayPitch(bool)), m_playPitch, SLOT(setEnabled(bool))); Chris@424: Chris@440: m_pitchLPW->setImageSize(lpwSize); Chris@440: m_pitchLPW->setBigImageSize(bigLpwSize); Chris@404: toolbar->addWidget(m_pitchLPW); Chris@424: } else { Chris@424: m_playPitch = 0; matthiasm@366: } Chris@404: Chris@290: // Notes Chris@290: spacer = new QLabel; Chris@419: spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); Chris@290: toolbar->addWidget(spacer); Chris@290: Chris@144: m_showNotes = toolbar->addAction(il.load("notes"), tr("Show Notes")); Chris@144: m_showNotes->setCheckable(true); Chris@144: connect(m_showNotes, SIGNAL(triggered()), this, SLOT(showNotesToggled())); Chris@144: connect(this, SIGNAL(canPlay(bool)), m_showNotes, SLOT(setEnabled(bool))); justin@156: Chris@405: if (m_withSonification) { Chris@424: m_playNotes = toolbar->addAction(il.load("speaker"), tr("Play Notes")); Chris@424: m_playNotes->setCheckable(true); Chris@424: connect(m_playNotes, SIGNAL(triggered()), this, SLOT(playNotesToggled())); Chris@424: connect(this, SIGNAL(canPlayNotes(bool)), m_playNotes, SLOT(setEnabled(bool))); Chris@424: Chris@440: m_notesLPW->setImageSize(lpwSize); Chris@440: m_notesLPW->setBigImageSize(bigLpwSize); Chris@404: toolbar->addWidget(m_notesLPW); Chris@424: } else { Chris@424: m_playNotes = 0; matthiasm@366: } justin@156: justin@156: // Spectrogram Chris@290: spacer = new QLabel; Chris@419: spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); Chris@290: toolbar->addWidget(spacer); Chris@290: matthiasm@366: if (!m_withSpectrogram) matthiasm@366: { matthiasm@366: m_showSpect = new QAction(tr("Show Spectrogram"), this); matthiasm@366: } else { matthiasm@366: m_showSpect = toolbar->addAction(il.load("spectrogram"), tr("Show Spectrogram")); matthiasm@366: } justin@156: m_showSpect->setCheckable(true); justin@156: connect(m_showSpect, SIGNAL(triggered()), this, SLOT(showSpectToggled())); justin@156: connect(this, SIGNAL(canPlay(bool)), m_showSpect, SLOT(setEnabled(bool))); Chris@128: Chris@0: Pane::registerShortcuts(*m_keyReference); matthiasm@366: Chris@405: updateLayerStatuses(); Chris@609: Chris@657: // QTimer::singleShot(500, this, SLOT(betaReleaseWarning())); Chris@0: } Chris@0: matthiasm@281: matthiasm@281: void matthiasm@281: MainWindow::moveOneNoteRight() matthiasm@281: { matthiasm@281: // cerr << "MainWindow::moveOneNoteRight" << endl; matthiasm@296: moveByOneNote(true, false); matthiasm@281: } matthiasm@281: matthiasm@281: void matthiasm@281: MainWindow::moveOneNoteLeft() matthiasm@281: { matthiasm@281: // cerr << "MainWindow::moveOneNoteLeft" << endl; matthiasm@296: moveByOneNote(false, false); matthiasm@281: } matthiasm@281: matthiasm@281: void matthiasm@283: MainWindow::selectOneNoteRight() matthiasm@283: { matthiasm@296: moveByOneNote(true, true); matthiasm@283: } matthiasm@283: matthiasm@283: void matthiasm@283: MainWindow::selectOneNoteLeft() matthiasm@283: { matthiasm@296: moveByOneNote(false, true); matthiasm@283: } matthiasm@283: matthiasm@283: matthiasm@283: void matthiasm@296: MainWindow::moveByOneNote(bool right, bool doSelect) matthiasm@281: { Chris@399: sv_frame_t frame = m_viewManager->getPlaybackFrame(); matthiasm@301: cerr << "MainWindow::moveByOneNote startframe: " << frame << endl; matthiasm@281: matthiasm@304: bool isAtSelectionBoundary = false; matthiasm@304: MultiSelection::SelectionList selections = m_viewManager->getSelections(); matthiasm@304: if (!selections.empty()) { matthiasm@304: Selection sel = *selections.begin(); matthiasm@342: isAtSelectionBoundary = (frame == sel.getStartFrame()) || (frame == sel.getEndFrame()); matthiasm@304: } matthiasm@304: if (!doSelect || !isAtSelectionBoundary) { matthiasm@296: m_selectionAnchor = frame; matthiasm@296: } matthiasm@296: matthiasm@281: Layer *layer = m_analyser->getLayer(Analyser::Notes); matthiasm@281: if (!layer) return; matthiasm@281: Chris@573: auto model = ModelById::getAs(layer->getModel()); matthiasm@281: if (!model) return; matthiasm@281: Chris@570: //!!! This seems like a strange and inefficient way to do this - Chris@570: //!!! there is almost certainly a better way making use of Chris@570: //!!! EventSeries api Chris@570: Chris@570: EventVector points = model->getAllEvents(); matthiasm@281: if (points.empty()) return; matthiasm@281: Chris@570: EventVector::iterator i = points.begin(); Chris@399: std::set snapFrames; matthiasm@281: snapFrames.insert(0); matthiasm@281: while (i != points.end()) { Chris@570: snapFrames.insert(i->getFrame()); Chris@570: snapFrames.insert(i->getFrame() + i->getDuration() + 1); matthiasm@281: ++i; matthiasm@281: } Chris@399: std::set::iterator i2; Chris@399: if (snapFrames.find(frame) == snapFrames.end()) { matthiasm@281: // we're not on an existing snap point, so go to previous matthiasm@281: snapFrames.insert(frame); matthiasm@281: } matthiasm@281: i2 = snapFrames.find(frame); Chris@399: if (right) { matthiasm@281: i2++; matthiasm@281: if (i2 == snapFrames.end()) i2--; matthiasm@281: } else { matthiasm@281: if (i2 != snapFrames.begin()) i2--; matthiasm@281: } matthiasm@281: frame = *i2; matthiasm@281: m_viewManager->setPlaybackFrame(frame); matthiasm@296: if (doSelect) { matthiasm@296: Selection sel; matthiasm@296: if (frame > m_selectionAnchor) { matthiasm@342: sel = Selection(m_selectionAnchor, frame); matthiasm@296: } else { matthiasm@342: sel = Selection(frame, m_selectionAnchor); matthiasm@296: } matthiasm@296: m_viewManager->setSelection(sel); matthiasm@296: } matthiasm@301: cerr << "MainWindow::moveByOneNote endframe: " << frame << endl; matthiasm@281: } matthiasm@281: Chris@0: void Chris@70: MainWindow::toolNavigateSelected() Chris@70: { Chris@70: m_viewManager->setToolMode(ViewManager::NavigateMode); Chris@70: m_intelligentActionOn = true; Chris@70: } Chris@70: Chris@70: void Chris@70: MainWindow::toolEditSelected() Chris@70: { matthiasm@294: cerr << "MainWindow::toolEditSelected" << endl; Chris@77: m_viewManager->setToolMode(ViewManager::NoteEditMode); Chris@70: m_intelligentActionOn = true; Chris@70: m_analyser->setIntelligentActions(m_intelligentActionOn); Chris@70: } Chris@70: Chris@70: void Chris@70: MainWindow::toolFreeEditSelected() Chris@70: { Chris@77: m_viewManager->setToolMode(ViewManager::NoteEditMode); Chris@70: m_intelligentActionOn = false; Chris@70: m_analyser->setIntelligentActions(m_intelligentActionOn); Chris@70: } Chris@70: gyorgyf@21: void Chris@0: MainWindow::updateMenuStates() Chris@0: { Chris@0: MainWindowBase::updateMenuStates(); Chris@0: Chris@0: Pane *currentPane = 0; Chris@0: Layer *currentLayer = 0; Chris@0: Chris@0: if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); Chris@0: if (currentPane) currentLayer = currentPane->getSelectedLayer(); Chris@0: Chris@291: bool haveMainModel = Chris@291: (getMainModel() != 0); Chris@291: bool havePlayTarget = Chris@479: (m_playTarget != 0 || m_audioIO != 0); Chris@0: bool haveCurrentPane = Chris@0: (currentPane != 0); Chris@0: bool haveCurrentLayer = Chris@0: (haveCurrentPane && Chris@0: (currentLayer != 0)); Chris@0: bool haveSelection = Chris@187: (m_viewManager && Chris@187: !m_viewManager->getSelections().empty()); Chris@0: bool haveCurrentTimeInstantsLayer = Chris@187: (haveCurrentLayer && Chris@187: qobject_cast(currentLayer)); Chris@0: bool haveCurrentTimeValueLayer = Chris@187: (haveCurrentLayer && Chris@187: qobject_cast(currentLayer)); Chris@187: bool pitchCandidatesVisible = Chris@187: m_analyser->arePitchCandidatesShown(); Chris@0: Chris@0: emit canChangePlaybackSpeed(true); Chris@0: int v = m_playSpeed->value(); Chris@0: emit canSpeedUpPlayback(v < m_playSpeed->maximum()); Chris@0: emit canSlowDownPlayback(v > m_playSpeed->minimum()); Chris@0: Chris@291: bool haveWaveform = Chris@291: m_analyser->isVisible(Analyser::Audio) && Chris@291: m_analyser->getLayer(Analyser::Audio); Chris@291: Chris@237: bool havePitchTrack = Chris@237: m_analyser->isVisible(Analyser::PitchTrack) && Chris@237: m_analyser->getLayer(Analyser::PitchTrack); Chris@237: Chris@237: bool haveNotes = Chris@237: m_analyser->isVisible(Analyser::Notes) && Chris@237: m_analyser->getLayer(Analyser::Notes); Chris@237: Chris@237: emit canExportPitchTrack(havePitchTrack); Chris@237: emit canExportNotes(haveNotes); Chris@237: emit canSnapNotes(haveSelection && haveNotes); Chris@224: Chris@291: emit canPlayWaveform(haveWaveform && haveMainModel && havePlayTarget); Chris@291: emit canPlayPitch(havePitchTrack && haveMainModel && havePlayTarget); Chris@291: emit canPlayNotes(haveNotes && haveMainModel && havePlayTarget); Chris@291: Chris@187: if (pitchCandidatesVisible) { Chris@187: m_showCandidatesAction->setText(tr("Hide Pitch Candidates")); Chris@187: m_showCandidatesAction->setStatusTip(tr("Remove the display of alternate pitch candidates for the selected region")); Chris@187: } else { Chris@187: m_showCandidatesAction->setText(tr("Show Pitch Candidates")); Chris@187: m_showCandidatesAction->setStatusTip(tr("Show alternate pitch candidates for the selected region")); Chris@187: } Chris@187: Chris@0: if (m_ffwdAction && m_rwdAction) { Chris@0: if (haveCurrentTimeInstantsLayer) { Chris@0: m_ffwdAction->setText(tr("Fast Forward to Next Instant")); Chris@0: m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer")); Chris@0: m_rwdAction->setText(tr("Rewind to Previous Instant")); Chris@0: m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer")); Chris@0: } else if (haveCurrentTimeValueLayer) { Chris@0: m_ffwdAction->setText(tr("Fast Forward to Next Point")); Chris@0: m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer")); Chris@0: m_rwdAction->setText(tr("Rewind to Previous Point")); Chris@0: m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer")); Chris@0: } else { Chris@0: m_ffwdAction->setText(tr("Fast Forward")); Chris@0: m_ffwdAction->setStatusTip(tr("Fast forward")); Chris@0: m_rwdAction->setText(tr("Rewind")); Chris@0: m_rwdAction->setStatusTip(tr("Rewind")); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@144: MainWindow::showAudioToggled() Chris@128: { Chris@144: m_analyser->toggleVisible(Analyser::Audio); justin@220: Chris@424: QSettings settings; Chris@424: settings.beginGroup("MainWindow"); Chris@424: Chris@424: bool playOn = false; Chris@424: if (m_analyser->isVisible(Analyser::Audio)) { Chris@424: // just switched layer on; check whether playback was also on previously Chris@424: playOn = settings.value("playaudiowas", true).toBool(); Chris@424: } else { Chris@424: settings.setValue("playaudiowas", m_playAudio->isChecked()); Chris@424: } Chris@291: m_analyser->setAudible(Analyser::Audio, playOn); Chris@424: Chris@424: settings.endGroup(); Chris@291: Chris@291: updateMenuStates(); Chris@424: updateLayerStatuses(); Chris@128: } Chris@128: Chris@128: void Chris@144: MainWindow::showPitchToggled() Chris@128: { Chris@144: m_analyser->toggleVisible(Analyser::PitchTrack); justin@185: Chris@424: QSettings settings; Chris@424: settings.beginGroup("MainWindow"); Chris@424: Chris@424: bool playOn = false; Chris@424: if (m_analyser->isVisible(Analyser::PitchTrack)) { Chris@424: // just switched layer on; check whether playback was also on previously Chris@424: playOn = settings.value("playpitchwas", true).toBool(); Chris@424: } else { Chris@424: settings.setValue("playpitchwas", m_playPitch->isChecked()); Chris@424: } Chris@424: m_analyser->setAudible(Analyser::PitchTrack, playOn); Chris@424: Chris@424: settings.endGroup(); Chris@291: Chris@291: updateMenuStates(); Chris@424: updateLayerStatuses(); Chris@128: } Chris@128: Chris@128: void Chris@145: MainWindow::showSpectToggled() Chris@145: { Chris@145: m_analyser->toggleVisible(Analyser::Spectrogram); Chris@145: } Chris@145: Chris@145: void Chris@144: MainWindow::showNotesToggled() Chris@128: { Chris@144: m_analyser->toggleVisible(Analyser::Notes); justin@220: Chris@424: QSettings settings; Chris@424: settings.beginGroup("MainWindow"); Chris@424: Chris@424: bool playOn = false; Chris@424: if (m_analyser->isVisible(Analyser::Notes)) { Chris@424: // just switched layer on; check whether playback was also on previously Chris@424: playOn = settings.value("playnoteswas", true).toBool(); Chris@424: } else { Chris@424: settings.setValue("playnoteswas", m_playNotes->isChecked()); Chris@424: } Chris@424: m_analyser->setAudible(Analyser::Notes, playOn); Chris@424: Chris@424: settings.endGroup(); Chris@291: Chris@291: updateMenuStates(); Chris@424: updateLayerStatuses(); Chris@144: } Chris@144: Chris@144: void Chris@144: MainWindow::playAudioToggled() Chris@144: { Chris@144: m_analyser->toggleAudible(Analyser::Audio); Chris@424: updateLayerStatuses(); Chris@144: } Chris@144: Chris@144: void Chris@144: MainWindow::playPitchToggled() Chris@144: { Chris@144: m_analyser->toggleAudible(Analyser::PitchTrack); Chris@424: updateLayerStatuses(); Chris@144: } Chris@144: Chris@144: void Chris@144: MainWindow::playNotesToggled() Chris@144: { Chris@144: m_analyser->toggleAudible(Analyser::Notes); Chris@424: updateLayerStatuses(); Chris@144: } Chris@144: Chris@144: void Chris@144: MainWindow::updateLayerStatuses() Chris@144: { Chris@144: m_showAudio->setChecked(m_analyser->isVisible(Analyser::Audio)); Chris@424: m_playAudio->setChecked(m_analyser->isAudible(Analyser::Audio)); Chris@424: m_audioLPW->setEnabled(m_analyser->isAudible(Analyser::Audio)); Chris@405: m_audioLPW->setLevel(m_analyser->getGain(Analyser::Audio)); Chris@405: m_audioLPW->setPan(m_analyser->getPan(Analyser::Audio)); Chris@424: Chris@405: m_showPitch->setChecked(m_analyser->isVisible(Analyser::PitchTrack)); Chris@424: m_playPitch->setChecked(m_analyser->isAudible(Analyser::PitchTrack)); Chris@424: m_pitchLPW->setEnabled(m_analyser->isAudible(Analyser::PitchTrack)); Chris@405: m_pitchLPW->setLevel(m_analyser->getGain(Analyser::PitchTrack)); Chris@405: m_pitchLPW->setPan(m_analyser->getPan(Analyser::PitchTrack)); Chris@405: Chris@405: m_showNotes->setChecked(m_analyser->isVisible(Analyser::Notes)); Chris@424: m_playNotes->setChecked(m_analyser->isAudible(Analyser::Notes)); Chris@424: m_notesLPW->setEnabled(m_analyser->isAudible(Analyser::Notes)); Chris@405: m_notesLPW->setLevel(m_analyser->getGain(Analyser::Notes)); Chris@405: m_notesLPW->setPan(m_analyser->getPan(Analyser::Notes)); Chris@405: Chris@145: m_showSpect->setChecked(m_analyser->isVisible(Analyser::Spectrogram)); Chris@128: } Chris@128: Chris@128: void Chris@227: MainWindow::editDisplayExtents() Chris@227: { Chris@399: double min, max; Chris@399: double vmin = 0; Chris@399: double vmax = getMainModel()->getSampleRate() /2; Chris@227: Chris@227: if (!m_analyser->getDisplayFrequencyExtents(min, max)) { Chris@227: //!!! Chris@227: return; Chris@227: } Chris@227: Chris@227: RangeInputDialog dialog(tr("Set frequency range"), Chris@227: tr("Enter new frequency range, from %1 to %2 Hz.\nThese values will be rounded to the nearest spectrogram bin.") Chris@227: .arg(vmin).arg(vmax), Chris@399: "Hz", float(vmin), float(vmax), this); Chris@399: dialog.setRange(float(min), float(max)); Chris@227: Chris@227: if (dialog.exec() == QDialog::Accepted) { Chris@399: float fmin, fmax; Chris@399: dialog.getRange(fmin, fmax); Chris@399: min = fmin; Chris@399: max = fmax; Chris@227: if (min > max) { Chris@399: double tmp = max; Chris@227: max = min; Chris@227: min = tmp; Chris@227: } Chris@227: m_analyser->setDisplayFrequencyExtents(min, max); Chris@227: } Chris@227: } Chris@227: Chris@227: void Chris@0: MainWindow::updateDescriptionLabel() Chris@0: { Chris@6: // Nothing, we don't have one Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::documentModified() Chris@0: { Chris@0: MainWindowBase::documentModified(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::documentRestored() Chris@0: { Chris@0: MainWindowBase::documentRestored(); 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: m_document->setAutoAlignment(true); Chris@0: Chris@604: Pane *pane = m_paneStack->addPane(); Chris@362: pane->setPlaybackFollow(PlaybackScrollPage); Chris@0: Chris@391: m_viewManager->setGlobalCentreFrame Chris@391: (pane->getFrameForX(width() / 2)); Chris@391: Chris@0: connect(pane, SIGNAL(contextHelpChanged(const QString &)), Chris@0: this, SLOT(contextHelpChanged(const QString &))); Chris@0: Chris@6: // Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); Chris@6: // m_document->addLayerToView(pane, waveform); Chris@0: Chris@0: m_overview->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@244: MainWindow::documentReplaced() Chris@244: { Chris@244: if (m_document) { Chris@244: connect(m_document, SIGNAL(activity(QString)), Chris@244: m_activityLog, SLOT(activityHappened(QString))); Chris@244: } Chris@244: } Chris@244: Chris@244: void Chris@0: MainWindow::closeSession() Chris@0: { Chris@0: if (!checkSaveModified()) return; Chris@0: Chris@226: m_analyser->fileClosed(); Chris@226: Chris@0: while (m_paneStack->getPaneCount() > 0) { Chris@0: Chris@167: Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); Chris@0: Chris@167: while (pane->getLayerCount() > 0) { Chris@167: m_document->removeLayerFromView Chris@167: (pane, pane->getLayer(pane->getLayerCount() - 1)); Chris@167: } Chris@167: Chris@167: m_overview->unregisterView(pane); Chris@167: m_paneStack->deletePane(pane); Chris@0: } Chris@0: Chris@0: while (m_paneStack->getHiddenPaneCount() > 0) { Chris@0: Chris@167: Pane *pane = m_paneStack->getHiddenPane Chris@167: (m_paneStack->getHiddenPaneCount() - 1); Chris@167: Chris@167: while (pane->getLayerCount() > 0) { Chris@167: m_document->removeLayerFromView Chris@167: (pane, pane->getLayer(pane->getLayerCount() - 1)); Chris@167: } Chris@167: Chris@167: m_overview->unregisterView(pane); Chris@167: 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: Chris@0: CommandHistory::getInstance()->clear(); Chris@0: CommandHistory::getInstance()->documentSaved(); Chris@0: documentRestored(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::openFile() Chris@0: { Chris@0: QString orig = m_audioFile; Chris@0: if (orig == "") orig = "."; Chris@0: else orig = QFileInfo(orig).absoluteDir().canonicalPath(); Chris@0: Chris@680: QString path = getOpenFileName(FileFinder::SessionOrAudioFile); Chris@0: Chris@0: if (path.isEmpty()) return; Chris@0: Chris@387: FileOpenStatus status = openPath(path, ReplaceSession); Chris@0: Chris@0: if (status == FileOpenFailed) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("File open failed

File \"%1\" could not be opened").arg(path)); Chris@0: } else if (status == FileOpenWrongMode) { Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@0: tr("Audio required

Please load at least one audio file before importing annotation data")); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::openLocation() Chris@0: { Chris@0: QSettings settings; Chris@0: settings.beginGroup("MainWindow"); Chris@0: QString lastLocation = settings.value("lastremote", "").toString(); Chris@0: Chris@0: bool ok = false; Chris@0: QString text = QInputDialog::getText Chris@0: (this, tr("Open Location"), Chris@0: tr("Please enter the URL of the location to open:"), Chris@0: QLineEdit::Normal, lastLocation, &ok); Chris@0: Chris@0: if (!ok) return; Chris@0: Chris@0: settings.setValue("lastremote", text); Chris@0: Chris@0: if (text.isEmpty()) return; Chris@0: Chris@387: FileOpenStatus status = openPath(text, ReplaceSession); Chris@0: Chris@0: if (status == FileOpenFailed) { Chris@0: QMessageBox::critical(this, tr("Failed to open location"), Chris@0: tr("Open failed

URL \"%1\" could not be opened").arg(text)); Chris@0: } else if (status == FileOpenWrongMode) { Chris@0: QMessageBox::critical(this, tr("Failed to open location"), Chris@0: tr("Audio required

Please load at least one audio file before importing annotation data")); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::openRecentFile() Chris@0: { Chris@0: QObject *obj = sender(); Chris@3: QAction *action = qobject_cast(obj); Chris@0: Chris@0: if (!action) { Chris@188: cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" Chris@188: << endl; Chris@188: return; Chris@0: } Chris@0: Chris@0: QString path = action->text(); Chris@0: if (path == "") return; Chris@0: Chris@387: FileOpenStatus status = openPath(path, ReplaceSession); Chris@0: Chris@0: if (status == FileOpenFailed) { Chris@0: QMessageBox::critical(this, tr("Failed to open location"), Chris@0: tr("Open failed

File or URL \"%1\" could not be opened").arg(path)); Chris@0: } else if (status == FileOpenWrongMode) { Chris@0: QMessageBox::critical(this, tr("Failed to open location"), Chris@0: tr("Audio required

Please load at least one audio file before importing annotation data")); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::paneAdded(Pane *pane) Chris@0: { Chris@6: pane->setPlaybackFollow(PlaybackScrollPage); Chris@0: m_paneStack->sizePanesEqually(); Chris@0: if (m_overview) m_overview->registerView(pane); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::paneHidden(Pane *pane) Chris@0: { Chris@0: if (m_overview) m_overview->unregisterView(pane); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::paneAboutToBeDeleted(Pane *pane) Chris@0: { Chris@0: if (m_overview) m_overview->unregisterView(pane); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::paneDropAccepted(Pane *pane, QStringList uriList) Chris@0: { Chris@4: if (pane) m_paneStack->setCurrentPane(pane); Chris@0: Chris@0: for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) { Chris@0: Chris@387: FileOpenStatus status = openPath(*i, ReplaceSession); Chris@0: Chris@0: if (status == FileOpenFailed) { Chris@0: QMessageBox::critical(this, tr("Failed to open dropped URL"), Chris@0: tr("Open failed

Dropped URL \"%1\" could not be opened").arg(*i)); Chris@0: } else if (status == FileOpenWrongMode) { Chris@0: QMessageBox::critical(this, tr("Failed to open dropped URL"), Chris@0: tr("Audio required

Please load at least one audio file before importing annotation data")); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::paneDropAccepted(Pane *pane, QString text) Chris@0: { Chris@0: if (pane) m_paneStack->setCurrentPane(pane); Chris@0: Chris@0: QUrl testUrl(text); Chris@0: if (testUrl.scheme() == "file" || Chris@0: testUrl.scheme() == "http" || Chris@0: testUrl.scheme() == "ftp") { Chris@0: QStringList list; Chris@0: list.push_back(text); Chris@0: paneDropAccepted(pane, list); Chris@0: return; Chris@0: } Chris@0: Chris@0: //!!! open as text -- but by importing as if a CSV, or just adding Chris@0: //to a text layer? Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::closeEvent(QCloseEvent *e) Chris@0: { Chris@70: // cerr << "MainWindow::closeEvent" << endl; Chris@0: Chris@0: if (m_openingAudioFile) { Chris@70: // cerr << "Busy - ignoring close event" << endl; Chris@257: e->ignore(); Chris@257: return; Chris@0: } Chris@0: Chris@688: if (!checkSaveModified()) { Chris@70: // cerr << "Ignoring close event" << endl; Chris@257: e->ignore(); Chris@257: return; Chris@0: } Chris@0: Chris@0: QSettings settings; Chris@0: settings.beginGroup("MainWindow"); Chris@0: settings.setValue("size", size()); Chris@0: settings.setValue("position", pos()); Chris@0: settings.endGroup(); Chris@0: Chris@0: delete m_keyReference; Chris@0: m_keyReference = 0; Chris@0: Chris@0: closeSession(); Chris@0: Chris@0: e->accept(); Chris@0: return; Chris@0: } Chris@0: Chris@0: bool Chris@0: MainWindow::commitData(bool mayAskUser) Chris@0: { Chris@0: if (mayAskUser) { Chris@0: bool rv = checkSaveModified(); Chris@0: return rv; Chris@0: } else { Chris@0: if (!m_documentModified) return true; Chris@0: Chris@0: // If we can't check with the user first, then we can't save Chris@0: // to the original session file (even if we have it) -- have Chris@0: // to use a temporary file Chris@0: Chris@0: QString svDirBase = ".sv1"; Chris@0: QString svDir = QDir::home().filePath(svDirBase); Chris@0: Chris@0: if (!QFileInfo(svDir).exists()) { Chris@0: if (!QDir::home().mkdir(svDirBase)) return false; Chris@0: } else { Chris@0: if (!QFileInfo(svDir).isDir()) return false; Chris@0: } Chris@0: Chris@0: // This name doesn't have to be unguessable Chris@0: #ifndef _WIN32 Chris@0: QString fname = QString("tmp-%1-%2.sv") Chris@0: .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")) Chris@0: .arg(QProcess().pid()); Chris@0: #else Chris@0: QString fname = QString("tmp-%1.sv") Chris@0: .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")); Chris@0: #endif Chris@0: QString fpath = QDir(svDir).filePath(fname); Chris@0: if (saveSessionFile(fpath)) { Chris@0: m_recentFiles.addFile(fpath); Chris@0: return true; Chris@0: } else { Chris@0: return false; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: 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@257: QMessageBox::warning(this, Chris@257: tr("Session modified"), Chris@257: tr("The current session has been modified.\nDo you want to save it?"), Chris@257: QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, Chris@0: QMessageBox::Yes); Chris@0: Chris@0: if (button == QMessageBox::Yes) { Chris@257: saveSession(); Chris@257: if (m_documentModified) { // save failed -- don't proceed! Chris@257: return false; Chris@257: } else { Chris@0: return true; // saved, so it's safe to continue now Chris@0: } Chris@0: } else if (button == QMessageBox::No) { Chris@257: m_documentModified = false; // so we know to abandon it Chris@257: return true; Chris@0: } Chris@0: Chris@0: // else cancel Chris@0: return false; Chris@0: } Chris@0: Chris@314: bool Chris@314: MainWindow::waitForInitialAnalysis() Chris@314: { Chris@314: // Called before saving a session. We can't safely save while the Chris@314: // initial analysis is happening, because then we end up with an Chris@314: // incomplete session on reload. There are certainly theoretically Chris@314: // better ways to handle this... Chris@314: Chris@330: QSettings settings; Chris@330: settings.beginGroup("Analyser"); Chris@330: bool autoAnalyse = settings.value("auto-analysis", true).toBool(); Chris@330: settings.endGroup(); Chris@330: Chris@330: if (!autoAnalyse) { Chris@330: return true; Chris@330: } Chris@330: Chris@314: if (!m_analyser || m_analyser->getInitialAnalysisCompletion() >= 100) { Chris@314: return true; Chris@314: } Chris@314: Chris@314: QMessageBox mb(QMessageBox::Information, Chris@314: tr("Waiting for analysis"), Chris@442: tr("Waiting for initial analysis to finish before loading or saving..."), Chris@314: QMessageBox::Cancel, Chris@314: this); Chris@314: Chris@314: connect(m_analyser, SIGNAL(initialAnalysisCompleted()), Chris@314: &mb, SLOT(accept())); Chris@314: Chris@314: if (mb.exec() == QDialog::Accepted) { Chris@314: return true; Chris@314: } else { Chris@314: return false; Chris@314: } Chris@314: } Chris@314: Chris@0: void Chris@0: MainWindow::saveSession() Chris@0: { Chris@269: // We do not want to save mid-analysis regions -- that would cause Chris@269: // confusion on reloading Chris@269: m_analyser->clearReAnalysis(); Chris@269: clearSelection(); Chris@269: Chris@0: if (m_sessionFile != "") { Chris@257: if (!saveSessionFile(m_sessionFile)) { Chris@257: QMessageBox::critical Chris@257: (this, tr("Failed to save file"), Chris@257: tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); Chris@257: } else { Chris@257: CommandHistory::getInstance()->documentSaved(); Chris@257: documentRestored(); Chris@257: } Chris@0: } else { Chris@257: saveSessionAs(); Chris@0: } Chris@0: } Chris@0: Chris@0: void matthiasm@310: MainWindow::saveSessionInAudioPath() matthiasm@310: { matthiasm@311: if (m_audioFile == "") return; matthiasm@311: Chris@314: if (!waitForInitialAnalysis()) return; Chris@314: matthiasm@310: // We do not want to save mid-analysis regions -- that would cause matthiasm@310: // confusion on reloading matthiasm@310: m_analyser->clearReAnalysis(); matthiasm@310: clearSelection(); matthiasm@310: matthiasm@310: QString filepath = QFileInfo(m_audioFile).absoluteDir().canonicalPath(); matthiasm@310: QString basename = QFileInfo(m_audioFile).completeBaseName(); matthiasm@310: matthiasm@310: QString path = QDir(filepath).filePath(basename + ".ton"); matthiasm@310: matthiasm@310: cerr << path << endl; matthiasm@310: Chris@313: // We don't want to overwrite an existing .ton file unless we put Chris@313: // it there in the first place Chris@313: bool shouldVerify = true; Chris@313: if (m_sessionFile == path) { Chris@313: shouldVerify = false; Chris@313: } Chris@313: Chris@313: if (shouldVerify && QFileInfo(path).exists()) { Chris@313: if (QMessageBox::question(0, tr("File exists"), Chris@313: tr("File exists

The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), Chris@313: QMessageBox::Ok, Chris@313: QMessageBox::Cancel) != QMessageBox::Ok) { Chris@313: return; Chris@313: } Chris@313: } Chris@313: Chris@314: if (!waitForInitialAnalysis()) { Chris@314: QMessageBox::warning(this, tr("File not saved"), Chris@314: tr("Wait cancelled: the session has not been saved.")); Chris@314: } Chris@314: matthiasm@310: if (!saveSessionFile(path)) { matthiasm@310: QMessageBox::critical(this, tr("Failed to save file"), matthiasm@310: tr("Session file \"%1\" could not be saved.").arg(path)); matthiasm@310: } else { matthiasm@310: setWindowTitle(tr("%1: %2") matthiasm@310: .arg(QApplication::applicationName()) matthiasm@310: .arg(QFileInfo(path).fileName())); matthiasm@310: m_sessionFile = path; matthiasm@310: CommandHistory::getInstance()->documentSaved(); matthiasm@310: documentRestored(); matthiasm@310: m_recentFiles.addFile(path); matthiasm@310: } matthiasm@310: } matthiasm@310: matthiasm@310: void Chris@0: MainWindow::saveSessionAs() Chris@0: { Chris@270: // We do not want to save mid-analysis regions -- that would cause Chris@270: // confusion on reloading Chris@270: m_analyser->clearReAnalysis(); Chris@270: clearSelection(); Chris@270: Chris@0: QString path = getSaveFileName(FileFinder::SessionFile); Chris@0: Chris@309: if (path == "") { Chris@309: return; Chris@309: } Chris@0: Chris@314: if (!waitForInitialAnalysis()) { Chris@314: QMessageBox::warning(this, tr("File not saved"), Chris@314: tr("Wait cancelled: the session has not been saved.")); Chris@314: return; Chris@314: } Chris@314: Chris@0: if (!saveSessionFile(path)) { Chris@85: QMessageBox::critical(this, tr("Failed to save file"), Chris@85: tr("Session file \"%1\" could not be saved.").arg(path)); Chris@0: } else { Chris@85: setWindowTitle(tr("%1: %2") Chris@0: .arg(QApplication::applicationName()) Chris@85: .arg(QFileInfo(path).fileName())); Chris@85: m_sessionFile = path; Chris@85: CommandHistory::getInstance()->documentSaved(); Chris@85: documentRestored(); Chris@0: m_recentFiles.addFile(path); Chris@0: } Chris@0: } Chris@0: Chris@85: QString Chris@85: MainWindow::exportToSVL(QString path, Layer *layer) Chris@85: { Chris@573: auto model = ModelById::get(layer->getModel()); Chris@573: if (!model) return "Internal error: No model in layer"; Chris@85: Chris@85: QFile file(path); Chris@85: if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { Chris@85: return tr("Failed to open file %1 for writing").arg(path); Chris@85: } else { Chris@85: QTextStream out(&file); Chris@85: out << "\n" Chris@85: << "\n" Chris@85: << "\n" Chris@85: << " \n"; Chris@85: Chris@85: model->toXml(out, " "); Chris@85: Chris@85: out << " \n" Chris@85: << " \n"; Chris@85: Chris@85: layer->toXml(out, " "); Chris@85: Chris@85: out << " \n" Chris@85: << "\n"; Chris@85: Chris@85: return ""; Chris@85: } Chris@85: } Chris@85: Chris@0: void Chris@174: MainWindow::importPitchLayer() Chris@174: { Chris@174: QString path = getOpenFileName(FileFinder::LayerFileNoMidiNonSV); Chris@174: if (path == "") return; Chris@174: Chris@174: FileOpenStatus status = importPitchLayer(path); Chris@174: Chris@174: if (status == FileOpenFailed) { Chris@174: emit hideSplash(); Chris@174: QMessageBox::critical(this, tr("Failed to open file"), Chris@174: tr("File open failed

Layer file %1 could not be opened.").arg(path)); Chris@174: return; Chris@174: } else if (status == FileOpenWrongMode) { Chris@174: emit hideSplash(); Chris@174: QMessageBox::critical(this, tr("Failed to open file"), Chris@174: tr("Audio required

Unable to load layer data from \"%1\" without an audio file.
Please load at least one audio file before importing annotations.").arg(path)); Chris@174: } Chris@174: } Chris@174: Chris@174: MainWindow::FileOpenStatus Chris@174: MainWindow::importPitchLayer(FileSource source) Chris@174: { Chris@174: if (!source.isAvailable()) return FileOpenFailed; Chris@174: source.waitForData(); Chris@174: Chris@442: if (!waitForInitialAnalysis()) return FileOpenCancelled; Chris@442: Chris@174: QString path = source.getLocalFilename(); Chris@174: Chris@174: RDFImporter::RDFDocumentType rdfType = Chris@174: RDFImporter::identifyDocumentType(QUrl::fromLocalFile(path).toString()); Chris@174: Chris@174: if (rdfType != RDFImporter::NotRDF) { Chris@174: Chris@174: //!!! Chris@174: return FileOpenFailed; Chris@174: Chris@174: } else if (source.getExtension().toLower() == "svl" || Chris@174: (source.getExtension().toLower() == "xml" && Chris@174: (SVFileReader::identifyXmlFile(source.getLocalFilename()) Chris@174: == SVFileReader::SVLayerFile))) { Chris@174: Chris@174: //!!! Chris@174: return FileOpenFailed; Chris@174: Chris@174: } else { Chris@174: Chris@174: try { Chris@174: Chris@174: CSVFormat format(path); Chris@174: format.setSampleRate(getMainModel()->getSampleRate()); Chris@174: Chris@174: if (format.getModelType() != CSVFormat::TwoDimensionalModel) { Chris@174: //!!! error report Chris@174: return FileOpenFailed; Chris@174: } Chris@174: Chris@174: Model *model = DataFileReaderFactory::loadCSV Chris@174: (path, format, getMainModel()->getSampleRate()); Chris@174: Chris@174: if (model) { Chris@174: Chris@174: SVDEBUG << "MainWindow::importPitchLayer: Have model" << endl; Chris@174: Chris@573: ModelId modelId = ModelById::add Chris@573: (std::shared_ptr(model)); Chris@573: Chris@174: CommandHistory::getInstance()->startCompoundOperation Chris@174: (tr("Import Pitch Track"), true); Chris@174: Chris@573: Layer *newLayer = m_document->createImportedLayer(modelId); Chris@174: Chris@174: m_analyser->takePitchTrackFrom(newLayer); Chris@174: Chris@174: m_document->deleteLayer(newLayer); Chris@174: Chris@174: CommandHistory::getInstance()->endCompoundOperation(); Chris@174: Chris@174: if (!source.isRemote()) { Chris@174: registerLastOpenedFilePath Chris@174: (FileFinder::LayerFile, Chris@174: path); // for file dialog Chris@174: } Chris@174: Chris@174: return FileOpenSucceeded; Chris@174: } Chris@174: } catch (DataFileReaderFactory::Exception e) { Chris@174: if (e == DataFileReaderFactory::ImportCancelled) { Chris@174: return FileOpenCancelled; Chris@174: } Chris@174: } Chris@174: } Chris@174: Chris@174: return FileOpenFailed; Chris@174: } Chris@174: Chris@174: void Chris@85: MainWindow::exportPitchLayer() matthiasm@26: { Chris@174: Layer *layer = m_analyser->getLayer(Analyser::PitchTrack); matthiasm@26: if (!layer) return; matthiasm@26: Chris@573: auto model = ModelById::getAs(layer->getModel()); matthiasm@26: if (!model) return; matthiasm@26: Chris@96: FileFinder::FileType type = FileFinder::LayerFileNoMidiNonSV; matthiasm@26: matthiasm@26: QString path = getSaveFileName(type); matthiasm@26: matthiasm@26: if (path == "") return; matthiasm@26: Chris@442: if (!waitForInitialAnalysis()) return; Chris@442: matthiasm@26: if (QFileInfo(path).suffix() == "") path += ".svl"; matthiasm@26: matthiasm@26: QString suffix = QFileInfo(path).suffix().toLower(); matthiasm@26: matthiasm@26: QString error; matthiasm@26: matthiasm@26: if (suffix == "xml" || suffix == "svl") { matthiasm@26: Chris@85: error = exportToSVL(path, layer); matthiasm@26: matthiasm@26: } else if (suffix == "ttl" || suffix == "n3") { matthiasm@26: Chris@573: RDFExporter exporter(path, model.get()); Chris@85: exporter.write(); Chris@85: if (!exporter.isOK()) { Chris@85: error = exporter.getError(); Chris@85: } Chris@85: Chris@85: } else { Chris@85: Chris@429: DataExportOptions options = DataExportFillGaps; Chris@429: Chris@573: CSVFileWriter writer(path, model.get(), Chris@429: ((suffix == "csv") ? "," : "\t"), Chris@429: options); Chris@85: writer.write(); Chris@85: Chris@85: if (!writer.isOK()) { Chris@85: error = writer.getError(); Chris@85: } Chris@85: } Chris@85: Chris@85: if (error != "") { Chris@85: QMessageBox::critical(this, tr("Failed to write file"), error); Chris@85: } else { Chris@85: emit activity(tr("Export layer to \"%1\"").arg(path)); Chris@85: } Chris@85: } Chris@85: Chris@85: void Chris@85: MainWindow::exportNoteLayer() Chris@85: { Chris@174: Layer *layer = m_analyser->getLayer(Analyser::Notes); Chris@85: if (!layer) return; Chris@85: Chris@573: auto model = ModelById::getAs(layer->getModel()); Chris@85: if (!model) return; Chris@85: Chris@96: FileFinder::FileType type = FileFinder::LayerFileNonSV; Chris@85: Chris@85: QString path = getSaveFileName(type); Chris@85: Chris@85: if (path == "") return; Chris@85: Chris@85: if (QFileInfo(path).suffix() == "") path += ".svl"; Chris@85: Chris@85: QString suffix = QFileInfo(path).suffix().toLower(); Chris@85: Chris@85: QString error; Chris@85: Chris@85: if (suffix == "xml" || suffix == "svl") { Chris@85: Chris@85: error = exportToSVL(path, layer); Chris@85: Chris@85: } else if (suffix == "mid" || suffix == "midi") { Chris@85: Chris@573: MIDIFileWriter writer(path, model.get(), model->getSampleRate()); Chris@85: writer.write(); Chris@85: if (!writer.isOK()) { Chris@85: error = writer.getError(); Chris@85: } Chris@85: Chris@85: } else if (suffix == "ttl" || suffix == "n3") { Chris@85: Chris@573: RDFExporter exporter(path, model.get()); Chris@85: exporter.write(); Chris@85: if (!exporter.isOK()) { Chris@85: error = exporter.getError(); matthiasm@26: } matthiasm@26: matthiasm@26: } else { matthiasm@26: Chris@688: DataExportOptions options = DataExportOmitLevel; Chris@429: Chris@573: CSVFileWriter writer(path, model.get(), Chris@429: ((suffix == "csv") ? "," : "\t"), Chris@429: options); matthiasm@26: writer.write(); matthiasm@26: matthiasm@26: if (!writer.isOK()) { matthiasm@26: error = writer.getError(); matthiasm@26: } matthiasm@26: } matthiasm@26: matthiasm@26: if (error != "") { matthiasm@26: QMessageBox::critical(this, tr("Failed to write file"), error); matthiasm@26: } else { matthiasm@26: emit activity(tr("Export layer to \"%1\"").arg(path)); matthiasm@26: } matthiasm@26: } matthiasm@26: Chris@139: void Chris@485: MainWindow::browseRecordedAudio() Chris@485: { Chris@485: if (!m_recordTarget) return; Chris@485: Chris@539: QString path = RecordDirectory::getRecordContainerDirectory(); Chris@539: if (path == "") path = RecordDirectory::getRecordDirectory(); Chris@485: if (path == "") return; Chris@485: Chris@485: openLocalFolder(path); Chris@485: } Chris@485: Chris@485: void Chris@399: MainWindow::doubleClickSelectInvoked(sv_frame_t frame) Chris@139: { Chris@399: sv_frame_t f0, f1; Chris@139: m_analyser->getEnclosingSelectionScope(frame, f0, f1); Chris@139: Chris@139: cerr << "MainWindow::doubleClickSelectInvoked(" << frame << "): [" << f0 << "," << f1 << "]" << endl; Chris@139: Chris@164: Selection sel(f0, f1); Chris@164: m_viewManager->setSelection(sel); Chris@164: } Chris@164: Chris@164: void Chris@194: MainWindow::abandonSelection() Chris@167: { Chris@194: // Named abandonSelection rather than clearSelection to indicate Chris@194: // that this is an active operation -- it restores the original Chris@194: // content of the pitch track in the selected region rather than Chris@194: // simply un-selecting. Chris@194: Chris@194: cerr << "MainWindow::abandonSelection()" << endl; Chris@167: Chris@199: CommandHistory::getInstance()->startCompoundOperation(tr("Abandon Selection"), true); Chris@168: Chris@194: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@194: if (!selections.empty()) { Chris@194: Selection sel = *selections.begin(); Chris@199: m_analyser->abandonReAnalysis(sel); Chris@252: auxSnapNotes(sel); Chris@194: } Chris@194: Chris@167: MainWindowBase::clearSelection(); Chris@168: Chris@168: CommandHistory::getInstance()->endCompoundOperation(); Chris@167: } Chris@167: Chris@167: void Chris@192: MainWindow::selectionChangedByUser() Chris@164: { Chris@221: if (!m_document) { Chris@221: // we're exiting, most likely Chris@221: return; Chris@221: } Chris@221: Chris@164: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@164: Chris@192: cerr << "MainWindow::selectionChangedByUser" << endl; Chris@192: Chris@192: m_analyser->showPitchCandidates(m_pendingConstraint.isConstrained()); Chris@190: Chris@164: if (!selections.empty()) { Chris@164: Selection sel = *selections.begin(); Chris@192: cerr << "MainWindow::selectionChangedByUser: have selection" << endl; Chris@192: QString error = m_analyser->reAnalyseSelection Chris@192: (sel, m_pendingConstraint); Chris@164: if (error != "") { Chris@164: QMessageBox::critical Chris@164: (this, tr("Failed to analyse selection"), Chris@164: tr("Analysis failed

%2

").arg(error)); Chris@164: } Chris@164: } Chris@192: Chris@192: m_pendingConstraint = Analyser::FrequencyRange(); Chris@192: } Chris@192: Chris@192: void Chris@192: MainWindow::regionOutlined(QRect r) Chris@192: { Chris@192: cerr << "MainWindow::regionOutlined(" << r.x() << "," << r.y() << "," << r.width() << "," << r.height() << ")" << endl; Chris@192: Chris@192: Pane *pane = qobject_cast(sender()); Chris@192: if (!pane) { Chris@192: cerr << "MainWindow::regionOutlined: not sent by pane, ignoring" << endl; Chris@192: return; Chris@192: } Chris@192: Chris@192: if (!m_analyser) { Chris@192: cerr << "MainWindow::regionOutlined: no analyser, ignoring" << endl; Chris@192: return; Chris@192: } Chris@192: Chris@192: SpectrogramLayer *spectrogram = qobject_cast Chris@192: (m_analyser->getLayer(Analyser::Spectrogram)); Chris@192: if (!spectrogram) { Chris@192: cerr << "MainWindow::regionOutlined: no spectrogram layer, ignoring" << endl; Chris@192: return; Chris@192: } Chris@192: Chris@399: sv_frame_t f0 = pane->getFrameForX(r.x()); Chris@399: sv_frame_t f1 = pane->getFrameForX(r.x() + r.width()); Chris@192: Chris@399: double v0 = spectrogram->getFrequencyForY(pane, r.y() + r.height()); Chris@399: double v1 = spectrogram->getFrequencyForY(pane, r.y()); Chris@192: Chris@192: cerr << "MainWindow::regionOutlined: frame " << f0 << " -> " << f1 Chris@192: << ", frequency " << v0 << " -> " << v1 << endl; Chris@192: Chris@192: m_pendingConstraint = Analyser::FrequencyRange(v0, v1); Chris@192: Chris@192: Selection sel(f0, f1); Chris@192: m_viewManager->setSelection(sel); Chris@0: } Chris@0: Chris@0: void Chris@168: MainWindow::clearPitches() Chris@168: { Chris@168: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@168: Chris@168: CommandHistory::getInstance()->startCompoundOperation(tr("Clear Pitches"), true); Chris@168: Chris@168: for (MultiSelection::SelectionList::iterator k = selections.begin(); Chris@168: k != selections.end(); ++k) { Chris@184: m_analyser->deletePitches(*k); Chris@252: auxSnapNotes(*k); Chris@168: } Chris@168: Chris@168: CommandHistory::getInstance()->endCompoundOperation(); Chris@168: } Chris@168: Chris@168: void Chris@142: MainWindow::octaveShift(bool up) Chris@142: { Chris@142: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@142: Chris@211: CommandHistory::getInstance()->startCompoundOperation Chris@211: (up ? tr("Choose Higher Octave") : tr("Choose Lower Octave"), true); Chris@142: Chris@168: for (MultiSelection::SelectionList::iterator k = selections.begin(); Chris@168: k != selections.end(); ++k) { Chris@142: Chris@168: m_analyser->shiftOctave(*k, up); Chris@252: auxSnapNotes(*k); Chris@142: } Chris@142: Chris@142: CommandHistory::getInstance()->endCompoundOperation(); Chris@142: } Chris@142: Chris@142: void Chris@184: MainWindow::togglePitchCandidates() Chris@184: { Chris@199: CommandHistory::getInstance()->startCompoundOperation(tr("Toggle Pitch Candidates"), true); Chris@199: Chris@184: m_analyser->showPitchCandidates(!m_analyser->arePitchCandidatesShown()); Chris@199: Chris@199: CommandHistory::getInstance()->endCompoundOperation(); Chris@199: Chris@187: updateMenuStates(); Chris@184: } Chris@184: Chris@184: void Chris@184: MainWindow::switchPitchUp() Chris@167: { Chris@211: if (m_analyser->arePitchCandidatesShown()) { Chris@211: if (m_analyser->haveHigherPitchCandidate()) { Chris@211: Chris@211: CommandHistory::getInstance()->startCompoundOperation Chris@211: (tr("Choose Higher Pitch Candidate"), true); Chris@211: Chris@211: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@211: Chris@211: for (MultiSelection::SelectionList::iterator k = selections.begin(); Chris@211: k != selections.end(); ++k) { Chris@211: m_analyser->switchPitchCandidate(*k, true); Chris@252: auxSnapNotes(*k); Chris@211: } Chris@211: Chris@211: CommandHistory::getInstance()->endCompoundOperation(); Chris@211: } Chris@211: } else { Chris@211: octaveShift(true); Chris@167: } Chris@167: } Chris@167: Chris@167: void Chris@184: MainWindow::switchPitchDown() Chris@184: { Chris@211: if (m_analyser->arePitchCandidatesShown()) { Chris@211: if (m_analyser->haveLowerPitchCandidate()) { Chris@211: Chris@211: CommandHistory::getInstance()->startCompoundOperation Chris@211: (tr("Choose Lower Pitch Candidate"), true); Chris@211: Chris@211: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@211: Chris@211: for (MultiSelection::SelectionList::iterator k = selections.begin(); Chris@211: k != selections.end(); ++k) { Chris@211: m_analyser->switchPitchCandidate(*k, false); Chris@252: auxSnapNotes(*k); Chris@211: } Chris@211: Chris@211: CommandHistory::getInstance()->endCompoundOperation(); Chris@211: } Chris@211: } else { Chris@211: octaveShift(false); Chris@184: } Chris@184: } Chris@184: Chris@184: void Chris@237: MainWindow::snapNotesToPitches() Chris@237: { matthiasm@274: cerr << "in snapNotesToPitches" << endl; Chris@237: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@237: Chris@237: if (!selections.empty()) { Chris@237: Chris@237: CommandHistory::getInstance()->startCompoundOperation Chris@237: (tr("Snap Notes to Pitches"), true); Chris@237: Chris@237: for (MultiSelection::SelectionList::iterator k = selections.begin(); Chris@237: k != selections.end(); ++k) { Chris@239: auxSnapNotes(*k); Chris@237: } Chris@237: Chris@237: CommandHistory::getInstance()->endCompoundOperation(); Chris@237: } Chris@237: } Chris@237: Chris@237: void Chris@239: MainWindow::auxSnapNotes(Selection s) Chris@239: { matthiasm@274: cerr << "in auxSnapNotes" << endl; Chris@239: FlexiNoteLayer *layer = Chris@239: qobject_cast(m_analyser->getLayer(Analyser::Notes)); Chris@239: if (!layer) return; Chris@239: Chris@239: layer->snapSelectedNotesToPitchTrack(m_analyser->getPane(), s); Chris@239: } Chris@239: Chris@239: void Chris@240: MainWindow::splitNote() Chris@237: { Chris@237: FlexiNoteLayer *layer = Chris@237: qobject_cast(m_analyser->getLayer(Analyser::Notes)); Chris@237: if (!layer) return; Chris@237: Chris@240: layer->splitNotesAt(m_analyser->getPane(), m_viewManager->getPlaybackFrame()); Chris@237: } Chris@237: Chris@237: void Chris@238: MainWindow::mergeNotes() Chris@238: { Chris@238: FlexiNoteLayer *layer = Chris@238: qobject_cast(m_analyser->getLayer(Analyser::Notes)); Chris@238: if (!layer) return; Chris@238: Chris@238: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@238: Chris@238: if (!selections.empty()) { Chris@238: Chris@238: CommandHistory::getInstance()->startCompoundOperation Chris@238: (tr("Merge Notes"), true); Chris@238: Chris@238: for (MultiSelection::SelectionList::iterator k = selections.begin(); Chris@238: k != selections.end(); ++k) { Chris@240: layer->mergeNotes(m_analyser->getPane(), *k, true); Chris@238: } Chris@238: Chris@238: CommandHistory::getInstance()->endCompoundOperation(); Chris@238: } Chris@238: } Chris@238: Chris@238: void matthiasm@292: MainWindow::deleteNotes() matthiasm@292: { matthiasm@292: FlexiNoteLayer *layer = matthiasm@292: qobject_cast(m_analyser->getLayer(Analyser::Notes)); matthiasm@292: if (!layer) return; matthiasm@292: matthiasm@292: MultiSelection::SelectionList selections = m_viewManager->getSelections(); matthiasm@292: matthiasm@292: if (!selections.empty()) { matthiasm@292: matthiasm@292: CommandHistory::getInstance()->startCompoundOperation matthiasm@292: (tr("Delete Notes"), true); matthiasm@292: matthiasm@292: for (MultiSelection::SelectionList::iterator k = selections.begin(); matthiasm@292: k != selections.end(); ++k) { matthiasm@294: layer->deleteSelectionInclusive(*k); matthiasm@292: } matthiasm@292: matthiasm@292: CommandHistory::getInstance()->endCompoundOperation(); matthiasm@292: } matthiasm@292: } matthiasm@292: matthiasm@292: matthiasm@292: void Chris@240: MainWindow::formNoteFromSelection() Chris@240: { Chris@571: Pane *pane = m_analyser->getPane(); matthiasm@293: Layer *layer0 = m_analyser->getLayer(Analyser::Notes); Chris@573: auto model = ModelById::getAs(layer0->getModel()); Chris@571: FlexiNoteLayer *layer = qobject_cast(layer0); Chris@573: if (!layer || !model) return; Chris@240: Chris@240: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@240: Chris@240: if (!selections.empty()) { Chris@240: Chris@240: CommandHistory::getInstance()->startCompoundOperation Chris@240: (tr("Form Note from Selection"), true); Chris@571: Chris@240: for (MultiSelection::SelectionList::iterator k = selections.begin(); Chris@240: k != selections.end(); ++k) { Chris@571: Chris@571: // Chop existing events at start and end frames; remember Chris@571: // the first starting pitch, to use as default for new Chris@571: // note; delete existing events; create new note; ask Chris@571: // layer to merge, just in order to adapt the note to the Chris@571: // existing pitch track if possible. This way we should Chris@571: // handle all the possible cases of existing notes that Chris@571: // may or may not overlap the start or end times Chris@571: Chris@571: sv_frame_t start = k->getStartFrame(); Chris@571: sv_frame_t end = k->getEndFrame(); Chris@571: Chris@571: EventVector existing = Chris@571: model->getEventsStartingWithin(start, end - start); Chris@571: Chris@571: int defaultPitch = 100; Chris@571: if (!existing.empty()) { Chris@571: defaultPitch = int(roundf(existing.begin()->getValue())); matthiasm@293: } Chris@571: Chris@571: layer->splitNotesAt(pane, start); Chris@571: layer->splitNotesAt(pane, end); Chris@571: layer->deleteSelection(*k); Chris@571: Chris@571: layer->addNoteOn(start, defaultPitch, 100); Chris@571: layer->addNoteOff(end, defaultPitch); Chris@571: Chris@571: layer->mergeNotes(pane, *k, false); Chris@240: } Chris@240: matthiasm@293: CommandHistory::getInstance()->endCompoundOperation(); Chris@240: } Chris@240: } Chris@240: Chris@240: void Chris@0: MainWindow::playSpeedChanged(int position) Chris@0: { Chris@474: PlaySpeedRangeMapper mapper; Chris@0: Chris@399: double percent = m_playSpeed->mappedValue(); Chris@399: double factor = mapper.getFactorForValue(percent); Chris@0: Chris@474: int centre = m_playSpeed->defaultValue(); Chris@474: Chris@474: // Percentage is shown to 0dp if >100, to 1dp if <100; factor is Chris@474: // shown to 3sf Chris@474: Chris@474: char pcbuf[30]; Chris@474: char facbuf[30]; Chris@474: Chris@474: if (position == centre) { Chris@0: contextHelpChanged(tr("Playback speed: Normal")); Chris@474: } else if (position < centre) { Chris@474: sprintf(pcbuf, "%.1f", percent); Chris@474: sprintf(facbuf, "%.3g", 1.0 / factor); Chris@474: contextHelpChanged(tr("Playback speed: %1% (%2x slower)") Chris@474: .arg(pcbuf) Chris@474: .arg(facbuf)); Chris@0: } else { Chris@474: sprintf(pcbuf, "%.0f", percent); Chris@474: sprintf(facbuf, "%.3g", factor); Chris@474: contextHelpChanged(tr("Playback speed: %1% (%2x faster)") Chris@474: .arg(pcbuf) Chris@474: .arg(facbuf)); Chris@0: } Chris@0: Chris@474: m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup Chris@0: Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::playSharpenToggled() Chris@0: { Chris@0: QSettings settings; Chris@0: settings.beginGroup("MainWindow"); Chris@0: settings.setValue("playsharpen", m_playSharpen->isChecked()); Chris@0: settings.endGroup(); Chris@0: Chris@0: playSpeedChanged(m_playSpeed->value()); justin@157: // TODO: pitch gain? Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::playMonoToggled() Chris@0: { Chris@0: QSettings settings; Chris@0: settings.beginGroup("MainWindow"); Chris@0: settings.setValue("playmono", m_playMono->isChecked()); Chris@0: settings.endGroup(); Chris@0: Chris@0: playSpeedChanged(m_playSpeed->value()); justin@157: // TODO: pitch gain? Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::speedUpPlayback() Chris@0: { Chris@0: int value = m_playSpeed->value(); Chris@0: value = value + m_playSpeed->pageStep(); Chris@0: if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum(); Chris@0: m_playSpeed->setValue(value); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::slowDownPlayback() Chris@0: { Chris@0: int value = m_playSpeed->value(); Chris@0: value = value - m_playSpeed->pageStep(); Chris@0: if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum(); Chris@0: m_playSpeed->setValue(value); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::restoreNormalPlayback() Chris@0: { Chris@0: m_playSpeed->setValue(m_playSpeed->defaultValue()); Chris@0: } Chris@0: justin@157: void Chris@404: MainWindow::audioGainChanged(float gain) justin@159: { Chris@404: double db = AudioLevel::multiplier_to_dB(gain); Chris@404: cerr << "gain = " << gain << " (" << db << " dB)" << endl; Chris@404: contextHelpChanged(tr("Audio Gain: %1 dB").arg(db)); Chris@417: if (gain == 0.f) { Chris@417: m_analyser->setAudible(Analyser::Audio, false); Chris@417: } else { Chris@417: m_analyser->setAudible(Analyser::Audio, true); Chris@417: m_analyser->setGain(Analyser::Audio, gain); Chris@417: } justin@159: updateMenuStates(); justin@159: } justin@159: justin@159: void Chris@404: MainWindow::pitchGainChanged(float gain) justin@159: { Chris@404: double db = AudioLevel::multiplier_to_dB(gain); Chris@404: cerr << "gain = " << gain << " (" << db << " dB)" << endl; Chris@404: contextHelpChanged(tr("Pitch Gain: %1 dB").arg(db)); Chris@417: if (gain == 0.f) { Chris@417: m_analyser->setAudible(Analyser::PitchTrack, false); Chris@417: } else { Chris@417: m_analyser->setAudible(Analyser::PitchTrack, true); Chris@417: m_analyser->setGain(Analyser::PitchTrack, gain); Chris@417: } justin@157: updateMenuStates(); justin@157: } justin@157: justin@157: void Chris@404: MainWindow::notesGainChanged(float gain) justin@157: { Chris@404: double db = AudioLevel::multiplier_to_dB(gain); Chris@404: cerr << "gain = " << gain << " (" << db << " dB)" << endl; Chris@404: contextHelpChanged(tr("Notes Gain: %1 dB").arg(db)); Chris@417: if (gain == 0.f) { Chris@417: m_analyser->setAudible(Analyser::Notes, false); Chris@417: } else { Chris@417: m_analyser->setAudible(Analyser::Notes, true); Chris@417: m_analyser->setGain(Analyser::Notes, gain); Chris@417: } justin@159: updateMenuStates(); justin@159: } justin@159: justin@159: void Chris@404: MainWindow::audioPanChanged(float pan) justin@159: { Chris@404: contextHelpChanged(tr("Audio Pan: %1").arg(pan)); Chris@404: m_analyser->setPan(Analyser::Audio, pan); justin@160: updateMenuStates(); justin@160: } justin@160: justin@160: void Chris@404: MainWindow::pitchPanChanged(float pan) justin@160: { Chris@404: contextHelpChanged(tr("Pitch Pan: %1").arg(pan)); Chris@404: m_analyser->setPan(Analyser::PitchTrack, pan); justin@160: updateMenuStates(); justin@160: } justin@160: justin@160: void Chris@404: MainWindow::notesPanChanged(float pan) justin@160: { Chris@404: contextHelpChanged(tr("Notes Pan: %1").arg(pan)); Chris@404: m_analyser->setPan(Analyser::Notes, pan); justin@160: updateMenuStates(); justin@160: } justin@160: justin@160: void Chris@0: MainWindow::updateVisibleRangeDisplay(Pane *p) const Chris@0: { Chris@0: if (!getMainModel() || !p) { Chris@0: return; Chris@0: } Chris@0: Chris@0: bool haveSelection = false; Chris@403: sv_frame_t startFrame = 0, endFrame = 0; Chris@0: Chris@0: if (m_viewManager && m_viewManager->haveInProgressSelection()) { Chris@0: Chris@0: bool exclusive = false; Chris@0: Selection s = m_viewManager->getInProgressSelection(exclusive); Chris@0: Chris@0: if (!s.isEmpty()) { Chris@0: haveSelection = true; Chris@0: startFrame = s.getStartFrame(); Chris@0: endFrame = s.getEndFrame(); Chris@0: } Chris@0: } Chris@0: Chris@0: if (!haveSelection) { Chris@0: startFrame = p->getFirstVisibleFrame(); Chris@0: endFrame = p->getLastVisibleFrame(); Chris@0: } Chris@0: Chris@0: RealTime start = RealTime::frame2RealTime Chris@0: (startFrame, getMainModel()->getSampleRate()); Chris@0: Chris@0: RealTime end = RealTime::frame2RealTime Chris@0: (endFrame, getMainModel()->getSampleRate()); Chris@0: Chris@0: RealTime duration = end - start; Chris@0: Chris@0: QString startStr, endStr, durationStr; Chris@0: startStr = start.toText(true).c_str(); Chris@0: endStr = end.toText(true).c_str(); Chris@0: durationStr = duration.toText(true).c_str(); Chris@0: Chris@0: if (haveSelection) { Chris@0: m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)") Chris@0: .arg(startStr).arg(endStr).arg(durationStr); Chris@0: } else { Chris@0: m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)") Chris@0: .arg(startStr).arg(endStr).arg(durationStr); Chris@0: } matthiasm@42: Chris@362: getStatusLabel()->setText(m_myStatusMessage); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::updatePositionStatusDisplays() const Chris@0: { Chris@0: if (!statusBar()->isVisible()) return; Chris@0: Chris@0: } Chris@0: Chris@0: void Chris@521: MainWindow::monitoringLevelsChanged(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@399: MainWindow::sampleRateMismatch(sv_samplerate_t , Chris@399: sv_samplerate_t , Chris@399: bool ) Chris@0: { Chris@0: updateDescriptionLabel(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::audioOverloadPluginDisabled() Chris@0: { Chris@0: QMessageBox::information Chris@0: (this, tr("Audio processing overload"), Chris@0: tr("Overloaded

Audio effects plugin auditioning has been disabled due to a processing overload.")); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::layerRemoved(Layer *layer) Chris@0: { Chris@0: MainWindowBase::layerRemoved(layer); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::layerInAView(Layer *layer, bool inAView) Chris@0: { Chris@0: MainWindowBase::layerInAView(layer, inAView); Chris@0: } Chris@0: Chris@0: void Chris@573: MainWindow::modelAdded(ModelId model) Chris@0: { Chris@0: MainWindowBase::modelAdded(model); Chris@573: auto dtvm = ModelById::getAs(model); Chris@0: if (dtvm) { Chris@70: cerr << "A dense time-value model (such as an audio file) has been loaded" << endl; Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@573: MainWindow::mainModelChanged(ModelId model) Chris@0: { Chris@0: m_panLayer->setModel(model); Chris@0: Chris@0: MainWindowBase::mainModelChanged(model); Chris@0: Chris@479: if (m_playTarget || m_audioIO) { Chris@0: connect(m_fader, SIGNAL(valueChanged(float)), Chris@474: this, SLOT(mainModelGainChanged(float))); Chris@474: } Chris@474: } Chris@474: Chris@474: void Chris@474: MainWindow::mainModelGainChanged(float gain) Chris@474: { Chris@474: if (m_playTarget) { Chris@474: m_playTarget->setOutputGain(gain); Chris@479: } else if (m_audioIO) { Chris@479: m_audioIO->setOutputGain(gain); Chris@0: } Chris@259: } Chris@259: Chris@259: void Chris@323: MainWindow::analyseNow() Chris@323: { Chris@323: cerr << "analyseNow called" << endl; Chris@325: if (!m_analyser) return; Chris@325: Chris@325: CommandHistory::getInstance()->startCompoundOperation Chris@325: (tr("Analyse Audio"), true); Chris@325: Chris@325: QString error = m_analyser->analyseExistingFile(); Chris@325: Chris@325: CommandHistory::getInstance()->endCompoundOperation(); Chris@325: Chris@325: if (error != "") { Chris@325: QMessageBox::warning Chris@325: (this, Chris@325: tr("Failed to analyse audio"), Chris@325: tr("Analysis failed

%1

").arg(error), Chris@325: QMessageBox::Ok); Chris@325: } Chris@323: } Chris@323: Chris@323: void Chris@259: MainWindow::analyseNewMainModel() Chris@259: { Chris@573: auto model = getMainModel(); Chris@260: Chris@604: SVDEBUG << "MainWindow::analyseNewMainModel: main model is " << model << endl; Chris@604: Chris@604: SVDEBUG << "(document is " << m_document << ", it says main model is " << m_document->getMainModel() << ")" << endl; Chris@259: Chris@324: if (!model) { Chris@324: cerr << "no main model!" << endl; Chris@324: return; Chris@324: } Chris@324: Chris@324: if (!m_paneStack) { Chris@324: cerr << "no pane stack!" << endl; Chris@324: return; Chris@324: } Chris@324: Chris@324: int pc = m_paneStack->getPaneCount(); Chris@324: Pane *pane = 0; Chris@324: Pane *selectionStrip = 0; Chris@324: Chris@324: if (pc < 2) { Chris@604: SVDEBUG << "MainWindow::analyseNewMainModel: Adding pane and selection strip (ruler)" << endl; Chris@604: pane = m_paneStack->addPane(); Chris@604: selectionStrip = m_paneStack->addPane(); Chris@324: m_document->addLayerToView Chris@324: (selectionStrip, Chris@324: m_document->createMainModelLayer(LayerFactory::TimeRuler)); Chris@324: } else { Chris@324: pane = m_paneStack->getPane(0); Chris@324: selectionStrip = m_paneStack->getPane(1); Chris@324: } Chris@324: Chris@362: pane->setPlaybackFollow(PlaybackScrollPage); Chris@362: Chris@324: if (selectionStrip) { Chris@362: selectionStrip->setPlaybackFollow(PlaybackScrollPage); Chris@324: selectionStrip->setFixedHeight(26); Chris@324: m_paneStack->sizePanesEqually(); Chris@324: m_viewManager->clearToolModeOverrides(); Chris@324: m_viewManager->setToolModeFor(selectionStrip, Chris@324: ViewManager::SelectMode); Chris@324: } Chris@324: Chris@324: if (pane) { Chris@324: Chris@324: disconnect(pane, SIGNAL(regionOutlined(QRect)), Chris@324: pane, SLOT(zoomToRegion(QRect))); Chris@324: connect(pane, SIGNAL(regionOutlined(QRect)), Chris@324: this, SLOT(regionOutlined(QRect))); Chris@324: Chris@324: QString error = m_analyser->newFileLoaded Chris@573: (m_document, getMainModelId(), m_paneStack, pane); Chris@324: if (error != "") { Chris@324: QMessageBox::warning Chris@324: (this, Chris@324: tr("Failed to analyse audio"), Chris@324: tr("Analysis failed

%1

").arg(error), Chris@324: QMessageBox::Ok); Chris@72: } Chris@72: } matthiasm@366: Chris@573: if (!m_withSpectrogram) { matthiasm@366: m_analyser->setVisible(Analyser::Spectrogram, false); matthiasm@366: } matthiasm@366: Chris@573: if (!m_withSonification) { matthiasm@366: m_analyser->setAudible(Analyser::PitchTrack, false); matthiasm@366: m_analyser->setAudible(Analyser::Notes, false); matthiasm@366: } Chris@419: Chris@419: updateLayerStatuses(); Chris@487: documentRestored(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::modelGenerationFailed(QString transformName, QString message) Chris@0: { Chris@0: if (message != "") { Chris@0: Chris@0: QMessageBox::warning Chris@0: (this, Chris@0: tr("Failed to generate layer"), Chris@0: tr("Layer generation failed

Failed to generate derived layer.

The layer transform \"%1\" failed:

%2") Chris@0: .arg(transformName).arg(message), Chris@0: QMessageBox::Ok); Chris@0: } else { Chris@0: QMessageBox::warning Chris@0: (this, Chris@0: tr("Failed to generate layer"), Chris@0: tr("Layer generation failed

Failed to generate a derived layer.

The layer transform \"%1\" failed.

No error information is available.") Chris@0: .arg(transformName), Chris@0: QMessageBox::Ok); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@355: MainWindow::modelGenerationWarning(QString /* transformName */, QString message) Chris@0: { Chris@0: QMessageBox::warning Chris@0: (this, tr("Warning"), message, QMessageBox::Ok); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::modelRegenerationFailed(QString layerName, Chris@399: QString transformName, Chris@399: QString message) Chris@0: { Chris@0: if (message != "") { Chris@0: Chris@0: QMessageBox::warning Chris@0: (this, Chris@0: tr("Failed to regenerate layer"), Chris@0: tr("Layer generation failed

Failed to regenerate derived layer \"%1\" using new data model as input.

The layer transform \"%2\" failed:

%3") Chris@0: .arg(layerName).arg(transformName).arg(message), Chris@0: QMessageBox::Ok); Chris@0: } else { Chris@0: QMessageBox::warning Chris@0: (this, Chris@0: tr("Failed to regenerate layer"), Chris@0: tr("Layer generation failed

Failed to regenerate derived layer \"%1\" using new data model as input.

The layer transform \"%2\" failed.

No error information is available.") Chris@0: .arg(layerName).arg(transformName), Chris@0: QMessageBox::Ok); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::modelRegenerationWarning(QString layerName, Chris@355: QString /* transformName */, Chris@355: QString message) Chris@0: { Chris@0: QMessageBox::warning Chris@0: (this, tr("Warning"), tr("Warning when regenerating layer

When regenerating the derived layer \"%1\" using new data model as input:

%2").arg(layerName).arg(message), QMessageBox::Ok); Chris@0: } Chris@0: Chris@0: void Chris@697: MainWindow::alignmentFailed(ModelId, QString message) Chris@0: { Chris@0: QMessageBox::warning Chris@0: (this, Chris@0: tr("Failed to calculate alignment"), Chris@520: tr("Alignment calculation failed

Failed to calculate an audio alignment:

%1") Chris@520: .arg(message), Chris@0: QMessageBox::Ok); Chris@0: } Chris@0: Chris@0: void Chris@688: MainWindow::paneRightButtonMenuRequested(Pane *pane, QPoint position) Chris@0: { Chris@70: // cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << endl; Chris@0: m_paneStack->setCurrentPane(pane); Chris@0: m_rightButtonMenu->popup(position); Chris@0: } Chris@0: Chris@0: void Chris@688: MainWindow::panePropertiesRightButtonMenuRequested(Pane *, QPoint) Chris@688: { Chris@688: } Chris@688: Chris@688: void Chris@688: MainWindow::layerPropertiesRightButtonMenuRequested(Pane *, Layer *, QPoint) Chris@688: { Chris@688: } Chris@688: Chris@688: void Chris@355: MainWindow::handleOSCMessage(const OSCMessage &) Chris@0: { Chris@70: cerr << "MainWindow::handleOSCMessage: Not implemented" << endl; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::mouseEnteredWidget() Chris@0: { Chris@3: QWidget *w = qobject_cast(sender()); Chris@0: if (!w) return; Chris@0: Chris@0: if (w == m_fader) { Chris@0: contextHelpChanged(tr("Adjust the master playback level")); Chris@0: } else if (w == m_playSpeed) { Chris@0: contextHelpChanged(tr("Adjust the master playback speed")); Chris@0: } else if (w == m_playSharpen && w->isEnabled()) { Chris@0: contextHelpChanged(tr("Toggle transient sharpening for playback time scaling")); Chris@0: } else if (w == m_playMono && w->isEnabled()) { Chris@0: contextHelpChanged(tr("Toggle mono mode for playback time scaling")); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::mouseLeftWidget() Chris@0: { Chris@0: contextHelpChanged(""); Chris@0: } Chris@0: Chris@0: void Chris@609: MainWindow::betaReleaseWarning() Chris@609: { Chris@609: QMessageBox::information Chris@609: (this, tr("Beta release"), Chris@609: tr("This is a beta release of %1

Please see the \"What's New\" option in the Help menu for a list of changes since the last proper release.

").arg(QApplication::applicationName())); Chris@609: } Chris@609: Chris@609: void Chris@0: MainWindow::help() Chris@0: { Chris@4: //!!! todo: help URL! matthiasm@369: openHelpUrl(tr("http://code.soundsoftware.ac.uk/projects/tony/wiki/Reference")); Chris@0: } Chris@0: Chris@0: void Chris@585: MainWindow::whatsNew() Chris@585: { Chris@585: QFile changelog(":CHANGELOG"); Chris@585: changelog.open(QFile::ReadOnly); Chris@585: QByteArray content = changelog.readAll(); Chris@585: QString text = QString::fromUtf8(content); Chris@585: Chris@585: QDialog *d = new QDialog(this); Chris@585: d->setWindowTitle(tr("What's New")); Chris@585: Chris@585: QGridLayout *layout = new QGridLayout; Chris@585: d->setLayout(layout); Chris@585: Chris@585: int row = 0; Chris@585: Chris@585: QLabel *iconLabel = new QLabel; Chris@585: iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64)); Chris@585: layout->addWidget(iconLabel, row, 0); Chris@585: Chris@585: layout->addWidget Chris@585: (new QLabel(tr("

What's New in %1

") Chris@585: .arg(QApplication::applicationName())), Chris@585: row++, 1); Chris@585: layout->setColumnStretch(2, 10); Chris@585: Chris@585: QTextEdit *textEdit = new QTextEdit; Chris@585: layout->addWidget(textEdit, row++, 1, 1, 2); Chris@585: Chris@585: if (m_newerVersionIs != "") { Chris@585: layout->addWidget(new QLabel(tr("Note: A newer version of %1 is available.
(Version %2 is available; you are using version %3)").arg(QApplication::applicationName()).arg(m_newerVersionIs).arg(TONY_VERSION)), row++, 1, 1, 2); Chris@585: } Chris@585: Chris@585: QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); Chris@585: layout->addWidget(bb, row++, 0, 1, 3); Chris@585: connect(bb, SIGNAL(accepted()), d, SLOT(accept())); Chris@585: Chris@585: text.replace('\r', ""); Chris@585: text.replace(QRegExp("(.)\n +(.)"), "\\1 \\2"); Chris@585: text.replace(QRegExp("\n - ([^\n]+)"), "\n
  • \\1
  • "); Chris@585: text.replace(QRegExp(": *\n"), ":\n
      \n"); Chris@585: text.replace(QRegExp("\n\\s*\n"), "\n
    \n\n"); Chris@585: text.replace(QRegExp("\n(\\w[^:\n]+:)"), "\n

    \\1

    "); Chris@585: // text.replace(QRegExp("
  • ([^,.\n]+)([,.] +\\w)"), "
  • \\1\\2"); Chris@585: Chris@585: textEdit->setHtml(text); Chris@585: textEdit->setReadOnly(true); Chris@585: Chris@585: d->setMinimumSize(m_viewManager->scalePixelSize(520), Chris@585: m_viewManager->scalePixelSize(450)); Chris@585: Chris@585: d->exec(); Chris@585: Chris@585: delete d; Chris@585: } Chris@585: Chris@598: QString Chris@598: MainWindow::getReleaseText() const Chris@598: { Chris@598: bool debug = false; Chris@598: QString version = "(unknown version)"; Chris@598: Chris@598: #ifdef BUILD_DEBUG Chris@598: debug = true; Chris@598: #endif // BUILD_DEBUG Chris@598: #ifdef TONY_VERSION Chris@598: #ifdef SVNREV Chris@598: version = tr("Release %1 : Revision %2").arg(TONY_VERSION).arg(SVNREV); Chris@598: #else // !SVNREV Chris@598: version = tr("Release %1").arg(TONY_VERSION); Chris@598: #endif // SVNREV Chris@598: #else // !TONY_VERSION Chris@598: #ifdef SVNREV Chris@598: version = tr("Unreleased : Revision %1").arg(SVNREV); Chris@598: #endif // SVNREV Chris@598: #endif // TONY_VERSION Chris@598: Chris@598: return tr("%1 : %2 configuration, %3-bit build") Chris@598: .arg(version) Chris@598: .arg(debug ? tr("Debug") : tr("Release")) Chris@598: .arg(sizeof(void *) * 8); Chris@598: } Chris@598: Chris@585: void Chris@0: MainWindow::about() Chris@0: { Chris@0: QString aboutText; Chris@0: Chris@0: aboutText += tr("

    About Tony

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

    Tony is a program for interactive note and pitch analysis and annotation.

    "); Chris@598: aboutText += QString("

    %1

    ").arg(getReleaseText()); Chris@212: aboutText += tr("

    Using Qt framework version %1.

    ") Chris@206: .arg(QT_VERSION_STR); Chris@0: Chris@0: aboutText += Chris@585: "

    Copyright © 2005–2019 Chris Cannam, Queen Mary University of London, and the Tony project authors: Matthias Mauch, George Fazekas, Justin Salamon, and Rachel Bittner.

    " Chris@212: "

    pYIN analysis plugin written by Matthias Mauch.

    " Chris@90: "

    This program is free software; you can redistribute it and/or " Chris@90: "modify it under the terms of the GNU General Public License as " Chris@90: "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@585: // use our own dialog so we can influence the size Chris@585: Chris@585: QDialog *d = new QDialog(this); Chris@585: Chris@585: d->setWindowTitle(tr("About %1").arg(QApplication::applicationName())); Chris@585: Chris@585: QGridLayout *layout = new QGridLayout; Chris@585: d->setLayout(layout); Chris@585: Chris@585: int row = 0; Chris@585: Chris@585: QLabel *iconLabel = new QLabel; Chris@585: iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64)); Chris@585: layout->addWidget(iconLabel, row, 0, Qt::AlignTop); Chris@585: Chris@585: QLabel *mainText = new QLabel(); Chris@585: layout->addWidget(mainText, row, 1, 1, 2); Chris@585: Chris@585: layout->setRowStretch(row, 10); Chris@585: layout->setColumnStretch(1, 10); Chris@585: Chris@585: ++row; Chris@585: Chris@585: QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); Chris@585: layout->addWidget(bb, row++, 0, 1, 3); Chris@585: connect(bb, SIGNAL(accepted()), d, SLOT(accept())); Chris@585: Chris@585: mainText->setWordWrap(true); Chris@585: mainText->setOpenExternalLinks(true); Chris@585: mainText->setText(aboutText); Chris@585: Chris@585: d->setMinimumSize(m_viewManager->scalePixelSize(420), Chris@585: m_viewManager->scalePixelSize(200)); Chris@585: Chris@585: d->exec(); Chris@585: Chris@585: delete d; Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::keyReference() Chris@0: { Chris@0: m_keyReference->show(); Chris@0: } Chris@0: Chris@231: void Chris@231: MainWindow::newerVersionAvailable(QString version) Chris@231: { Chris@585: m_newerVersionIs = version; Chris@585: Chris@231: //!!! nicer URL would be nicer Chris@231: QSettings settings; Chris@231: settings.beginGroup("NewerVersionWarning"); Chris@231: QString tag = QString("version-%1-available-show").arg(version); Chris@231: if (settings.value(tag, true).toBool()) { Chris@231: QString title(tr("Newer version available")); Chris@231: QString text(tr("

    Newer version available

    You are using version %1 of Tony, but version %2 is now available.

    Please see the Tony website for more information.

    ").arg(TONY_VERSION).arg(version)); Chris@231: QMessageBox::information(this, title, text); Chris@231: settings.setValue(tag, false); Chris@231: } Chris@231: settings.endGroup(); Chris@231: } Chris@231: matthiasm@356: void matthiasm@356: MainWindow::ffwd() matthiasm@356: { matthiasm@356: if (!getMainModel()) return; matthiasm@356: Chris@403: sv_frame_t frame = m_viewManager->getPlaybackFrame(); matthiasm@356: ++frame; matthiasm@356: Chris@403: sv_samplerate_t sr = getMainModel()->getSampleRate(); matthiasm@356: matthiasm@356: // The step is supposed to scale and be as wide as a step of matthiasm@357: // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 Chris@540: Chris@540: ZoomLevel zoom = m_viewManager->getGlobalZoom(); Chris@540: double framesPerPixel = 1.0; Chris@540: if (zoom.zone == ZoomLevel::FramesPerPixel) { Chris@540: framesPerPixel = zoom.level; Chris@540: } else { Chris@540: framesPerPixel = 1.0 / zoom.level; Chris@540: } Chris@540: double defaultFramesPerPixel = (720 * 44100) / sr; Chris@540: double scaler = framesPerPixel / defaultFramesPerPixel; Chris@403: RealTime step = m_defaultFfwdRwdStep * scaler; Chris@403: matthiasm@356: frame = RealTime::realTime2Frame Chris@403: (RealTime::frame2RealTime(frame, sr) + step, sr); Chris@403: Chris@403: if (frame > getMainModel()->getEndFrame()) { matthiasm@356: frame = getMainModel()->getEndFrame(); matthiasm@356: } Chris@403: matthiasm@356: if (frame < 0) frame = 0; matthiasm@356: matthiasm@356: if (m_viewManager->getPlaySelectionMode()) { Chris@403: frame = m_viewManager->constrainFrameToSelection(frame); matthiasm@356: } matthiasm@356: matthiasm@356: m_viewManager->setPlaybackFrame(frame); matthiasm@356: Chris@403: if (frame == getMainModel()->getEndFrame() && matthiasm@356: m_playSource && matthiasm@356: m_playSource->isPlaying() && matthiasm@356: !m_viewManager->getPlayLoopMode()) { matthiasm@356: stop(); matthiasm@356: } matthiasm@356: } matthiasm@356: matthiasm@356: void matthiasm@356: MainWindow::rewind() matthiasm@356: { matthiasm@356: if (!getMainModel()) return; matthiasm@356: Chris@403: sv_frame_t frame = m_viewManager->getPlaybackFrame(); matthiasm@356: if (frame > 0) --frame; matthiasm@356: Chris@403: sv_samplerate_t sr = getMainModel()->getSampleRate(); matthiasm@356: matthiasm@356: // The step is supposed to scale and be as wide as a step of matthiasm@357: // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 Chris@540: Chris@540: ZoomLevel zoom = m_viewManager->getGlobalZoom(); Chris@540: double framesPerPixel = 1.0; Chris@540: if (zoom.zone == ZoomLevel::FramesPerPixel) { Chris@540: framesPerPixel = zoom.level; Chris@540: } else { Chris@540: framesPerPixel = 1.0 / zoom.level; Chris@540: } Chris@540: double defaultFramesPerPixel = (720 * 44100) / sr; Chris@540: double scaler = framesPerPixel / defaultFramesPerPixel; Chris@403: RealTime step = m_defaultFfwdRwdStep * scaler; Chris@403: matthiasm@356: frame = RealTime::realTime2Frame Chris@403: (RealTime::frame2RealTime(frame, sr) - step, sr); Chris@403: Chris@403: if (frame < getMainModel()->getStartFrame()) { matthiasm@356: frame = getMainModel()->getStartFrame(); matthiasm@356: } matthiasm@356: matthiasm@356: if (frame < 0) frame = 0; matthiasm@356: matthiasm@356: if (m_viewManager->getPlaySelectionMode()) { Chris@403: frame = m_viewManager->constrainFrameToSelection(frame); matthiasm@356: } matthiasm@356: matthiasm@356: m_viewManager->setPlaybackFrame(frame); matthiasm@356: }