Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@0: Sonic Visualiser Chris@0: An audio file viewer and annotation editor. Chris@0: Centre for Digital Music, Queen Mary, University of London. Chris@200: This file copyright 2006-2007 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@144: #include "../version.h" Chris@0: Chris@0: #include "MainWindow.h" Chris@0: #include "PreferencesDialog.h" Chris@0: Chris@1: #include "view/Pane.h" Chris@1: #include "view/PaneStack.h" Chris@1: #include "data/model/WaveFileModel.h" Chris@1: #include "data/model/SparseOneDimensionalModel.h" Chris@415: #include "data/model/RangeSummarisableTimeValueModel.h" Chris@185: #include "data/model/NoteModel.h" Chris@895: #include "data/model/AggregateWaveModel.h" Chris@189: #include "data/model/Labeller.h" Chris@222: #include "data/osc/OSCQueue.h" Chris@216: #include "framework/Document.h" Chris@357: #include "framework/TransformUserConfigurator.h" Chris@1: #include "view/ViewManager.h" Chris@0: #include "base/Preferences.h" Chris@423: #include "base/ResourceFinder.h" Chris@1995: #include "base/RecordDirectory.h" Chris@0: #include "layer/WaveformLayer.h" Chris@0: #include "layer/TimeRulerLayer.h" Chris@0: #include "layer/TimeInstantLayer.h" Chris@0: #include "layer/TimeValueLayer.h" Chris@307: #include "layer/NoteLayer.h" Chris@0: #include "layer/Colour3DPlotLayer.h" Chris@95: #include "layer/SliceLayer.h" Chris@95: #include "layer/SliceableLayer.h" Chris@193: #include "layer/ImageLayer.h" Chris@340: #include "layer/RegionLayer.h" Chris@65: #include "view/Overview.h" Chris@0: #include "widgets/PropertyBox.h" Chris@0: #include "widgets/PropertyStack.h" Chris@0: #include "widgets/AudioDial.h" Chris@1386: #include "widgets/LevelPanWidget.h" Chris@1431: #include "widgets/LevelPanToolButton.h" Chris@168: #include "widgets/IconLoader.h" Chris@219: #include "widgets/LayerTreeDialog.h" Chris@0: #include "widgets/ListInputDialog.h" Chris@36: #include "widgets/SubdividingMenu.h" Chris@90: #include "widgets/NotifyingPushButton.h" Chris@162: #include "widgets/KeyReference.h" Chris@273: #include "widgets/TransformFinder.h" Chris@192: #include "widgets/LabelCounterInputDialog.h" Chris@302: #include "widgets/ActivityLog.h" Chris@891: #include "widgets/UnitConverter.h" Chris@1779: #include "widgets/ProgressDialog.h" Chris@1989: #include "widgets/CSVAudioFormatDialog.h" Chris@1035: #include "audio/AudioCallbackPlaySource.h" Chris@1476: #include "audio/AudioCallbackRecordTarget.h" Chris@1035: #include "audio/PlaySpeedRangeMapper.h" Chris@1: #include "data/fileio/DataFileReaderFactory.h" Chris@180: #include "data/fileio/PlaylistFileReader.h" Chris@1: #include "data/fileio/WavFileWriter.h" Chris@1: #include "data/fileio/CSVFileWriter.h" Chris@185: #include "data/fileio/MIDIFileWriter.h" Chris@1: #include "data/fileio/BZipFileDevice.h" Chris@198: #include "data/fileio/FileSource.h" Chris@304: #include "data/midi/MIDIInput.h" Chris@1: #include "base/RecentFiles.h" Chris@1145: #include "plugin/PluginScan.h" Chris@249: #include "transform/TransformFactory.h" Chris@249: #include "transform/ModelTransformerFactory.h" Chris@0: #include "base/XmlExportable.h" Chris@248: #include "widgets/CommandHistory.h" Chris@0: #include "base/Profiler.h" Chris@0: #include "base/Clipboard.h" Chris@165: #include "base/UnitDatabase.h" Chris@248: #include "layer/ColourDatabase.h" Chris@265: #include "widgets/ModelDataTableDialog.h" Chris@289: #include "rdf/PluginRDFIndexer.h" Chris@276: Chris@662: #include "Surveyer.h" Chris@663: #include "NetworkPermissionTester.h" Chris@334: #include "framework/VersionTester.h" Chris@333: Chris@0: // For version information Chris@280: #include Chris@280: #include Chris@0: #include "plugin/api/ladspa.h" Chris@0: #include "plugin/api/dssi.h" Chris@0: Chris@1035: #include Chris@1055: #include Chris@1035: 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@88: #include Chris@88: #include Chris@0: #include Chris@911: #include Chris@0: #include Chris@7: #include Chris@5: #include Chris@11: #include Chris@11: #include Chris@16: #include Chris@55: #include Chris@158: #include Chris@1432: #include Chris@483: #include Chris@426: #include Chris@1516: #include Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: Chris@33: using std::vector; Chris@33: using std::map; Chris@33: using std::set; Chris@33: Chris@0: Chris@1045: MainWindow::MainWindow(SoundOptions options, bool withOSCSupport) : Chris@1045: MainWindowBase(options), Chris@2126: m_overview(nullptr), Chris@0: m_mainMenusCreated(false), Chris@2126: m_paneMenu(nullptr), Chris@2126: m_layerMenu(nullptr), Chris@2126: m_transformsMenu(nullptr), Chris@2126: m_playbackMenu(nullptr), Chris@2126: m_existingLayersMenu(nullptr), Chris@2126: m_sliceMenu(nullptr), Chris@2126: m_recentFilesMenu(nullptr), Chris@2126: m_recentTransformsMenu(nullptr), Chris@2126: m_templatesMenu(nullptr), Chris@2126: m_rightButtonMenu(nullptr), Chris@2126: m_rightButtonLayerMenu(nullptr), Chris@2126: m_rightButtonTransformsMenu(nullptr), Chris@2126: m_rightButtonPlaybackMenu(nullptr), Chris@2126: m_soloAction(nullptr), Chris@2126: m_rwdStartAction(nullptr), Chris@2126: m_rwdSimilarAction(nullptr), Chris@2126: m_rwdAction(nullptr), Chris@2126: m_ffwdAction(nullptr), Chris@2126: m_ffwdSimilarAction(nullptr), Chris@2126: m_ffwdEndAction(nullptr), Chris@2126: m_playAction(nullptr), Chris@2126: m_recordAction(nullptr), Chris@2126: m_playSelectionAction(nullptr), Chris@2126: m_playLoopAction(nullptr), Chris@705: m_soloModified(false), Chris@705: m_prevSolo(false), Chris@2126: m_playControlsSpacer(nullptr), Chris@239: m_playControlsWidth(0), Chris@2126: m_preferencesDialog(nullptr), Chris@2126: m_layerTreeDialog(nullptr), Chris@302: m_activityLog(new ActivityLog()), Chris@891: m_unitConverter(new UnitConverter()), Chris@426: m_keyReference(new KeyReference()), Chris@2126: m_templateWatcher(nullptr) Chris@0: { Chris@253: Profiler profiler("MainWindow::MainWindow"); Chris@253: Chris@1610: SVDEBUG << "MainWindow: " << getReleaseText() << endl; Chris@1610: Chris@518: setWindowTitle(QApplication::applicationName()); Chris@0: Chris@165: UnitDatabase *udb = UnitDatabase::getInstance(); Chris@165: udb->registerUnit("Hz"); Chris@165: udb->registerUnit("dB"); Chris@165: udb->registerUnit("s"); Chris@165: Chris@165: ColourDatabase *cdb = ColourDatabase::getInstance(); Chris@165: cdb->addColour(Qt::black, tr("Black")); Chris@165: cdb->addColour(Qt::darkRed, tr("Red")); Chris@165: cdb->addColour(Qt::darkBlue, tr("Blue")); Chris@165: cdb->addColour(Qt::darkGreen, tr("Green")); Chris@165: cdb->addColour(QColor(200, 50, 255), tr("Purple")); Chris@165: cdb->addColour(QColor(255, 150, 50), tr("Orange")); Chris@166: cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true); Chris@166: cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true); Chris@166: cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true); Chris@166: cdb->setUseDarkBackground(cdb->addColour(Qt::green, tr("Bright Green")), true); Chris@166: cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true); Chris@166: cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true); Chris@0: Chris@1639: SVDEBUG << "MainWindow: Creating main user interface layout" << endl; Chris@1639: Chris@0: QFrame *frame = new QFrame; Chris@0: setCentralWidget(frame); Chris@0: Chris@0: QGridLayout *layout = new QGridLayout; Chris@180: Chris@519: m_descriptionLabel = new QLabel; Chris@0: Chris@489: m_mainScroll = new QScrollArea(frame); Chris@489: m_mainScroll->setWidgetResizable(true); Chris@489: m_mainScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); Chris@489: m_mainScroll->setFrameShape(QFrame::NoFrame); Chris@489: Chris@489: m_mainScroll->setWidget(m_paneStack); Chris@159: Chris@65: m_overview = new Overview(frame); Chris@65: m_overview->setViewManager(m_viewManager); Chris@1431: int overviewHeight = m_viewManager->scalePixelSize(35); Chris@1431: if (overviewHeight < 40) overviewHeight = 40; Chris@1431: m_overview->setFixedHeight(overviewHeight); Chris@144: #ifndef _WIN32 Chris@144: // For some reason, the contents of the overview never appear if we Chris@144: // make this setting on Windows. I have no inclination at the moment Chris@144: // to track down the reason why. Chris@129: m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); Chris@144: #endif Chris@90: connect(m_overview, SIGNAL(contextHelpChanged(const QString &)), Chris@116: 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@65: m_overview->addLayer(m_panLayer); Chris@174: Chris@1448: coloursChanged(); // sets pan layer colour from preferences Chris@0: Chris@0: m_playSpeed = new AudioDial(frame); Chris@12: m_playSpeed->setMinimum(0); Chris@1031: m_playSpeed->setMaximum(120); Chris@1031: m_playSpeed->setValue(60); Chris@1431: m_playSpeed->setFixedWidth(overviewHeight); Chris@1431: m_playSpeed->setFixedHeight(overviewHeight); Chris@0: m_playSpeed->setNotchesVisible(true); Chris@25: m_playSpeed->setPageStep(10); Chris@1031: m_playSpeed->setObjectName(tr("Playback Speed")); Chris@1031: m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper); Chris@1031: m_playSpeed->setDefaultValue(60); Chris@60: m_playSpeed->setShowToolTip(true); Chris@0: connect(m_playSpeed, SIGNAL(valueChanged(int)), Chris@1770: this, SLOT(playSpeedChanged(int))); Chris@90: connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); Chris@90: connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); Chris@90: Chris@1431: m_mainLevelPan = new LevelPanToolButton(frame); Chris@1386: connect(m_mainLevelPan, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); Chris@1386: connect(m_mainLevelPan, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); Chris@1431: m_mainLevelPan->setFixedHeight(overviewHeight); Chris@1431: m_mainLevelPan->setFixedWidth(overviewHeight); Chris@1431: m_mainLevelPan->setImageSize((overviewHeight * 3) / 4); Chris@1431: m_mainLevelPan->setBigImageSize(overviewHeight * 3); Chris@168: Chris@239: m_playControlsSpacer = new QFrame; Chris@239: Chris@1383: layout->setSpacing(m_viewManager->scalePixelSize(4)); Chris@1383: layout->addWidget(m_mainScroll, 0, 0, 1, 4); Chris@1383: layout->addWidget(m_overview, 1, 0); Chris@1431: layout->addWidget(m_playSpeed, 1, 1); Chris@1431: layout->addWidget(m_playControlsSpacer, 1, 2); Chris@1386: layout->addWidget(m_mainLevelPan, 1, 3); Chris@239: Chris@239: m_playControlsWidth = Chris@1386: m_mainLevelPan->width() + m_playSpeed->width() + layout->spacing() * 2; Chris@239: Chris@239: m_paneStack->setPropertyStackMinWidth(m_playControlsWidth Chris@239: + 2 + layout->spacing()); Chris@239: m_playControlsSpacer->setFixedSize(QSize(2, 2)); Chris@239: Chris@1383: layout->setColumnStretch(0, 10); Chris@239: Chris@239: connect(m_paneStack, SIGNAL(propertyStacksResized(int)), Chris@239: this, SLOT(propertyStacksResized(int))); Chris@16: Chris@0: frame->setLayout(layout); Chris@0: Chris@1639: SVDEBUG << "MainWindow: Creating menus and toolbars" << endl; Chris@1639: cannam@1461: #ifdef Q_OS_MAC cannam@1461: // Mac doesn't align menu labels when icons are shown: result is messy cannam@1461: QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); cannam@1461: setIconsVisibleInMenus(false); cannam@1461: #endif cannam@1461: Chris@0: setupMenus(); Chris@0: setupToolbars(); Chris@155: setupHelpMenu(); Chris@0: Chris@90: statusBar(); Chris@340: m_currentLabel = new QLabel; Chris@340: statusBar()->addPermanentWidget(m_currentLabel); Chris@0: Chris@728: finaliseMenus(); Chris@728: Chris@302: connect(m_viewManager, SIGNAL(activity(QString)), Chris@302: m_activityLog, SLOT(activityHappened(QString))); Chris@302: connect(m_playSource, SIGNAL(activity(QString)), Chris@302: m_activityLog, SLOT(activityHappened(QString))); Chris@302: connect(CommandHistory::getInstance(), SIGNAL(activity(QString)), Chris@302: m_activityLog, SLOT(activityHappened(QString))); Chris@310: connect(this, SIGNAL(activity(QString)), Chris@310: m_activityLog, SLOT(activityHappened(QString))); Chris@303: connect(this, SIGNAL(replacedDocument()), this, SLOT(documentReplaced())); Chris@1087: Chris@306: m_activityLog->hide(); Chris@303: Chris@891: m_unitConverter->hide(); Chris@1056: Chris@1056: setAudioRecordMode(RecordCreateAdditionalModel); Chris@1639: Chris@1639: SVDEBUG << "MainWindow: Creating new session" << endl; Chris@1639: Chris@303: newSession(); Chris@303: Chris@304: connect(m_midiInput, SIGNAL(eventsAvailable()), Chris@304: this, SLOT(midiEventsAvailable())); Chris@663: Chris@1639: SVDEBUG << "MainWindow: Creating network permission tester" << endl; Chris@1639: Chris@1613: NetworkPermissionTester tester(withOSCSupport); Chris@663: bool networkPermission = tester.havePermission(); Chris@663: if (networkPermission) { Chris@1639: SVDEBUG << "MainWindow: Starting transform population thread" << endl; Chris@663: TransformFactory::getInstance()->startPopulationThread(); Chris@1639: Chris@1639: SVDEBUG << "MainWindow: Creating surveyer" << endl; Chris@663: m_surveyer = new Surveyer Chris@663: ("sonicvisualiser.org", "survey23-present.txt", "survey23.php"); Chris@1639: Chris@1639: SVDEBUG << "MainWindow: Creating version tester" << endl; Chris@663: m_versionTester = new VersionTester Chris@663: ("sonicvisualiser.org", "latest-version.txt", SV_VERSION); Chris@663: connect(m_versionTester, SIGNAL(newerVersionAvailable(QString)), Chris@663: this, SLOT(newerVersionAvailable(QString))); Chris@663: } else { Chris@2126: m_surveyer = nullptr; Chris@2126: m_versionTester = nullptr; Chris@663: } Chris@1087: Chris@2240: if (withOSCSupport && networkPermission) { Chris@2240: SVDEBUG << "MainWindow: Creating OSC queue with network port" Chris@2240: << endl; Chris@2240: startOSCQueue(true); Chris@2240: } else { Chris@2240: SVDEBUG << "MainWindow: Creating internal-only OSC queue without port" Chris@2240: << endl; Chris@2240: startOSCQueue(false); Chris@2240: } Chris@2240: Chris@1630: /* Chris@1521: QTimer::singleShot(500, this, SLOT(betaReleaseWarning())); Chris@1630: */ Chris@1521: Chris@1145: QString warning = PluginScan::getInstance()->getStartupFailureReport(); Chris@1148: if (warning != "") { Chris@1148: QTimer::singleShot(500, this, SLOT(pluginPopulationWarning())); Chris@1148: } Chris@1639: Chris@1639: SVDEBUG << "MainWindow: Constructor done" << endl; Chris@0: } Chris@0: Chris@0: MainWindow::~MainWindow() Chris@0: { Chris@438: // SVDEBUG << "MainWindow::~MainWindow" << endl; Chris@162: delete m_keyReference; Chris@504: delete m_activityLog; Chris@891: delete m_unitConverter; Chris@163: delete m_preferencesDialog; Chris@219: delete m_layerTreeDialog; Chris@573: delete m_versionTester; Chris@662: delete m_surveyer; Chris@0: Profiles::getInstance()->dump(); Chris@438: // SVDEBUG << "MainWindow::~MainWindow finishing" << endl; Chris@0: } Chris@0: Chris@81: void Chris@0: MainWindow::setupMenus() Chris@0: { Chris@0: if (!m_mainMenusCreated) { Chris@779: Chris@779: #ifdef Q_OS_LINUX Chris@779: // In Ubuntu 14.04 the window's menu bar goes missing entirely Chris@779: // if the user is running any desktop environment other than Unity Chris@779: // (in which the faux single-menubar appears). The user has a Chris@779: // workaround, to remove the appmenu-qt5 package, but that is Chris@779: // awkward and the problem is so severe that it merits disabling Chris@779: // the system menubar integration altogether. Like this: Chris@1770: menuBar()->setNativeMenuBar(false); // fix #1039 Chris@779: #endif Chris@1458: Chris@0: m_rightButtonMenu = new QMenu(); Chris@104: Chris@1458: // We don't want tear-off enabled on the right-button menu. Chris@1458: // If it is enabled, then simply right-clicking and releasing Chris@1458: // will pop up the menu, activate the tear-off, and leave the Chris@1458: // torn-off menu window in front of the main window. That Chris@1458: // isn't desirable. Chris@1458: m_rightButtonMenu->setTearOffEnabled(false); Chris@0: } Chris@0: Chris@211: if (m_rightButtonTransformsMenu) { Chris@211: m_rightButtonTransformsMenu->clear(); Chris@34: } else { Chris@211: m_rightButtonTransformsMenu = m_rightButtonMenu->addMenu(tr("&Transform")); Chris@211: m_rightButtonTransformsMenu->setTearOffEnabled(true); Chris@34: m_rightButtonMenu->addSeparator(); Chris@34: } Chris@34: Chris@346: // This will be created (if not found) or cleared (if found) in Chris@346: // setupPaneAndLayerMenus, but we want to ensure it's created Chris@346: // sooner so it can go nearer the top of the right button menu Chris@346: if (m_rightButtonLayerMenu) { Chris@346: m_rightButtonLayerMenu->clear(); Chris@346: } else { Chris@346: m_rightButtonLayerMenu = m_rightButtonMenu->addMenu(tr("&Layer")); Chris@346: m_rightButtonLayerMenu->setTearOffEnabled(true); Chris@346: m_rightButtonMenu->addSeparator(); Chris@346: } Chris@346: Chris@0: if (!m_mainMenusCreated) { Chris@0: CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); Chris@0: m_rightButtonMenu->addSeparator(); Chris@66: } Chris@66: Chris@66: setupFileMenu(); Chris@66: setupEditMenu(); Chris@66: setupViewMenu(); Chris@66: setupPaneAndLayerMenus(); Chris@211: setupTransformsMenu(); Chris@66: Chris@66: m_mainMenusCreated = true; Chris@66: } Chris@66: Chris@66: void Chris@489: MainWindow::goFullScreen() Chris@489: { Chris@588: if (m_viewManager->getZoomWheelsEnabled()) { Chris@588: // The wheels seem to end up in the wrong place in full-screen mode Chris@588: toggleZoomWheels(); Chris@588: } Chris@588: Chris@492: QWidget *ps = m_mainScroll->takeWidget(); Chris@2126: ps->setParent(nullptr); Chris@494: Chris@494: QShortcut *sc; Chris@494: Chris@494: sc = new QShortcut(QKeySequence("Esc"), ps); Chris@494: connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen())); Chris@494: Chris@494: sc = new QShortcut(QKeySequence("F11"), ps); Chris@494: connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen())); Chris@494: Chris@494: QAction *acts[] = { Chris@494: m_playAction, m_zoomInAction, m_zoomOutAction, m_zoomFitAction, Chris@494: m_scrollLeftAction, m_scrollRightAction, m_showPropertyBoxesAction Chris@494: }; Chris@494: Chris@705: for (int i = 0; i < int(sizeof(acts)/sizeof(acts[0])); ++i) { Chris@494: sc = new QShortcut(acts[i]->shortcut(), ps); Chris@494: connect(sc, SIGNAL(activated()), acts[i], SLOT(trigger())); Chris@494: } Chris@494: Chris@492: ps->showFullScreen(); Chris@492: } Chris@492: Chris@492: void Chris@492: MainWindow::endFullScreen() Chris@492: { Chris@494: // these were only created in goFullScreen: Chris@494: QObjectList cl = m_paneStack->children(); Chris@494: foreach (QObject *o, cl) { Chris@494: QShortcut *sc = qobject_cast(o); Chris@494: if (sc) delete sc; Chris@494: } Chris@494: Chris@588: m_paneStack->showNormal(); Chris@492: m_mainScroll->setWidget(m_paneStack); Chris@489: } Chris@489: Chris@489: void Chris@66: MainWindow::setupFileMenu() Chris@66: { Chris@66: if (m_mainMenusCreated) return; Chris@66: Chris@66: QMenu *menu = menuBar()->addMenu(tr("&File")); Chris@97: menu->setTearOffEnabled(true); Chris@66: QToolBar *toolbar = addToolBar(tr("File Toolbar")); Chris@66: Chris@162: m_keyReference->setCategory(tr("File and Session Management")); Chris@162: Chris@168: IconLoader il; Chris@168: Chris@168: QIcon icon = il.load("filenew"); Chris@66: QAction *action = new QAction(icon, tr("&New Session"), this); Chris@66: action->setShortcut(tr("Ctrl+N")); Chris@518: action->setStatusTip(tr("Abandon the current %1 session and start a new one").arg(QApplication::applicationName())); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(newSession())); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: toolbar->addAction(action); Chris@518: Chris@168: icon = il.load("fileopen"); Chris@66: action = new QAction(icon, tr("&Open..."), this); Chris@418: action->setShortcut(tr("Ctrl+O")); Chris@66: action->setStatusTip(tr("Open a session file, audio file, or layer")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(openSomething())); Chris@705: m_keyReference->registerShortcut(action); Chris@66: toolbar->addAction(action); Chris@418: menu->addAction(action); Chris@418: Chris@418: // We want this one to go on the toolbar now, if we add it at all, Chris@418: // but on the menu later Chris@418: QAction *iaction = new QAction(tr("&Import More Audio..."), this); Chris@418: iaction->setShortcut(tr("Ctrl+I")); Chris@418: iaction->setStatusTip(tr("Import an extra audio file into a new pane")); Chris@418: connect(iaction, SIGNAL(triggered()), this, SLOT(importMoreAudio())); Chris@418: connect(this, SIGNAL(canImportMoreAudio(bool)), iaction, SLOT(setEnabled(bool))); Chris@418: m_keyReference->registerShortcut(iaction); Chris@418: Chris@508: // We want this one to go on the toolbar now, if we add it at all, Chris@508: // but on the menu later Chris@508: QAction *raction = new QAction(tr("Replace &Main Audio..."), this); Chris@508: raction->setStatusTip(tr("Replace the main audio file of the session with a different file")); Chris@508: connect(raction, SIGNAL(triggered()), this, SLOT(replaceMainAudio())); Chris@508: connect(this, SIGNAL(canReplaceMainAudio(bool)), raction, SLOT(setEnabled(bool))); Chris@508: Chris@418: action = new QAction(tr("Open Lo&cation..."), this); Chris@418: action->setShortcut(tr("Ctrl+Shift+O")); Chris@418: action->setStatusTip(tr("Open or import a file from a remote URL")); Chris@418: connect(action, SIGNAL(triggered()), this, SLOT(openLocation())); Chris@418: m_keyReference->registerShortcut(action); Chris@418: menu->addAction(action); Chris@418: Chris@420: m_recentFilesMenu = menu->addMenu(tr("Open &Recent")); Chris@418: m_recentFilesMenu->setTearOffEnabled(true); Chris@418: setupRecentFilesMenu(); Chris@418: connect(&m_recentFiles, SIGNAL(recentChanged()), Chris@418: this, SLOT(setupRecentFilesMenu())); Chris@418: Chris@418: menu->addSeparator(); Chris@415: Chris@168: icon = il.load("filesave"); Chris@66: action = new QAction(icon, tr("&Save Session"), this); Chris@66: action->setShortcut(tr("Ctrl+S")); Chris@518: action->setStatusTip(tr("Save the current session into a %1 session file").arg(QApplication::applicationName())); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); Chris@66: connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: toolbar->addAction(action); Chris@1770: Chris@168: icon = il.load("filesaveas"); Chris@66: action = new QAction(icon, tr("Save Session &As..."), this); Chris@336: action->setShortcut(tr("Ctrl+Shift+S")); Chris@518: action->setStatusTip(tr("Save the current session into a new %1 session file").arg(QApplication::applicationName())); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); Chris@66: menu->addAction(action); Chris@66: toolbar->addAction(action); Chris@66: Chris@66: menu->addSeparator(); Chris@66: Chris@508: // the Replace action we made earlier Chris@508: menu->addAction(raction); Chris@508: Chris@418: // the Import action we made earlier Chris@418: menu->addAction(iaction); Chris@66: Chris@66: action = new QAction(tr("&Export Audio File..."), this); Chris@66: action->setStatusTip(tr("Export selection as an audio file")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(exportAudio())); Chris@66: connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool))); Chris@66: menu->addAction(action); Chris@66: Chris@66: menu->addSeparator(); Chris@66: Chris@66: action = new QAction(tr("Import Annotation &Layer..."), this); Chris@66: action->setShortcut(tr("Ctrl+L")); Chris@66: action->setStatusTip(tr("Import layer data from an existing file")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(importLayer())); Chris@66: connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: Chris@977: action = new QAction(tr("Export Annotation La&yer..."), this); Chris@977: action->setShortcut(tr("Ctrl+Y")); Chris@66: action->setStatusTip(tr("Export layer data to a file")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(exportLayer())); Chris@66: connect(this, SIGNAL(canExportLayer(bool)), action, SLOT(setEnabled(bool))); Chris@977: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: Chris@66: menu->addSeparator(); Chris@1912: Chris@1995: action = new QAction(tr("Convert Audio from Data File..."), this); Chris@1995: action->setStatusTip(tr("Convert and import audio sample values from a CSV data file")); Chris@1995: connect(action, SIGNAL(triggered()), this, SLOT(convertAudio())); Chris@1912: menu->addAction(action); Chris@1912: Chris@1987: action = new QAction(tr("Export Audio to Data File..."), this); Chris@1912: action->setStatusTip(tr("Export audio from selection into a CSV data file")); Chris@1912: connect(action, SIGNAL(triggered()), this, SLOT(exportAudioData())); Chris@1912: connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool))); Chris@1912: menu->addAction(action); Chris@1912: Chris@1912: menu->addSeparator(); Chris@86: Chris@121: action = new QAction(tr("Export Image File..."), this); Chris@121: action->setStatusTip(tr("Export a single pane to an image file")); Chris@121: connect(action, SIGNAL(triggered()), this, SLOT(exportImage())); Chris@121: connect(this, SIGNAL(canExportImage(bool)), action, SLOT(setEnabled(bool))); Chris@121: menu->addAction(action); Chris@121: Chris@1451: action = new QAction(tr("Export SVG File..."), this); Chris@1451: action->setStatusTip(tr("Export a single pane to a scalable SVG image file")); Chris@1451: connect(action, SIGNAL(triggered()), this, SLOT(exportSVG())); Chris@1451: connect(this, SIGNAL(canExportImage(bool)), action, SLOT(setEnabled(bool))); Chris@1451: menu->addAction(action); Chris@1451: Chris@121: menu->addSeparator(); Chris@121: Chris@1995: action = new QAction(tr("Browse Recorded and Converted Audio"), this); Chris@1056: action->setStatusTip(tr("Open the Recorded Audio folder in the system file browser")); Chris@1056: connect(action, SIGNAL(triggered()), this, SLOT(browseRecordedAudio())); Chris@1056: menu->addAction(action); Chris@1056: Chris@1056: menu->addSeparator(); Chris@1056: Chris@435: QString templatesMenuLabel = tr("Apply Session Template"); Chris@425: m_templatesMenu = menu->addMenu(templatesMenuLabel); Chris@425: m_templatesMenu->setTearOffEnabled(true); Chris@436: // We need to have a main model for this option to be useful: Chris@436: // canExportAudio captures that Chris@436: connect(this, SIGNAL(canExportAudio(bool)), m_templatesMenu, SLOT(setEnabled(bool))); Chris@436: Chris@436: // Set up the menu in a moment, after m_manageTemplatesAction constructed Chris@435: Chris@435: action = new QAction(tr("Export Session as Template..."), this); Chris@435: connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAsTemplate())); Chris@436: // We need to have something in the session for this to be useful: Chris@436: // canDeleteCurrentLayer captures that Chris@436: connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool))); Chris@435: menu->addAction(action); Chris@435: Chris@436: m_manageTemplatesAction = new QAction(tr("Manage Exported Templates"), this); Chris@436: connect(m_manageTemplatesAction, SIGNAL(triggered()), this, SLOT(manageSavedTemplates())); Chris@436: menu->addAction(m_manageTemplatesAction); Chris@436: Chris@436: setupTemplatesMenu(); Chris@425: Chris@66: action = new QAction(tr("&Preferences..."), this); Chris@66: action->setStatusTip(tr("Adjust the application preferences")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(preferences())); Chris@66: menu->addAction(action); Chris@1770: Chris@66: menu->addSeparator(); Chris@168: action = new QAction(il.load("exit"), Chris@66: tr("&Quit"), this); Chris@66: action->setShortcut(tr("Ctrl+Q")); Chris@518: action->setStatusTip(tr("Exit %1").arg(QApplication::applicationName())); Chris@500: connect(action, SIGNAL(triggered()), qApp, SLOT(closeAllWindows())); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: } Chris@66: Chris@66: void Chris@66: MainWindow::setupEditMenu() Chris@66: { Chris@66: if (m_mainMenusCreated) return; Chris@66: Chris@66: QMenu *menu = menuBar()->addMenu(tr("&Edit")); Chris@97: menu->setTearOffEnabled(true); Chris@66: CommandHistory::getInstance()->registerMenu(menu); Chris@66: Chris@162: m_keyReference->setCategory(tr("Editing")); Chris@162: Chris@66: menu->addSeparator(); Chris@66: Chris@168: IconLoader il; Chris@168: Chris@168: QAction *action = new QAction(il.load("editcut"), Chris@66: tr("Cu&t"), this); Chris@66: action->setShortcut(tr("Ctrl+X")); Chris@90: action->setStatusTip(tr("Cut the selection from the current layer to the clipboard")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(cut())); Chris@66: connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: m_rightButtonMenu->addAction(action); Chris@66: Chris@168: action = new QAction(il.load("editcopy"), Chris@66: tr("&Copy"), this); Chris@66: action->setShortcut(tr("Ctrl+C")); Chris@90: action->setStatusTip(tr("Copy the selection from the current layer to the clipboard")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(copy())); Chris@66: connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: m_rightButtonMenu->addAction(action); Chris@66: Chris@168: action = new QAction(il.load("editpaste"), Chris@66: tr("&Paste"), this); Chris@66: action->setShortcut(tr("Ctrl+V")); Chris@90: action->setStatusTip(tr("Paste from the clipboard to the current layer")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(paste())); Chris@66: connect(this, SIGNAL(canPaste(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: m_rightButtonMenu->addAction(action); Chris@66: Chris@395: action = new QAction(tr("Paste at Playback Position"), this); Chris@395: action->setShortcut(tr("Ctrl+Shift+V")); Chris@395: action->setStatusTip(tr("Paste from the clipboard to the current layer, placing the first item at the playback position")); Chris@395: connect(action, SIGNAL(triggered()), this, SLOT(pasteAtPlaybackPosition())); Chris@395: connect(this, SIGNAL(canPaste(bool)), action, SLOT(setEnabled(bool))); Chris@395: m_keyReference->registerShortcut(action); Chris@395: menu->addAction(action); Chris@395: m_rightButtonMenu->addAction(action); Chris@395: Chris@164: m_deleteSelectedAction = new QAction(tr("&Delete Selected Items"), this); Chris@164: m_deleteSelectedAction->setShortcut(tr("Del")); Chris@164: m_deleteSelectedAction->setStatusTip(tr("Delete items in current selection from the current layer")); Chris@164: connect(m_deleteSelectedAction, SIGNAL(triggered()), this, SLOT(deleteSelected())); Chris@164: connect(this, SIGNAL(canDeleteSelection(bool)), m_deleteSelectedAction, SLOT(setEnabled(bool))); Chris@164: m_keyReference->registerShortcut(m_deleteSelectedAction); Chris@164: menu->addAction(m_deleteSelectedAction); Chris@164: m_rightButtonMenu->addAction(m_deleteSelectedAction); Chris@66: Chris@66: menu->addSeparator(); Chris@66: m_rightButtonMenu->addSeparator(); Chris@162: Chris@162: m_keyReference->setCategory(tr("Selection")); Chris@162: Chris@66: action = new QAction(tr("Select &All"), this); Chris@66: action->setShortcut(tr("Ctrl+A")); Chris@90: action->setStatusTip(tr("Select the whole duration of the current session")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); Chris@66: connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: m_rightButtonMenu->addAction(action); Chris@1770: Chris@66: action = new QAction(tr("Select &Visible Range"), this); Chris@66: action->setShortcut(tr("Ctrl+Shift+A")); Chris@90: action->setStatusTip(tr("Select the time range corresponding to the current window width")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(selectVisible())); Chris@66: connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@1770: Chris@66: action = new QAction(tr("Select to &Start"), this); Chris@66: action->setShortcut(tr("Shift+Left")); Chris@90: action->setStatusTip(tr("Select from the start of the session to the current playback position")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(selectToStart())); Chris@66: connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@1770: Chris@66: action = new QAction(tr("Select to &End"), this); Chris@66: action->setShortcut(tr("Shift+Right")); Chris@90: action->setStatusTip(tr("Select from the current playback position to the end of the session")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(selectToEnd())); Chris@66: connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: Chris@66: action = new QAction(tr("C&lear Selection"), this); Chris@66: action->setShortcut(tr("Esc")); Chris@90: action->setStatusTip(tr("Clear the selection")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(clearSelection())); Chris@66: connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: m_rightButtonMenu->addAction(action); Chris@66: Chris@66: menu->addSeparator(); Chris@66: Chris@162: m_keyReference->setCategory(tr("Tapping Time Instants")); Chris@162: Chris@66: action = new QAction(tr("&Insert Instant at Playback Position"), this); Chris@1782: action->setShortcut(tr(";")); Chris@90: action->setStatusTip(tr("Insert a new time instant at the current playback position, in a new layer if necessary")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(insertInstant())); Chris@66: connect(this, SIGNAL(canInsertInstant(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: Chris@1782: // Historically this was the main shortcut for "Insert Instant at Chris@1782: // Playback Position". Note that Enter refers to the keypad key, Chris@1782: // rather than the Return key, so this doesn't actually exist on Chris@1782: // many keyboards now. Accordingly the alternative shortcut ";" Chris@1782: // has been promoted to primary, listed above. Same goes for the Chris@1782: // shifted version below Chris@1782: QString shortcut(tr("Enter")); Chris@162: connect(new QShortcut(shortcut, this), SIGNAL(activated()), Chris@162: this, SLOT(insertInstant())); Chris@162: m_keyReference->registerAlternativeShortcut(action, shortcut); Chris@162: Chris@81: action = new QAction(tr("Insert Instants at Selection &Boundaries"), this); Chris@1782: action->setShortcut(tr("Shift+;")); Chris@163: action->setStatusTip(tr("Insert new time instants at the start and end of the current selected regions, in a new layer if necessary")); Chris@81: connect(action, SIGNAL(triggered()), this, SLOT(insertInstantsAtBoundaries())); Chris@81: connect(this, SIGNAL(canInsertInstantsAtBoundaries(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@81: menu->addAction(action); Chris@189: Chris@1782: shortcut = QString(tr("Shift+Enter")); Chris@1782: connect(new QShortcut(shortcut, this), SIGNAL(activated()), Chris@1782: this, SLOT(insertInstantsAtBoundaries())); Chris@1782: m_keyReference->registerAlternativeShortcut(action, shortcut); Chris@1782: Chris@1782: // The previous two actions used shortcuts with the (keypad) Enter Chris@1782: // key, while this one I (bizarrely) switched from Enter to Return Chris@1782: // in September 2014. Let's make it consistent with the above by Chris@1782: // making the primary shortcut for it Ctrl+Shift+; and keeping Chris@1782: // both Return and Enter as synonyms for ; Chris@338: action = new QAction(tr("Insert Item at Selection"), this); Chris@1782: action->setShortcut(tr("Ctrl+Shift+;")); Chris@338: action->setStatusTip(tr("Insert a new note or region item corresponding to the current selection")); Chris@338: connect(action, SIGNAL(triggered()), this, SLOT(insertItemAtSelection())); Chris@338: connect(this, SIGNAL(canInsertItemAtSelection(bool)), action, SLOT(setEnabled(bool))); Chris@338: m_keyReference->registerShortcut(action); Chris@338: menu->addAction(action); Chris@338: Chris@1782: shortcut = QString(tr("Ctrl+Shift+Enter")); Chris@1782: connect(new QShortcut(shortcut, this), SIGNAL(activated()), Chris@1782: this, SLOT(insertItemAtSelection())); Chris@1782: m_keyReference->registerAlternativeShortcut(action, shortcut); Chris@1782: Chris@1782: shortcut = QString(tr("Ctrl+Shift+Return")); Chris@1782: connect(new QShortcut(shortcut, this), SIGNAL(activated()), Chris@1782: this, SLOT(insertItemAtSelection())); Chris@1782: // we had that one for historical compatibility, but let's not Chris@1782: // register it publicly; having three shortcuts for such an Chris@1782: // obscure function is really over-egging it Chris@1782: Chris@338: menu->addSeparator(); Chris@338: Chris@190: QMenu *numberingMenu = menu->addMenu(tr("Number New Instants with")); Chris@225: numberingMenu->setTearOffEnabled(true); Chris@189: QActionGroup *numberingGroup = new QActionGroup(this); Chris@2093: m_numberingActions.clear(); Chris@189: Chris@189: Labeller::TypeNameMap types = m_labeller->getTypeNames(); Chris@189: for (Labeller::TypeNameMap::iterator i = types.begin(); i != types.end(); ++i) { Chris@190: Chris@190: if (i->first == Labeller::ValueFromLabel || Chris@190: i->first == Labeller::ValueFromExistingNeighbour) continue; Chris@190: Chris@189: action = new QAction(i->second, this); Chris@189: connect(action, SIGNAL(triggered()), this, SLOT(setInstantsNumbering())); Chris@189: action->setCheckable(true); Chris@189: action->setChecked(m_labeller->getType() == i->first); Chris@189: numberingGroup->addAction(action); Chris@189: numberingMenu->addAction(action); Chris@2093: m_numberingActions.push_back({ action, (int)i->first }); Chris@190: Chris@190: if (i->first == Labeller::ValueFromTwoLevelCounter) { Chris@192: Chris@190: QMenu *cycleMenu = numberingMenu->addMenu(tr("Cycle size")); Chris@190: QActionGroup *cycleGroup = new QActionGroup(this); Chris@190: Chris@229: int cycles[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 16 }; Chris@200: for (int i = 0; i < int(sizeof(cycles)/sizeof(cycles[0])); ++i) { Chris@190: action = new QAction(QString("%1").arg(cycles[i]), this); Chris@190: connect(action, SIGNAL(triggered()), this, SLOT(setInstantsCounterCycle())); Chris@190: action->setCheckable(true); Chris@190: action->setChecked(cycles[i] == m_labeller->getCounterCycleSize()); Chris@190: cycleGroup->addAction(action); Chris@190: cycleMenu->addAction(action); Chris@190: } Chris@190: } Chris@190: Chris@190: if (i->first == Labeller::ValueNone || Chris@190: i->first == Labeller::ValueFromTwoLevelCounter || Chris@190: i->first == Labeller::ValueFromRealTime) { Chris@190: numberingMenu->addSeparator(); Chris@190: } Chris@189: } Chris@189: Chris@597: action = new QAction(tr("Reset Numbering Counters"), this); Chris@597: action->setStatusTip(tr("Reset to 1 all the counters used for counter-based labelling")); Chris@597: connect(action, SIGNAL(triggered()), this, SLOT(resetInstantsCounters())); Chris@597: connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger())); Chris@597: menu->addAction(action); Chris@597: Chris@241: action = new QAction(tr("Set Numbering Counters..."), this); Chris@241: action->setStatusTip(tr("Set the counters used for counter-based labelling")); Chris@597: connect(action, SIGNAL(triggered()), this, SLOT(setInstantsCounters())); Chris@241: menu->addAction(action); Chris@241: Chris@241: action = new QAction(tr("Renumber Selected Instants"), this); Chris@241: action->setStatusTip(tr("Renumber the selected instants using the current labelling scheme")); Chris@189: connect(action, SIGNAL(triggered()), this, SLOT(renumberInstants())); Chris@189: connect(this, SIGNAL(canRenumberInstants(bool)), action, SLOT(setEnabled(bool))); Chris@189: // m_keyReference->registerShortcut(action); Chris@189: menu->addAction(action); Chris@1356: Chris@1356: menu->addSeparator(); Chris@1356: Chris@1355: action = new QAction(tr("Subdivide Selected Instants..."), this); Chris@1355: action->setStatusTip(tr("Add new instants at regular intervals between the selected instants")); Chris@1355: connect(action, SIGNAL(triggered()), this, SLOT(subdivideInstants())); Chris@1355: connect(this, SIGNAL(canSubdivideInstants(bool)), action, SLOT(setEnabled(bool))); Chris@1355: menu->addAction(action); Chris@1356: Chris@1356: action = new QAction(tr("Winnow Selected Instants..."), this); Chris@1356: action->setStatusTip(tr("Remove subdivisions, leaving only every Nth instant")); Chris@1356: connect(action, SIGNAL(triggered()), this, SLOT(winnowInstants())); Chris@1356: connect(this, SIGNAL(canWinnowInstants(bool)), action, SLOT(setEnabled(bool))); Chris@1356: menu->addAction(action); Chris@66: } Chris@66: Chris@66: void Chris@66: MainWindow::setupViewMenu() Chris@66: { Chris@66: if (m_mainMenusCreated) return; Chris@66: Chris@168: IconLoader il; Chris@168: Chris@2126: QAction *action = nullptr; Chris@90: Chris@162: m_keyReference->setCategory(tr("Panning and Navigation")); Chris@162: Chris@66: QMenu *menu = menuBar()->addMenu(tr("&View")); Chris@97: menu->setTearOffEnabled(true); Chris@494: m_scrollLeftAction = new QAction(tr("Scroll &Left"), this); Chris@494: m_scrollLeftAction->setShortcut(tr("Left")); Chris@494: m_scrollLeftAction->setStatusTip(tr("Scroll the current pane to the left")); Chris@494: connect(m_scrollLeftAction, SIGNAL(triggered()), this, SLOT(scrollLeft())); Chris@494: connect(this, SIGNAL(canScroll(bool)), m_scrollLeftAction, SLOT(setEnabled(bool))); Chris@494: m_keyReference->registerShortcut(m_scrollLeftAction); Chris@494: menu->addAction(m_scrollLeftAction); Chris@1770: Chris@494: m_scrollRightAction = new QAction(tr("Scroll &Right"), this); Chris@494: m_scrollRightAction->setShortcut(tr("Right")); Chris@494: m_scrollRightAction->setStatusTip(tr("Scroll the current pane to the right")); Chris@494: connect(m_scrollRightAction, SIGNAL(triggered()), this, SLOT(scrollRight())); Chris@494: connect(this, SIGNAL(canScroll(bool)), m_scrollRightAction, SLOT(setEnabled(bool))); Chris@494: m_keyReference->registerShortcut(m_scrollRightAction); Chris@494: menu->addAction(m_scrollRightAction); Chris@1770: Chris@90: action = new QAction(tr("&Jump Left"), this); Chris@66: action->setShortcut(tr("Ctrl+Left")); Chris@66: action->setStatusTip(tr("Scroll the current pane a big step to the left")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft())); Chris@66: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@1770: Chris@90: action = new QAction(tr("J&ump Right"), this); Chris@66: action->setShortcut(tr("Ctrl+Right")); Chris@66: action->setStatusTip(tr("Scroll the current pane a big step to the right")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(jumpRight())); Chris@66: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: Chris@308: action = new QAction(tr("Peek Left"), this); Chris@308: action->setShortcut(tr("Alt+Left")); Chris@308: action->setStatusTip(tr("Scroll the current pane to the left without moving the playback cursor or other panes")); Chris@308: connect(action, SIGNAL(triggered()), this, SLOT(peekLeft())); Chris@308: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@308: m_keyReference->registerShortcut(action); Chris@308: menu->addAction(action); Chris@1770: Chris@308: action = new QAction(tr("Peek Right"), this); Chris@308: action->setShortcut(tr("Alt+Right")); Chris@308: action->setStatusTip(tr("Scroll the current pane to the right without moving the playback cursor or other panes")); Chris@308: connect(action, SIGNAL(triggered()), this, SLOT(peekRight())); Chris@308: connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); Chris@308: m_keyReference->registerShortcut(action); Chris@308: menu->addAction(action); Chris@308: Chris@66: menu->addSeparator(); Chris@66: Chris@162: m_keyReference->setCategory(tr("Zoom")); Chris@162: Chris@494: m_zoomInAction = new QAction(il.load("zoom-in"), Chris@494: tr("Zoom &In"), this); Chris@494: m_zoomInAction->setShortcut(tr("Up")); Chris@494: m_zoomInAction->setStatusTip(tr("Increase the zoom level")); Chris@494: connect(m_zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn())); Chris@494: connect(this, SIGNAL(canZoom(bool)), m_zoomInAction, SLOT(setEnabled(bool))); Chris@494: m_keyReference->registerShortcut(m_zoomInAction); Chris@494: menu->addAction(m_zoomInAction); Chris@1770: Chris@494: m_zoomOutAction = new QAction(il.load("zoom-out"), Chris@494: tr("Zoom &Out"), this); Chris@494: m_zoomOutAction->setShortcut(tr("Down")); Chris@494: m_zoomOutAction->setStatusTip(tr("Decrease the zoom level")); Chris@494: connect(m_zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut())); Chris@494: connect(this, SIGNAL(canZoom(bool)), m_zoomOutAction, SLOT(setEnabled(bool))); Chris@494: m_keyReference->registerShortcut(m_zoomOutAction); Chris@494: menu->addAction(m_zoomOutAction); Chris@1770: Chris@66: action = new QAction(tr("Restore &Default Zoom"), this); Chris@90: action->setStatusTip(tr("Restore the zoom level to the default")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault())); Chris@66: connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); Chris@66: menu->addAction(action); Chris@66: Chris@494: m_zoomFitAction = new QAction(il.load("zoom-fit"), Chris@494: tr("Zoom to &Fit"), this); Chris@494: m_zoomFitAction->setShortcut(tr("F")); Chris@494: m_zoomFitAction->setStatusTip(tr("Zoom to show the whole file")); Chris@494: connect(m_zoomFitAction, SIGNAL(triggered()), this, SLOT(zoomToFit())); Chris@494: connect(this, SIGNAL(canZoom(bool)), m_zoomFitAction, SLOT(setEnabled(bool))); Chris@494: m_keyReference->registerShortcut(m_zoomFitAction); Chris@494: menu->addAction(m_zoomFitAction); Chris@90: Chris@90: menu->addSeparator(); Chris@90: Chris@162: m_keyReference->setCategory(tr("Display Features")); Chris@162: Chris@497: action = new QAction(tr("Show &Centre Line"), this); Chris@497: action->setShortcut(tr("'")); Chris@497: action->setStatusTip(tr("Show or hide the centre line")); Chris@497: connect(action, SIGNAL(triggered()), this, SLOT(toggleCentreLine())); Chris@497: action->setCheckable(true); Chris@703: action->setChecked(m_viewManager->shouldShowCentreLine()); Chris@497: m_keyReference->registerShortcut(action); Chris@497: menu->addAction(action); Chris@497: Chris@497: action = new QAction(tr("Toggle All Time Rulers"), this); Chris@497: action->setShortcut(tr("#")); Chris@497: action->setStatusTip(tr("Show or hide all time rulers")); Chris@497: connect(action, SIGNAL(triggered()), this, SLOT(toggleTimeRulers())); Chris@497: m_keyReference->registerShortcut(action); Chris@497: menu->addAction(action); Chris@497: Chris@497: menu->addSeparator(); Chris@497: Chris@90: QActionGroup *overlayGroup = new QActionGroup(this); Chris@90: Chris@703: ViewManager::OverlayMode mode = m_viewManager->getOverlayMode(); Chris@703: Chris@90: action = new QAction(tr("Show &No Overlays"), this); Chris@90: action->setShortcut(tr("0")); Chris@497: action->setStatusTip(tr("Hide times, layer names, and scale")); Chris@90: connect(action, SIGNAL(triggered()), this, SLOT(showNoOverlays())); Chris@90: action->setCheckable(true); Chris@703: action->setChecked(mode == ViewManager::NoOverlays); Chris@90: overlayGroup->addAction(action); Chris@162: m_keyReference->registerShortcut(action); Chris@90: menu->addAction(action); Chris@90: Chris@90: action = new QAction(tr("Show &Minimal Overlays"), this); Chris@90: action->setShortcut(tr("9")); Chris@497: action->setStatusTip(tr("Show times and basic scale")); Chris@90: connect(action, SIGNAL(triggered()), this, SLOT(showMinimalOverlays())); Chris@90: action->setCheckable(true); Chris@715: action->setChecked(mode == ViewManager::StandardOverlays); Chris@90: overlayGroup->addAction(action); Chris@162: m_keyReference->registerShortcut(action); Chris@90: menu->addAction(action); Chris@90: Chris@90: action = new QAction(tr("Show &All Overlays"), this); Chris@497: action->setShortcut(tr("8")); Chris@497: action->setStatusTip(tr("Show times, layer names, and scale")); Chris@90: connect(action, SIGNAL(triggered()), this, SLOT(showAllOverlays())); Chris@90: action->setCheckable(true); Chris@703: action->setChecked(mode == ViewManager::AllOverlays); Chris@90: overlayGroup->addAction(action); Chris@162: m_keyReference->registerShortcut(action); Chris@90: menu->addAction(action); Chris@387: Chris@387: menu->addSeparator(); Chris@497: Chris@66: action = new QAction(tr("Show &Zoom Wheels"), this); Chris@66: action->setShortcut(tr("Z")); Chris@66: action->setStatusTip(tr("Show thumbwheels for zooming horizontally and vertically")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(toggleZoomWheels())); Chris@66: action->setCheckable(true); Chris@66: action->setChecked(m_viewManager->getZoomWheelsEnabled()); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@387: Chris@494: m_showPropertyBoxesAction = new QAction(tr("Show Property Bo&xes"), this); Chris@494: m_showPropertyBoxesAction->setShortcut(tr("X")); Chris@494: m_showPropertyBoxesAction->setStatusTip(tr("Show the layer property boxes at the side of the main window")); Chris@494: connect(m_showPropertyBoxesAction, SIGNAL(triggered()), this, SLOT(togglePropertyBoxes())); Chris@494: m_showPropertyBoxesAction->setCheckable(true); Chris@494: m_showPropertyBoxesAction->setChecked(true); Chris@494: m_keyReference->registerShortcut(m_showPropertyBoxesAction); Chris@494: menu->addAction(m_showPropertyBoxesAction); Chris@0: Chris@90: action = new QAction(tr("Show Status &Bar"), this); Chris@90: action->setStatusTip(tr("Show context help information in the status bar at the bottom of the window")); Chris@90: connect(action, SIGNAL(triggered()), this, SLOT(toggleStatusBar())); Chris@90: action->setCheckable(true); Chris@90: action->setChecked(true); Chris@90: menu->addAction(action); Chris@90: Chris@90: QSettings settings; Chris@90: settings.beginGroup("MainWindow"); Chris@90: bool sb = settings.value("showstatusbar", true).toBool(); Chris@90: if (!sb) { Chris@90: action->setChecked(false); Chris@90: statusBar()->hide(); Chris@90: } Chris@90: settings.endGroup(); Chris@90: Chris@66: menu->addSeparator(); Chris@66: Chris@219: action = new QAction(tr("Show La&yer Summary"), this); Chris@219: action->setShortcut(tr("Y")); Chris@90: action->setStatusTip(tr("Open a window displaying the hierarchy of panes and layers in this session")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(showLayerTree())); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@306: Chris@306: action = new QAction(tr("Show Acti&vity Log"), this); Chris@306: action->setStatusTip(tr("Open a window listing interactions and other events")); Chris@306: connect(action, SIGNAL(triggered()), this, SLOT(showActivityLog())); Chris@306: menu->addAction(action); Chris@493: Chris@891: action = new QAction(tr("Show &Unit Converter"), this); Chris@891: action->setStatusTip(tr("Open a window of pitch and timing conversion utilities")); Chris@891: connect(action, SIGNAL(triggered()), this, SLOT(showUnitConverter())); Chris@891: menu->addAction(action); Chris@891: Chris@494: menu->addSeparator(); Chris@494: Chris@1387: #ifndef Q_OS_MAC Chris@1387: // Only on non-Mac platforms -- on the Mac this interacts very Chris@1387: // badly with the "native" full-screen mode Chris@493: action = new QAction(tr("Go Full-Screen"), this); Chris@494: action->setShortcut(tr("F11")); Chris@494: action->setStatusTip(tr("Expand the pane area to the whole screen")); Chris@493: connect(action, SIGNAL(triggered()), this, SLOT(goFullScreen())); Chris@494: m_keyReference->registerShortcut(action); Chris@493: menu->addAction(action); Chris@1387: #endif Chris@66: } Chris@66: Chris@1794: QString Chris@1794: MainWindow::shortcutFor(LayerFactory::LayerType layer, bool isPaneMenu) Chris@1794: { Chris@1794: QString shortcutText; Chris@1794: Chris@1794: #ifdef __GNUC__ Chris@1794: #pragma GCC diagnostic ignored "-Wswitch-enum" Chris@1794: #endif Chris@1794: Chris@1794: switch (layer) { Chris@1794: case LayerFactory::Waveform: Chris@1794: if (isPaneMenu) { Chris@1794: shortcutText = tr("W"); Chris@1794: } else { Chris@1794: shortcutText = tr("Shift+W"); Chris@1794: } Chris@1794: break; Chris@1794: Chris@1794: case LayerFactory::Spectrogram: Chris@1794: if (isPaneMenu) { Chris@1794: shortcutText = tr("G"); Chris@1794: } else { Chris@1794: shortcutText = tr("Shift+G"); Chris@1794: } Chris@1794: break; Chris@1794: Chris@1794: case LayerFactory::MelodicRangeSpectrogram: Chris@1794: if (isPaneMenu) { Chris@1794: shortcutText = tr("M"); Chris@1794: } else { Chris@1794: shortcutText = tr("Shift+M"); Chris@1794: } Chris@1794: break; Chris@1794: Chris@1794: case LayerFactory::PeakFrequencySpectrogram: Chris@1794: if (isPaneMenu) { Chris@1794: shortcutText = tr("K"); Chris@1794: } else { Chris@1794: shortcutText = tr("Shift+K"); Chris@1794: } Chris@1794: break; Chris@1794: Chris@1794: case LayerFactory::Spectrum: Chris@1794: if (isPaneMenu) { Chris@1794: shortcutText = tr("U"); Chris@1794: } else { Chris@1794: shortcutText = tr("Shift+U"); Chris@1794: } Chris@1794: break; Chris@1794: Chris@1794: default: Chris@1794: break; Chris@1794: } Chris@1794: Chris@1794: return shortcutText; Chris@1794: } Chris@1794: Chris@66: void Chris@66: MainWindow::setupPaneAndLayerMenus() Chris@66: { Chris@0: if (m_paneMenu) { Chris@1770: m_paneActions.clear(); Chris@1770: m_paneMenu->clear(); Chris@0: } else { Chris@1770: m_paneMenu = menuBar()->addMenu(tr("&Pane")); Chris@97: m_paneMenu->setTearOffEnabled(true); Chris@0: } Chris@0: Chris@0: if (m_layerMenu) { Chris@1770: m_layerActions.clear(); Chris@1770: m_layerMenu->clear(); Chris@0: } else { Chris@1770: m_layerMenu = menuBar()->addMenu(tr("&Layer")); Chris@97: m_layerMenu->setTearOffEnabled(true); Chris@0: } Chris@0: Chris@345: if (m_rightButtonLayerMenu) { Chris@345: m_rightButtonLayerMenu->clear(); Chris@345: } else { Chris@345: m_rightButtonLayerMenu = m_rightButtonMenu->addMenu(tr("&Layer")); Chris@345: m_rightButtonLayerMenu->setTearOffEnabled(true); Chris@345: m_rightButtonMenu->addSeparator(); Chris@345: } Chris@345: Chris@66: QMenu *menu = m_paneMenu; Chris@66: Chris@168: IconLoader il; Chris@168: Chris@162: m_keyReference->setCategory(tr("Managing Panes and Layers")); Chris@162: Chris@2093: m_paneActions.clear(); Chris@2093: m_layerActions.clear(); Chris@2093: Chris@168: QAction *action = new QAction(il.load("pane"), tr("Add &New Pane"), this); Chris@155: action->setShortcut(tr("N")); Chris@66: action->setStatusTip(tr("Add a new pane containing only a time ruler")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(addPane())); Chris@66: connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool))); Chris@2093: m_paneActions.push_back({ action, LayerConfiguration(LayerFactory::TimeRuler) }); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: Chris@66: menu->addSeparator(); Chris@66: Chris@66: menu = m_layerMenu; Chris@66: Chris@66: LayerFactory::LayerTypeSet emptyLayerTypes = Chris@1770: LayerFactory::getInstance()->getValidEmptyLayerTypes(); Chris@66: Chris@66: for (LayerFactory::LayerTypeSet::iterator i = emptyLayerTypes.begin(); Chris@1770: i != emptyLayerTypes.end(); ++i) { Chris@1770: Chris@1770: QIcon icon; Chris@1770: QString mainText, tipText, channelText; Chris@1770: LayerFactory::LayerType type = *i; Chris@1770: QString name = LayerFactory::getInstance()->getLayerPresentationName(type); Chris@1770: Chris@1770: icon = il.load(LayerFactory::getInstance()->getLayerIconName(type)); Chris@1770: Chris@1770: mainText = tr("Add New %1 Layer").arg(name); Chris@1770: tipText = tr("Add a new empty layer of type %1").arg(name); Chris@1770: Chris@1770: action = new QAction(icon, mainText, this); Chris@1770: action->setStatusTip(tipText); Chris@1770: Chris@1770: if (type == LayerFactory::Text) { Chris@1770: action->setShortcut(tr("T")); Chris@162: m_keyReference->registerShortcut(action); Chris@1770: } Chris@1770: Chris@1770: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@1770: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@2093: m_layerActions.push_back({ action, LayerConfiguration(type) }); Chris@1770: menu->addAction(action); Chris@66: m_rightButtonLayerMenu->addAction(action); Chris@66: } Chris@66: Chris@66: m_rightButtonLayerMenu->addSeparator(); Chris@66: menu->addSeparator(); Chris@66: Chris@66: LayerFactory::LayerType backgroundTypes[] = { Chris@1770: LayerFactory::Waveform, Chris@1770: LayerFactory::Spectrogram, Chris@1770: LayerFactory::MelodicRangeSpectrogram, Chris@1770: LayerFactory::PeakFrequencySpectrogram, Chris@66: LayerFactory::Spectrum Chris@66: }; Chris@2022: int backgroundTypeCount = int(sizeof(backgroundTypes) / Chris@2022: sizeof(backgroundTypes[0])); Chris@66: Chris@2300: std::vector models; Chris@224: if (m_document) models = m_document->getTransformInputModels(); Chris@66: bool plural = (models.size() > 1); Chris@66: if (models.empty()) { Chris@2300: models.push_back(getMainModelId()); // probably None at this point Chris@66: } Chris@66: Chris@2022: for (int i = 0; i < backgroundTypeCount; ++i) { Chris@66: Chris@231: const int paneMenuType = 0, layerMenuType = 1; Chris@231: Chris@1770: for (int menuType = paneMenuType; menuType <= layerMenuType; ++menuType) { Chris@1770: Chris@1770: if (menuType == paneMenuType) menu = m_paneMenu; Chris@1770: else menu = m_layerMenu; Chris@1770: Chris@2126: QMenu *submenu = nullptr; Chris@66: Chris@66: QIcon icon; Chris@1794: QString mainText, tipText, channelText; Chris@66: LayerFactory::LayerType type = backgroundTypes[i]; Chris@66: bool mono = true; Chris@730: Chris@1794: QString shortcutText = shortcutFor(type, menuType == paneMenuType); Chris@1794: Chris@730: // Avoid warnings/errors with -Wextra because we aren't explicitly Chris@730: // handling all layer types (-Wall is OK with this because of the Chris@730: // default but the stricter level insists) Chris@1264: #ifdef __GNUC__ Chris@730: #pragma GCC diagnostic ignored "-Wswitch-enum" Chris@1264: #endif Chris@66: Chris@66: switch (type) { Chris@66: Chris@66: case LayerFactory::Waveform: Chris@168: icon = il.load("waveform"); Chris@66: mainText = tr("Add &Waveform"); Chris@231: if (menuType == paneMenuType) { Chris@66: tipText = tr("Add a new pane showing a waveform view"); Chris@66: } else { Chris@66: tipText = tr("Add a new layer showing a waveform view"); Chris@66: } Chris@66: mono = false; Chris@66: break; Chris@1770: Chris@66: case LayerFactory::Spectrogram: Chris@168: icon = il.load("spectrogram"); Chris@161: mainText = tr("Add Spectro&gram"); Chris@231: if (menuType == paneMenuType) { Chris@90: tipText = tr("Add a new pane showing a spectrogram"); Chris@66: } else { Chris@90: tipText = tr("Add a new layer showing a spectrogram"); Chris@66: } Chris@66: break; Chris@1770: Chris@66: case LayerFactory::MelodicRangeSpectrogram: Chris@168: icon = il.load("spectrogram"); Chris@66: mainText = tr("Add &Melodic Range Spectrogram"); Chris@231: if (menuType == paneMenuType) { Chris@90: tipText = tr("Add a new pane showing a spectrogram set up for an overview of note pitches"); Chris@66: } else { Chris@90: tipText = tr("Add a new layer showing a spectrogram set up for an overview of note pitches"); Chris@66: } Chris@66: break; Chris@1770: Chris@66: case LayerFactory::PeakFrequencySpectrogram: Chris@168: icon = il.load("spectrogram"); Chris@155: mainText = tr("Add Pea&k Frequency Spectrogram"); Chris@231: if (menuType == paneMenuType) { Chris@66: tipText = tr("Add a new pane showing a spectrogram set up for tracking frequencies"); Chris@66: } else { Chris@66: tipText = tr("Add a new layer showing a spectrogram set up for tracking frequencies"); Chris@66: } Chris@66: break; Chris@66: Chris@66: case LayerFactory::Spectrum: Chris@168: icon = il.load("spectrum"); Chris@66: mainText = tr("Add Spectr&um"); Chris@231: if (menuType == paneMenuType) { Chris@66: tipText = tr("Add a new pane showing a frequency spectrum"); Chris@66: } else { Chris@66: tipText = tr("Add a new layer showing a frequency spectrum"); Chris@66: } Chris@66: break; Chris@66: Chris@66: default: break; Chris@66: } Chris@66: Chris@2300: std::vector candidateModels = models; Chris@2300: if (candidateModels.empty()) { Chris@2300: throw std::logic_error("candidateModels should not be empty"); Chris@2300: } Chris@66: Chris@2300: for (auto modelId: candidateModels) { Chris@2300: Chris@2300: auto model = ModelById::get(modelId); Chris@66: Chris@66: int channels = 0; Chris@66: if (model) { Chris@2300: if (auto dtvm = ModelById::getAs Chris@2300: (modelId)) { Chris@2300: channels = dtvm->getChannelCount(); Chris@2300: } Chris@66: } Chris@66: if (channels < 1 && getMainModel()) { Chris@66: channels = getMainModel()->getChannelCount(); Chris@66: } Chris@66: if (channels < 1) channels = 1; Chris@66: Chris@66: for (int c = 0; c <= channels; ++c) { Chris@66: Chris@66: if (c == 1 && channels == 1) continue; Chris@66: bool isDefault = (c == 0); Chris@66: bool isOnly = (isDefault && (channels == 1)); Chris@66: Chris@346: if (isOnly && !plural) { Chris@346: Chris@346: action = new QAction(icon, mainText, this); Chris@67: Chris@66: action->setShortcut(shortcutText); Chris@66: action->setStatusTip(tipText); Chris@231: if (menuType == paneMenuType) { Chris@66: connect(action, SIGNAL(triggered()), Chris@66: this, SLOT(addPane())); Chris@66: connect(this, SIGNAL(canAddPane(bool)), Chris@66: action, SLOT(setEnabled(bool))); Chris@2093: m_paneActions.push_back Chris@2300: ({ action, LayerConfiguration(type, modelId) }); Chris@66: } else { Chris@66: connect(action, SIGNAL(triggered()), Chris@66: this, SLOT(addLayer())); Chris@66: connect(this, SIGNAL(canAddLayer(bool)), Chris@66: action, SLOT(setEnabled(bool))); Chris@2093: m_layerActions.push_back Chris@2300: ({ action, LayerConfiguration(type, modelId) }); Chris@66: } Chris@162: if (shortcutText != "") { Chris@162: m_keyReference->registerShortcut(action); Chris@162: } Chris@66: menu->addAction(action); Chris@66: Chris@66: } else { Chris@66: Chris@66: if (!submenu) { Chris@66: submenu = menu->addMenu(mainText); Chris@97: submenu->setTearOffEnabled(true); Chris@67: } else if (isDefault) { Chris@67: submenu->addSeparator(); Chris@66: } Chris@66: Chris@66: QString actionText; Chris@66: if (c == 0) { Chris@66: if (mono) { Chris@66: actionText = tr("&All Channels Mixed"); Chris@66: } else { Chris@66: actionText = tr("&All Channels"); Chris@66: } Chris@66: } else { Chris@66: actionText = tr("Channel &%1").arg(c); Chris@66: } Chris@66: Chris@66: if (model) { Chris@66: actionText = tr("%1: %2") Chris@66: .arg(model->objectName()) Chris@66: .arg(actionText); Chris@66: } Chris@67: Chris@67: if (isDefault) { Chris@67: action = new QAction(icon, actionText, this); Chris@2300: if (!model || modelId == getMainModelId()) { Chris@2093: // Default for the shortcut is to Chris@2093: // attach to an action that uses the Chris@2093: // main model as input. But this may Chris@2093: // change when the user selects a Chris@2093: // different pane - see Chris@2093: // updateLayerShortcutsFor() below. Chris@162: action->setShortcut(shortcutText); Chris@67: } Chris@67: } else { Chris@67: action = new QAction(actionText, this); Chris@67: } Chris@67: Chris@66: action->setStatusTip(tipText); Chris@66: Chris@231: if (menuType == paneMenuType) { Chris@66: connect(action, SIGNAL(triggered()), Chris@66: this, SLOT(addPane())); Chris@66: connect(this, SIGNAL(canAddPane(bool)), Chris@66: action, SLOT(setEnabled(bool))); Chris@2093: m_paneActions.push_back Chris@2300: ({ action, LayerConfiguration(type, modelId, c - 1) }); Chris@66: } else { Chris@66: connect(action, SIGNAL(triggered()), Chris@66: this, SLOT(addLayer())); Chris@66: connect(this, SIGNAL(canAddLayer(bool)), Chris@66: action, SLOT(setEnabled(bool))); Chris@2093: m_layerActions.push_back Chris@2300: ({ action, LayerConfiguration(type, modelId, c - 1) }); Chris@66: } Chris@66: Chris@66: submenu->addAction(action); Chris@66: } Chris@346: Chris@415: if (isDefault && menuType == layerMenuType && Chris@2300: modelId == *candidateModels.begin()) { Chris@415: // only add for one model, one channel, one menu on Chris@415: // right button -- the action itself will discover Chris@415: // which model is the correct one (based on pane) Chris@346: action = new QAction(icon, mainText, this); Chris@346: action->setStatusTip(tipText); Chris@346: connect(action, SIGNAL(triggered()), Chris@346: this, SLOT(addLayer())); Chris@346: connect(this, SIGNAL(canAddLayer(bool)), Chris@346: action, SLOT(setEnabled(bool))); Chris@2093: m_layerActions.push_back Chris@2300: ({ action, LayerConfiguration(type, ModelId(), 0) }); Chris@346: m_rightButtonLayerMenu->addAction(action); Chris@346: } Chris@1770: } Chris@1770: } Chris@1770: } Chris@66: } Chris@66: Chris@347: m_rightButtonLayerMenu->addSeparator(); Chris@347: Chris@66: menu = m_paneMenu; Chris@225: menu->addSeparator(); Chris@225: Chris@225: action = new QAction(tr("Switch to Previous Pane"), this); Chris@225: action->setShortcut(tr("[")); Chris@225: action->setStatusTip(tr("Make the next pane up in the pane stack current")); Chris@225: connect(action, SIGNAL(triggered()), this, SLOT(previousPane())); Chris@225: connect(this, SIGNAL(canSelectPreviousPane(bool)), action, SLOT(setEnabled(bool))); Chris@225: m_keyReference->registerShortcut(action); Chris@225: menu->addAction(action); Chris@225: Chris@225: action = new QAction(tr("Switch to Next Pane"), this); Chris@225: action->setShortcut(tr("]")); Chris@225: action->setStatusTip(tr("Make the next pane down in the pane stack current")); Chris@225: connect(action, SIGNAL(triggered()), this, SLOT(nextPane())); Chris@225: connect(this, SIGNAL(canSelectNextPane(bool)), action, SLOT(setEnabled(bool))); Chris@225: m_keyReference->registerShortcut(action); Chris@225: menu->addAction(action); Chris@66: Chris@66: menu->addSeparator(); Chris@66: Chris@168: action = new QAction(il.load("editdelete"), tr("&Delete Pane"), this); Chris@155: action->setShortcut(tr("Ctrl+Shift+D")); Chris@90: action->setStatusTip(tr("Delete the currently active pane")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentPane())); Chris@66: connect(this, SIGNAL(canDeleteCurrentPane(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: Chris@66: menu = m_layerMenu; Chris@66: Chris@168: action = new QAction(il.load("timeruler"), tr("Add &Time Ruler"), this); Chris@66: action->setStatusTip(tr("Add a new layer showing a time ruler")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@66: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@2093: m_layerActions.push_back({ action, LayerConfiguration(LayerFactory::TimeRuler) }); Chris@66: menu->addAction(action); Chris@66: Chris@66: menu->addSeparator(); Chris@66: Chris@66: m_existingLayersMenu = menu->addMenu(tr("Add &Existing Layer")); Chris@97: m_existingLayersMenu->setTearOffEnabled(true); Chris@66: m_rightButtonLayerMenu->addMenu(m_existingLayersMenu); Chris@95: Chris@95: m_sliceMenu = menu->addMenu(tr("Add S&lice of Layer")); Chris@97: m_sliceMenu->setTearOffEnabled(true); Chris@95: m_rightButtonLayerMenu->addMenu(m_sliceMenu); Chris@95: Chris@95: setupExistingLayersMenus(); Chris@66: Chris@225: menu->addSeparator(); Chris@225: Chris@225: action = new QAction(tr("Switch to Previous Layer"), this); Chris@225: action->setShortcut(tr("{")); Chris@225: action->setStatusTip(tr("Make the previous layer in the pane current")); Chris@225: connect(action, SIGNAL(triggered()), this, SLOT(previousLayer())); Chris@225: connect(this, SIGNAL(canSelectPreviousLayer(bool)), action, SLOT(setEnabled(bool))); Chris@225: m_keyReference->registerShortcut(action); Chris@225: menu->addAction(action); Chris@225: Chris@225: action = new QAction(tr("Switch to Next Layer"), this); Chris@225: action->setShortcut(tr("}")); Chris@225: action->setStatusTip(tr("Make the next layer in the pane current")); Chris@225: connect(action, SIGNAL(triggered()), this, SLOT(nextLayer())); Chris@225: connect(this, SIGNAL(canSelectNextLayer(bool)), action, SLOT(setEnabled(bool))); Chris@225: m_keyReference->registerShortcut(action); Chris@225: menu->addAction(action); Chris@785: Chris@66: m_rightButtonLayerMenu->addSeparator(); Chris@66: menu->addSeparator(); Chris@66: Chris@163: QAction *raction = new QAction(tr("&Rename Layer..."), this); Chris@163: raction->setShortcut(tr("R")); Chris@163: raction->setStatusTip(tr("Rename the currently active layer")); Chris@163: connect(raction, SIGNAL(triggered()), this, SLOT(renameCurrentLayer())); Chris@163: connect(this, SIGNAL(canRenameLayer(bool)), raction, SLOT(setEnabled(bool))); Chris@163: menu->addAction(raction); Chris@163: m_rightButtonLayerMenu->addAction(raction); Chris@66: Chris@258: QAction *eaction = new QAction(tr("Edit Layer Data"), this); Chris@258: eaction->setShortcut(tr("E")); Chris@258: eaction->setStatusTip(tr("Edit the currently active layer as a data grid")); Chris@258: connect(eaction, SIGNAL(triggered()), this, SLOT(editCurrentLayer())); Chris@291: connect(this, SIGNAL(canEditLayerTabular(bool)), eaction, SLOT(setEnabled(bool))); Chris@258: menu->addAction(eaction); Chris@258: m_rightButtonLayerMenu->addAction(eaction); Chris@258: Chris@168: action = new QAction(il.load("editdelete"), tr("&Delete Layer"), this); Chris@155: action->setShortcut(tr("Ctrl+D")); Chris@66: action->setStatusTip(tr("Delete the currently active layer")); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentLayer())); Chris@66: connect(this, SIGNAL(canDeleteCurrentLayer(bool)), action, SLOT(setEnabled(bool))); Chris@162: m_keyReference->registerShortcut(action); Chris@66: menu->addAction(action); Chris@66: m_rightButtonLayerMenu->addAction(action); Chris@163: Chris@163: m_keyReference->registerShortcut(raction); // rename after delete, so delete layer goes next to delete pane Chris@258: m_keyReference->registerShortcut(eaction); // edit also after delete Chris@755: Chris@755: finaliseMenus(); Chris@66: } Chris@66: Chris@66: void Chris@2300: MainWindow::updateLayerShortcutsFor(ModelId modelId) Chris@1794: { Chris@2093: // Called when e.g. the current pane has changed, to ensure the Chris@2093: // various layer shortcuts select an action whose input model is Chris@2093: // the active one in this pane Chris@2093: Chris@1794: set seen; Chris@1794: Chris@1794: for (auto &a : m_paneActions) { Chris@2300: if (a.second.sourceModel.isNone()) { Chris@2300: continue; // empty pane/layer shortcut Chris@2300: } Chris@1794: auto type = a.second.layer; Chris@2300: if (a.second.sourceModel == modelId && seen.find(type) == seen.end()) { Chris@1794: a.first->setShortcut(shortcutFor(type, true)); Chris@1794: seen.insert(type); Chris@1794: } else { Chris@1794: a.first->setShortcut(QString()); Chris@1794: } Chris@1794: } Chris@1794: Chris@1794: seen.clear(); Chris@1794: Chris@1794: for (auto &a : m_layerActions) { Chris@2300: if (a.second.sourceModel.isNone()) { Chris@2300: continue; // empty pane/layer shortcut Chris@2300: } Chris@1794: auto type = a.second.layer; Chris@2300: if (a.second.sourceModel == modelId && seen.find(type) == seen.end()) { Chris@1794: a.first->setShortcut(shortcutFor(type, false)); Chris@1794: seen.insert(type); Chris@1794: } else { Chris@1794: a.first->setShortcut(QString()); Chris@1794: } Chris@1794: } Chris@1794: } Chris@1794: Chris@1794: void Chris@211: MainWindow::setupTransformsMenu() Chris@66: { Chris@34: if (m_transformsMenu) { Chris@34: m_transformActions.clear(); Chris@34: m_transformActionsReverse.clear(); Chris@34: m_transformsMenu->clear(); Chris@34: } else { Chris@1770: m_transformsMenu = menuBar()->addMenu(tr("&Transform")); Chris@97: m_transformsMenu->setTearOffEnabled(true); Chris@286: m_transformsMenu->setSeparatorsCollapsible(true); Chris@272: } Chris@34: Chris@288: TransformFactory *factory = TransformFactory::getInstance(); Chris@288: Chris@288: TransformList transforms = factory->getAllTransformDescriptions(); Chris@1277: Chris@1277: if (factory->getStartupFailureReport() != "") { Chris@1277: pluginPopulationWarning(); Chris@1277: } Chris@1277: Chris@288: vector types = factory->getAllTransformTypes(); Chris@288: Chris@288: map > categoryMenus; Chris@288: map > makerMenus; Chris@288: Chris@288: map byPluginNameMenus; Chris@288: map > pluginNameMenus; Chris@33: Chris@37: set pendingMenus; Chris@37: Chris@211: m_recentTransformsMenu = m_transformsMenu->addMenu(tr("&Recent Transforms")); Chris@211: m_recentTransformsMenu->setTearOffEnabled(true); Chris@211: m_rightButtonTransformsMenu->addMenu(m_recentTransformsMenu); Chris@211: connect(&m_recentTransforms, SIGNAL(recentChanged()), Chris@211: this, SLOT(setupRecentTransformsMenu())); Chris@34: Chris@34: m_transformsMenu->addSeparator(); Chris@211: m_rightButtonTransformsMenu->addSeparator(); Chris@34: Chris@288: for (vector::iterator i = types.begin(); Chris@288: i != types.end(); ++i) { Chris@33: Chris@33: if (i != types.begin()) { Chris@34: m_transformsMenu->addSeparator(); Chris@211: m_rightButtonTransformsMenu->addSeparator(); Chris@33: } Chris@33: Chris@288: QString byCategoryLabel = tr("%1 by Category") Chris@288: .arg(factory->getTransformTypeName(*i)); Chris@37: SubdividingMenu *byCategoryMenu = new SubdividingMenu(byCategoryLabel, Chris@37: 20, 40); Chris@97: byCategoryMenu->setTearOffEnabled(true); Chris@37: m_transformsMenu->addMenu(byCategoryMenu); Chris@211: m_rightButtonTransformsMenu->addMenu(byCategoryMenu); Chris@37: pendingMenus.insert(byCategoryMenu); Chris@33: Chris@288: vector categories = factory->getTransformCategories(*i); Chris@33: Chris@33: for (vector::iterator j = categories.begin(); Chris@33: j != categories.end(); ++j) { Chris@33: Chris@33: QString category = *j; Chris@33: if (category == "") category = tr("Unclassified"); Chris@33: Chris@33: if (categories.size() < 2) { Chris@33: categoryMenus[*i][category] = byCategoryMenu; Chris@33: continue; Chris@33: } Chris@33: Chris@33: QStringList components = category.split(" > "); Chris@33: QString key; Chris@33: Chris@33: for (QStringList::iterator k = components.begin(); Chris@33: k != components.end(); ++k) { Chris@33: Chris@33: QString parentKey = key; Chris@33: if (key != "") key += " > "; Chris@33: key += *k; Chris@33: Chris@33: if (categoryMenus[*i].find(key) == categoryMenus[*i].end()) { Chris@37: SubdividingMenu *m = new SubdividingMenu(*k, 20, 40); Chris@97: m->setTearOffEnabled(true); Chris@37: pendingMenus.insert(m); Chris@37: categoryMenus[*i][key] = m; Chris@33: if (parentKey == "") { Chris@37: byCategoryMenu->addMenu(m); Chris@33: } else { Chris@37: categoryMenus[*i][parentKey]->addMenu(m); Chris@33: } Chris@33: } Chris@33: } Chris@33: } Chris@33: Chris@288: QString byPluginNameLabel = tr("%1 by Plugin Name") Chris@288: .arg(factory->getTransformTypeName(*i)); Chris@36: byPluginNameMenus[*i] = new SubdividingMenu(byPluginNameLabel); Chris@97: byPluginNameMenus[*i]->setTearOffEnabled(true); Chris@36: m_transformsMenu->addMenu(byPluginNameMenus[*i]); Chris@211: m_rightButtonTransformsMenu->addMenu(byPluginNameMenus[*i]); Chris@37: pendingMenus.insert(byPluginNameMenus[*i]); Chris@34: Chris@288: QString byMakerLabel = tr("%1 by Maker") Chris@288: .arg(factory->getTransformTypeName(*i)); Chris@37: SubdividingMenu *byMakerMenu = new SubdividingMenu(byMakerLabel, 20, 40); Chris@97: byMakerMenu->setTearOffEnabled(true); Chris@37: m_transformsMenu->addMenu(byMakerMenu); Chris@211: m_rightButtonTransformsMenu->addMenu(byMakerMenu); Chris@37: pendingMenus.insert(byMakerMenu); Chris@33: Chris@288: vector makers = factory->getTransformMakers(*i); Chris@37: Chris@33: for (vector::iterator j = makers.begin(); Chris@33: j != makers.end(); ++j) { Chris@33: Chris@33: QString maker = *j; Chris@33: if (maker == "") maker = tr("Unknown"); Chris@55: maker.replace(QRegExp(tr(" [\\(<].*$")), ""); Chris@55: Chris@37: makerMenus[*i][maker] = new SubdividingMenu(maker, 30, 40); Chris@97: makerMenus[*i][maker]->setTearOffEnabled(true); Chris@37: byMakerMenu->addMenu(makerMenus[*i][maker]); Chris@37: pendingMenus.insert(makerMenus[*i][maker]); Chris@33: } Chris@0: } Chris@0: Chris@230: // Names should only be duplicated here if they have the same Chris@230: // plugin name, output name and maker but are in different library Chris@230: // .so names -- that won't happen often I hope Chris@230: std::map idNameSonameMap; Chris@230: std::set seenNames, duplicateNames; Chris@2022: for (int i = 0; in_range_for(transforms, i); ++i) { Chris@230: QString name = transforms[i].name; Chris@230: if (seenNames.find(name) != seenNames.end()) { Chris@230: duplicateNames.insert(name); Chris@230: } else { Chris@230: seenNames.insert(name); Chris@230: } Chris@230: } Chris@230: Chris@2093: m_transformActions.clear(); Chris@2093: m_transformActionsReverse.clear(); Chris@2093: Chris@2022: for (int i = 0; in_range_for(transforms, i); ++i) { Chris@1770: Chris@1770: QString name = transforms[i].name; Chris@1770: if (name == "") name = transforms[i].identifier; Chris@107: Chris@665: // cerr << "Plugin Name: " << name << endl; Chris@80: Chris@288: TransformDescription::Type type = transforms[i].type; Chris@288: QString typeStr = factory->getTransformTypeName(type); Chris@33: Chris@33: QString category = transforms[i].category; Chris@33: if (category == "") category = tr("Unclassified"); Chris@33: Chris@33: QString maker = transforms[i].maker; Chris@33: if (maker == "") maker = tr("Unknown"); Chris@55: maker.replace(QRegExp(tr(" [\\(<].*$")), ""); Chris@33: Chris@107: QString pluginName = name.section(": ", 0, 0); Chris@107: QString output = name.section(": ", 1); Chris@107: Chris@230: if (duplicateNames.find(pluginName) != duplicateNames.end()) { Chris@230: pluginName = QString("%1 <%2>") Chris@230: .arg(pluginName) Chris@230: .arg(transforms[i].identifier.section(':', 1, 1)); Chris@230: if (output == "") name = pluginName; Chris@230: else name = QString("%1: %2") Chris@230: .arg(pluginName) Chris@230: .arg(output); Chris@230: } Chris@230: Chris@1770: QAction *action = new QAction(tr("%1...").arg(name), this); Chris@1770: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@2093: m_transformActions.push_back({ action, transforms[i].identifier }); Chris@107: m_transformActionsReverse[transforms[i].identifier] = action; Chris@1770: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@33: Chris@272: action->setStatusTip(transforms[i].longDescription); Chris@90: Chris@33: if (categoryMenus[type].find(category) == categoryMenus[type].end()) { Chris@665: cerr << "WARNING: MainWindow::setupMenus: Internal error: " Chris@33: << "No category menu for transform \"" Chris@432: << name << "\" (category = \"" Chris@665: << category << "\")" << endl; Chris@33: } else { Chris@33: categoryMenus[type][category]->addAction(action); Chris@33: } Chris@33: Chris@33: if (makerMenus[type].find(maker) == makerMenus[type].end()) { Chris@665: cerr << "WARNING: MainWindow::setupMenus: Internal error: " Chris@33: << "No maker menu for transform \"" Chris@432: << name << "\" (maker = \"" Chris@665: << maker << "\")" << endl; Chris@33: } else { Chris@80: makerMenus[type][maker]->addAction(action); Chris@33: } Chris@33: Chris@33: action = new QAction(tr("%1...").arg(output == "" ? pluginName : output), this); Chris@33: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@2093: m_transformActions.push_back({ action, transforms[i].identifier }); Chris@33: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@272: action->setStatusTip(transforms[i].longDescription); Chris@33: Chris@432: // cerr << "Transform: \"" << name << "\": plugin name \"" << pluginName << "\"" << endl; Chris@34: Chris@33: if (pluginNameMenus[type].find(pluginName) == Chris@33: pluginNameMenus[type].end()) { Chris@33: Chris@36: SubdividingMenu *parentMenu = byPluginNameMenus[type]; Chris@97: parentMenu->setTearOffEnabled(true); Chris@34: Chris@33: if (output == "") { Chris@36: parentMenu->addAction(pluginName, action); Chris@33: } else { Chris@34: pluginNameMenus[type][pluginName] = Chris@34: parentMenu->addMenu(pluginName); Chris@33: connect(this, SIGNAL(canAddLayer(bool)), Chris@33: pluginNameMenus[type][pluginName], Chris@33: SLOT(setEnabled(bool))); Chris@33: } Chris@33: } Chris@33: Chris@33: if (pluginNameMenus[type].find(pluginName) != Chris@33: pluginNameMenus[type].end()) { Chris@33: pluginNameMenus[type][pluginName]->addAction(action); Chris@33: } Chris@0: } Chris@0: Chris@37: for (set::iterator i = pendingMenus.begin(); Chris@37: i != pendingMenus.end(); ++i) { Chris@37: (*i)->entriesAdded(); Chris@37: } Chris@37: Chris@273: m_transformsMenu->addSeparator(); Chris@273: m_rightButtonTransformsMenu->addSeparator(); Chris@273: Chris@273: QAction *action = new QAction(tr("Find a Transform..."), this); Chris@273: action->setStatusTip(tr("Search for a transform from the installed plugins, by name or description")); Chris@275: action->setShortcut(tr("Ctrl+M")); Chris@273: connect(action, SIGNAL(triggered()), this, SLOT(findTransform())); Chris@287: // connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@275: m_keyReference->registerShortcut(action); Chris@273: m_transformsMenu->addAction(action); Chris@273: m_rightButtonTransformsMenu->addAction(action); Chris@273: Chris@211: setupRecentTransformsMenu(); Chris@66: } Chris@66: Chris@66: void Chris@66: MainWindow::setupHelpMenu() Chris@66: { Chris@66: QMenu *menu = menuBar()->addMenu(tr("&Help")); Chris@97: menu->setTearOffEnabled(true); Chris@66: Chris@162: m_keyReference->setCategory(tr("Help")); Chris@162: Chris@168: IconLoader il; Chris@168: Chris@518: QString name = QApplication::applicationName(); Chris@518: Chris@168: QAction *action = new QAction(il.load("help"), Chris@138: tr("&Help Reference"), this); Chris@162: action->setShortcut(tr("F1")); Chris@518: action->setStatusTip(tr("Open the %1 reference manual").arg(name)); Chris@66: connect(action, SIGNAL(triggered()), this, SLOT(help())); Chris@162: m_keyReference->registerShortcut(action); Chris@0: menu->addAction(action); Chris@162: Chris@163: action = new QAction(tr("&Key and Mouse Reference"), this); Chris@162: action->setShortcut(tr("F2")); Chris@518: action->setStatusTip(tr("Open a window showing the keystrokes you can use in %1").arg(name)); Chris@162: connect(action, SIGNAL(triggered()), this, SLOT(keyReference())); Chris@162: m_keyReference->registerShortcut(action); Chris@162: menu->addAction(action); Chris@66: Chris@2038: action = new QAction(tr("What's &New In This Release?"), this); Chris@2038: action->setStatusTip(tr("List the changes in this release (and every previous release) of %1").arg(name)); Chris@1516: connect(action, SIGNAL(triggered()), this, SLOT(whatsNew())); Chris@1516: menu->addAction(action); Chris@1516: Chris@518: action = new QAction(tr("&About %1").arg(name), this); Chris@518: action->setStatusTip(tr("Show information about %1").arg(name)); Chris@66: 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@34: vector files = m_recentFiles.getRecent(); Chris@0: for (size_t i = 0; i < files.size(); ++i) { Chris@2149: QString path = files[i]; Chris@1253: QAction *action = new QAction(path, this); Chris@2149: action->setObjectName(path); Chris@2149: connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); Chris@162: if (i == 0) { Chris@162: action->setShortcut(tr("Ctrl+R")); Chris@162: m_keyReference->registerShortcut Chris@163: (tr("Re-open"), Chris@528: action->shortcut().toString(), Chris@163: tr("Re-open the current or most recently opened file")); Chris@162: } Chris@1770: m_recentFilesMenu->addAction(action); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@423: MainWindow::setupTemplatesMenu() Chris@423: { Chris@423: m_templatesMenu->clear(); Chris@423: Chris@455: QAction *defaultAction = new QAction(tr("Standard Waveform"), this); Chris@435: defaultAction->setObjectName("default"); Chris@435: connect(defaultAction, SIGNAL(triggered()), this, SLOT(applyTemplate())); Chris@435: m_templatesMenu->addAction(defaultAction); Chris@435: Chris@435: m_templatesMenu->addSeparator(); Chris@435: Chris@2126: QAction *action = nullptr; Chris@435: Chris@435: QStringList templates = ResourceFinder().getResourceFiles("templates", "svt"); Chris@435: Chris@436: bool havePersonal = false; Chris@436: Chris@435: // (ordered by name) Chris@435: std::set byName; Chris@435: foreach (QString t, templates) { Chris@436: if (!t.startsWith(":")) havePersonal = true; Chris@435: byName.insert(QFileInfo(t).baseName()); Chris@435: } Chris@435: Chris@435: foreach (QString t, byName) { Chris@435: if (t.toLower() == "default") continue; Chris@435: action = new QAction(t, this); Chris@435: connect(action, SIGNAL(triggered()), this, SLOT(applyTemplate())); Chris@435: m_templatesMenu->addAction(action); Chris@435: } Chris@435: Chris@435: if (!templates.empty()) m_templatesMenu->addSeparator(); Chris@435: Chris@435: if (!m_templateWatcher) { Chris@435: m_templateWatcher = new QFileSystemWatcher(this); Chris@435: m_templateWatcher->addPath(ResourceFinder().getResourceSaveDir("templates")); Chris@435: connect(m_templateWatcher, SIGNAL(directoryChanged(const QString &)), Chris@435: this, SLOT(setupTemplatesMenu())); Chris@435: } Chris@436: Chris@436: QAction *setDefaultAction = new QAction(tr("Choose Default Template..."), this); Chris@436: setDefaultAction->setObjectName("set_default_template"); Chris@436: connect(setDefaultAction, SIGNAL(triggered()), this, SLOT(preferences())); Chris@436: m_templatesMenu->addSeparator(); Chris@436: m_templatesMenu->addAction(setDefaultAction); Chris@436: Chris@436: m_manageTemplatesAction->setEnabled(havePersonal); Chris@435: } Chris@435: Chris@423: Chris@423: void Chris@211: MainWindow::setupRecentTransformsMenu() Chris@34: { Chris@211: m_recentTransformsMenu->clear(); Chris@211: vector transforms = m_recentTransforms.getRecent(); Chris@34: for (size_t i = 0; i < transforms.size(); ++i) { Chris@211: TransformActionReverseMap::iterator ti = Chris@34: m_transformActionsReverse.find(transforms[i]); Chris@34: if (ti == m_transformActionsReverse.end()) { Chris@665: cerr << "WARNING: MainWindow::setupRecentTransformsMenu: " Chris@665: << "Unknown transform \"" << transforms[i] Chris@665: << "\" in recent transforms list" << endl; Chris@34: continue; Chris@34: } Chris@162: if (i == 0) { Chris@162: ti->second->setShortcut(tr("Ctrl+T")); Chris@162: m_keyReference->registerShortcut Chris@211: (tr("Repeat Transform"), Chris@528: ti->second->shortcut().toString(), Chris@163: tr("Re-select the most recently run transform")); Chris@216: } else { Chris@216: ti->second->setShortcut(QString("")); Chris@162: } Chris@1770: m_recentTransformsMenu->addAction(ti->second); Chris@34: } Chris@34: } Chris@34: Chris@34: void Chris@95: MainWindow::setupExistingLayersMenus() Chris@0: { Chris@0: if (!m_existingLayersMenu) return; // should have been created by setupMenus Chris@0: Chris@438: // SVDEBUG << "MainWindow::setupExistingLayersMenu" << endl; Chris@0: Chris@0: m_existingLayersMenu->clear(); Chris@0: m_existingLayerActions.clear(); Chris@0: Chris@95: m_sliceMenu->clear(); Chris@95: m_sliceActions.clear(); Chris@95: Chris@168: IconLoader il; Chris@168: Chris@33: vector orderedLayers; Chris@33: set observedLayers; Chris@95: set sliceableLayers; Chris@95: Chris@95: LayerFactory *factory = LayerFactory::getInstance(); Chris@0: Chris@0: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@0: Chris@1770: Pane *pane = m_paneStack->getPane(i); Chris@1770: if (!pane) continue; Chris@1770: Chris@1770: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@1770: Chris@1770: Layer *layer = pane->getLayer(j); Chris@1770: if (!layer) continue; Chris@1770: if (observedLayers.find(layer) != observedLayers.end()) { Chris@1770: // cerr << "found duplicate layer " << layer << endl; Chris@1770: continue; Chris@1770: } Chris@1770: Chris@1770: // cerr << "found new layer " << layer << " (name = " Chris@1770: // << layer->getLayerPresentationName() << ")" << endl; Chris@1770: Chris@1770: orderedLayers.push_back(layer); Chris@1770: observedLayers.insert(layer); Chris@95: Chris@95: if (factory->isLayerSliceable(layer)) { Chris@95: sliceableLayers.insert(layer); Chris@95: } Chris@1770: } Chris@0: } Chris@0: Chris@33: map observedNames; Chris@0: Chris@137: for (size_t i = 0; i < orderedLayers.size(); ++i) { Chris@1770: Chris@95: Layer *layer = orderedLayers[i]; Chris@95: Chris@1770: QString name = layer->getLayerPresentationName(); Chris@1770: int n = ++observedNames[name]; Chris@1770: if (n > 1) name = QString("%1 <%2>").arg(name).arg(n); Chris@1770: Chris@1770: QIcon icon = il.load(factory->getLayerIconName Chris@168: (factory->getLayerType(layer))); Chris@95: Chris@1770: QAction *action = new QAction(icon, name, this); Chris@1770: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@1770: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@2093: m_existingLayerActions.push_back({ action, layer }); Chris@1770: Chris@1770: m_existingLayersMenu->addAction(action); Chris@95: Chris@95: if (sliceableLayers.find(layer) != sliceableLayers.end()) { Chris@95: action = new QAction(icon, name, this); Chris@95: connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); Chris@95: connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); Chris@2093: m_sliceActions.push_back({ action, layer }); Chris@95: m_sliceMenu->addAction(action); Chris@95: } Chris@0: } Chris@95: Chris@95: m_sliceMenu->setEnabled(!m_sliceActions.empty()); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::setupToolbars() Chris@0: { Chris@162: m_keyReference->setCategory(tr("Playback and Transport Controls")); Chris@162: Chris@168: IconLoader il; Chris@168: Chris@155: QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back")); Chris@155: menu->setTearOffEnabled(true); Chris@155: m_rightButtonMenu->addSeparator(); Chris@155: m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback")); Chris@155: Chris@155: QToolBar *toolbar = addToolBar(tr("Playback Toolbar")); Chris@155: Chris@265: m_rwdStartAction = toolbar->addAction(il.load("rewind-start"), Chris@265: tr("Rewind to Start")); Chris@265: m_rwdStartAction->setShortcut(tr("Home")); Chris@265: m_rwdStartAction->setStatusTip(tr("Rewind to the start")); Chris@265: connect(m_rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart())); Chris@265: connect(this, SIGNAL(canPlay(bool)), m_rwdStartAction, SLOT(setEnabled(bool))); Chris@265: Chris@265: m_rwdAction = toolbar->addAction(il.load("rewind"), tr("Rewind")); Chris@155: m_rwdAction->setShortcut(tr("PgUp")); Chris@163: m_rwdAction->setStatusTip(tr("Rewind to the previous time instant or time ruler notch")); Chris@155: connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind())); Chris@155: connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool))); Chris@155: Chris@323: m_rwdSimilarAction = new QAction(tr("Rewind to Similar Point"), this); Chris@323: m_rwdSimilarAction->setShortcut(tr("Shift+PgUp")); Chris@323: m_rwdSimilarAction->setStatusTip(tr("Rewind to the previous similarly valued time instant")); Chris@323: connect(m_rwdSimilarAction, SIGNAL(triggered()), this, SLOT(rewindSimilar())); Chris@323: connect(this, SIGNAL(canRewind(bool)), m_rwdSimilarAction, SLOT(setEnabled(bool))); Chris@323: Chris@265: m_playAction = toolbar->addAction(il.load("playpause"), Chris@265: tr("Play / Pause")); Chris@265: m_playAction->setCheckable(true); Chris@1790: Chris@1790: /*: This text is a shortcut label referring to the space-bar on Chris@1790: the keyboard. It probably should not be translated, and Chris@1790: certainly should not be translated as if referring to an empty Chris@1790: void or to the extra-terrestrial universe. Chris@1790: */ Chris@265: m_playAction->setShortcut(tr("Space")); Chris@1790: Chris@265: m_playAction->setStatusTip(tr("Start or stop playback from the current position")); Chris@265: connect(m_playAction, SIGNAL(triggered()), this, SLOT(play())); Chris@0: connect(m_playSource, SIGNAL(playStatusChanged(bool)), Chris@1770: m_playAction, SLOT(setChecked(bool))); Chris@305: connect(m_playSource, SIGNAL(playStatusChanged(bool)), Chris@305: this, SLOT(playStatusChanged(bool))); Chris@265: connect(this, SIGNAL(canPlay(bool)), m_playAction, SLOT(setEnabled(bool))); Chris@155: Chris@168: m_ffwdAction = toolbar->addAction(il.load("ffwd"), Chris@286: tr("Fast Forward")); Chris@155: m_ffwdAction->setShortcut(tr("PgDown")); Chris@163: m_ffwdAction->setStatusTip(tr("Fast-forward to the next time instant or time ruler notch")); Chris@155: connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd())); Chris@155: connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool))); Chris@155: Chris@323: m_ffwdSimilarAction = new QAction(tr("Fast Forward to Similar Point"), this); Chris@323: m_ffwdSimilarAction->setShortcut(tr("Shift+PgDown")); Chris@323: m_ffwdSimilarAction->setStatusTip(tr("Fast-forward to the next similarly valued time instant")); Chris@323: connect(m_ffwdSimilarAction, SIGNAL(triggered()), this, SLOT(ffwdSimilar())); Chris@323: connect(this, SIGNAL(canFfwd(bool)), m_ffwdSimilarAction, SLOT(setEnabled(bool))); Chris@323: Chris@265: m_ffwdEndAction = toolbar->addAction(il.load("ffwd-end"), Chris@265: tr("Fast Forward to End")); Chris@265: m_ffwdEndAction->setShortcut(tr("End")); Chris@265: m_ffwdEndAction->setStatusTip(tr("Fast-forward to the end")); Chris@265: connect(m_ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd())); Chris@265: connect(this, SIGNAL(canPlay(bool)), m_ffwdEndAction, SLOT(setEnabled(bool))); Chris@0: Chris@1047: m_recordAction = toolbar->addAction(il.load("record"), Chris@1047: tr("Record")); Chris@1047: m_recordAction->setCheckable(true); Chris@1047: m_recordAction->setShortcut(tr("Ctrl+Space")); Chris@1047: m_recordAction->setStatusTip(tr("Record a new audio file")); Chris@1047: connect(m_recordAction, SIGNAL(triggered()), this, SLOT(record())); Chris@1047: connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)), Chris@1770: m_recordAction, SLOT(setChecked(bool))); Chris@1047: connect(this, SIGNAL(canRecord(bool)), Chris@1047: m_recordAction, SLOT(setEnabled(bool))); Chris@1047: Chris@0: toolbar = addToolBar(tr("Play Mode Toolbar")); Chris@0: Chris@265: m_playSelectionAction = toolbar->addAction(il.load("playselection"), Chris@265: tr("Constrain Playback to Selection")); Chris@265: m_playSelectionAction->setCheckable(true); Chris@265: m_playSelectionAction->setChecked(m_viewManager->getPlaySelectionMode()); Chris@265: m_playSelectionAction->setShortcut(tr("s")); Chris@265: m_playSelectionAction->setStatusTip(tr("Constrain playback to the selected regions")); Chris@69: connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)), Chris@265: m_playSelectionAction, SLOT(setChecked(bool))); Chris@265: connect(m_playSelectionAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); Chris@265: connect(this, SIGNAL(canPlaySelection(bool)), m_playSelectionAction, SLOT(setEnabled(bool))); Chris@265: Chris@265: m_playLoopAction = toolbar->addAction(il.load("playloop"), Chris@265: tr("Loop Playback")); Chris@265: m_playLoopAction->setCheckable(true); Chris@265: m_playLoopAction->setChecked(m_viewManager->getPlayLoopMode()); Chris@265: m_playLoopAction->setShortcut(tr("l")); Chris@265: m_playLoopAction->setStatusTip(tr("Loop playback")); Chris@69: connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)), Chris@265: m_playLoopAction, SLOT(setChecked(bool))); Chris@265: connect(m_playLoopAction, SIGNAL(triggered()), this, SLOT(playLoopToggled())); Chris@265: connect(this, SIGNAL(canPlay(bool)), m_playLoopAction, SLOT(setEnabled(bool))); Chris@155: Chris@207: m_soloAction = toolbar->addAction(il.load("solo"), Chris@323: tr("Solo Current Pane")); Chris@207: m_soloAction->setCheckable(true); Chris@207: m_soloAction->setChecked(m_viewManager->getPlaySoloMode()); Chris@207: m_prevSolo = m_viewManager->getPlaySoloMode(); Chris@207: m_soloAction->setShortcut(tr("o")); Chris@207: m_soloAction->setStatusTip(tr("Solo the current pane during playback")); Chris@180: connect(m_viewManager, SIGNAL(playSoloModeChanged(bool)), Chris@207: m_soloAction, SLOT(setChecked(bool))); Chris@207: connect(m_soloAction, SIGNAL(triggered()), this, SLOT(playSoloToggled())); Chris@207: connect(this, SIGNAL(canChangeSolo(bool)), m_soloAction, SLOT(setEnabled(bool))); Chris@180: Chris@2126: QAction *alAction = nullptr; Chris@208: if (Document::canAlign()) { Chris@208: alAction = toolbar->addAction(il.load("align"), Chris@208: tr("Align File Timelines")); Chris@208: alAction->setCheckable(true); Chris@208: alAction->setChecked(m_viewManager->getAlignMode()); Chris@208: alAction->setStatusTip(tr("Treat multiple audio files as versions of the same work, and align their timelines")); Chris@208: connect(m_viewManager, SIGNAL(alignModeChanged(bool)), Chris@208: alAction, SLOT(setChecked(bool))); Chris@208: connect(alAction, SIGNAL(triggered()), this, SLOT(alignToggled())); Chris@208: connect(this, SIGNAL(canAlign(bool)), alAction, SLOT(setEnabled(bool))); Chris@208: } Chris@206: Chris@265: m_keyReference->registerShortcut(m_playAction); Chris@1056: m_keyReference->registerShortcut(m_recordAction); Chris@265: m_keyReference->registerShortcut(m_playSelectionAction); Chris@265: m_keyReference->registerShortcut(m_playLoopAction); Chris@207: m_keyReference->registerShortcut(m_soloAction); Chris@208: if (alAction) m_keyReference->registerShortcut(alAction); Chris@162: m_keyReference->registerShortcut(m_rwdAction); Chris@162: m_keyReference->registerShortcut(m_ffwdAction); Chris@323: m_keyReference->registerShortcut(m_rwdSimilarAction); Chris@323: m_keyReference->registerShortcut(m_ffwdSimilarAction); Chris@265: m_keyReference->registerShortcut(m_rwdStartAction); Chris@265: m_keyReference->registerShortcut(m_ffwdEndAction); Chris@265: Chris@265: menu->addAction(m_playAction); Chris@1056: menu->addAction(m_recordAction); Chris@265: menu->addAction(m_playSelectionAction); Chris@265: menu->addAction(m_playLoopAction); Chris@207: menu->addAction(m_soloAction); Chris@208: if (alAction) menu->addAction(alAction); Chris@155: menu->addSeparator(); Chris@155: menu->addAction(m_rwdAction); Chris@155: menu->addAction(m_ffwdAction); Chris@155: menu->addSeparator(); Chris@323: menu->addAction(m_rwdSimilarAction); Chris@323: menu->addAction(m_ffwdSimilarAction); Chris@323: menu->addSeparator(); Chris@265: menu->addAction(m_rwdStartAction); Chris@265: menu->addAction(m_ffwdEndAction); Chris@155: menu->addSeparator(); Chris@1055: menu->addAction(m_recordAction); Chris@1055: menu->addSeparator(); Chris@155: Chris@265: m_rightButtonPlaybackMenu->addAction(m_playAction); Chris@265: m_rightButtonPlaybackMenu->addAction(m_playSelectionAction); Chris@265: m_rightButtonPlaybackMenu->addAction(m_playLoopAction); Chris@207: m_rightButtonPlaybackMenu->addAction(m_soloAction); Chris@208: if (alAction) m_rightButtonPlaybackMenu->addAction(alAction); Chris@155: m_rightButtonPlaybackMenu->addSeparator(); Chris@155: m_rightButtonPlaybackMenu->addAction(m_rwdAction); Chris@155: m_rightButtonPlaybackMenu->addAction(m_ffwdAction); Chris@155: m_rightButtonPlaybackMenu->addSeparator(); Chris@265: m_rightButtonPlaybackMenu->addAction(m_rwdStartAction); Chris@265: m_rightButtonPlaybackMenu->addAction(m_ffwdEndAction); Chris@155: m_rightButtonPlaybackMenu->addSeparator(); Chris@1055: m_rightButtonPlaybackMenu->addAction(m_recordAction); Chris@1055: m_rightButtonPlaybackMenu->addSeparator(); Chris@155: Chris@155: QAction *fastAction = menu->addAction(tr("Speed Up")); Chris@155: fastAction->setShortcut(tr("Ctrl+PgUp")); Chris@163: fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch")); Chris@155: connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback())); Chris@155: connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool))); Chris@155: Chris@155: QAction *slowAction = menu->addAction(tr("Slow Down")); Chris@155: slowAction->setShortcut(tr("Ctrl+PgDown")); Chris@163: slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch")); Chris@155: connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback())); Chris@155: connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool))); Chris@155: Chris@155: QAction *normalAction = menu->addAction(tr("Restore Normal Speed")); Chris@155: normalAction->setShortcut(tr("Ctrl+Home")); Chris@163: normalAction->setStatusTip(tr("Restore non-time-stretched playback")); Chris@155: connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback())); Chris@155: connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool))); Chris@155: Chris@162: m_keyReference->registerShortcut(fastAction); Chris@162: m_keyReference->registerShortcut(slowAction); Chris@162: m_keyReference->registerShortcut(normalAction); Chris@162: Chris@155: m_rightButtonPlaybackMenu->addAction(fastAction); Chris@155: m_rightButtonPlaybackMenu->addAction(slowAction); Chris@155: m_rightButtonPlaybackMenu->addAction(normalAction); Chris@0: Chris@0: toolbar = addToolBar(tr("Edit Toolbar")); Chris@0: CommandHistory::getInstance()->registerToolbar(toolbar); Chris@0: Chris@0: toolbar = addToolBar(tr("Tools Toolbar")); Chris@0: QActionGroup *group = new QActionGroup(this); Chris@2093: m_toolActions.clear(); Chris@2093: Chris@705: m_keyReference->setCategory(tr("Tool Selection")); Chris@2093: QAction *action = toolbar->addAction(il.load("navigate"), tr("Navigate")); Chris@0: action->setCheckable(true); Chris@0: action->setChecked(true); Chris@0: action->setShortcut(tr("1")); Chris@90: action->setStatusTip(tr("Navigate")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); Chris@596: connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger())); Chris@0: group->addAction(action); Chris@162: m_keyReference->registerShortcut(action); Chris@2093: m_toolActions.push_back({ ViewManager::NavigateMode, action }); Chris@705: Chris@705: m_keyReference->setCategory Chris@705: (tr("Navigate Tool Mouse Actions")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Navigate"), tr("Left"), Chris@705: tr("Click left button and drag to move around")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Zoom to Area"), tr("Shift+Left"), Chris@705: tr("Shift-click left button and drag to zoom to a rectangular area")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Relocate"), tr("Double-Click Left"), Chris@705: tr("Double-click left button to jump to clicked location")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Edit"), tr("Double-Click Left"), Chris@705: tr("Double-click left button on an item to edit it")); Chris@705: Chris@705: m_keyReference->setCategory(tr("Tool Selection")); Chris@2093: action = toolbar->addAction(il.load("select"), tr("Select")); Chris@0: action->setCheckable(true); Chris@0: action->setShortcut(tr("2")); Chris@90: action->setStatusTip(tr("Select ranges")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(toolSelectSelected())); Chris@0: group->addAction(action); Chris@162: m_keyReference->registerShortcut(action); Chris@2093: m_toolActions.push_back({ ViewManager::SelectMode, action }); Chris@705: Chris@705: m_keyReference->setCategory Chris@705: (tr("Select Tool Mouse Actions")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Select"), tr("Left"), Chris@705: tr("Click left button and drag to select region; drag region edge to resize")); Chris@705: #ifdef Q_OS_MAC Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Multi Select"), tr("Ctrl+Left"), Chris@705: tr("Cmd-click left button and drag to select an additional region")); Chris@705: #else Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Multi Select"), tr("Ctrl+Left"), Chris@705: tr("Ctrl-click left button and drag to select an additional region")); Chris@705: #endif Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Fine Select"), tr("Shift+Left"), Chris@705: tr("Shift-click left button and drag to select without snapping to items or grid")); Chris@705: Chris@705: m_keyReference->setCategory(tr("Tool Selection")); Chris@2093: action = toolbar->addAction(il.load("move"), tr("Edit")); Chris@0: action->setCheckable(true); Chris@0: action->setShortcut(tr("3")); Chris@90: action->setStatusTip(tr("Edit items in layer")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); Chris@0: connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: group->addAction(action); Chris@162: m_keyReference->registerShortcut(action); Chris@2093: m_toolActions.push_back({ ViewManager::EditMode, action }); Chris@705: Chris@705: m_keyReference->setCategory Chris@705: (tr("Edit Tool Mouse Actions")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Move"), tr("Left"), Chris@705: tr("Click left button on an item or selected region and drag to move")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Edit"), tr("Double-Click Left"), Chris@705: tr("Double-click left button on an item to edit it")); Chris@705: Chris@705: m_keyReference->setCategory(tr("Tool Selection")); Chris@2093: action = toolbar->addAction(il.load("draw"), tr("Draw")); Chris@0: action->setCheckable(true); Chris@0: action->setShortcut(tr("4")); Chris@90: action->setStatusTip(tr("Draw new items in layer")); Chris@0: connect(action, SIGNAL(triggered()), this, SLOT(toolDrawSelected())); Chris@0: connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); Chris@0: group->addAction(action); Chris@162: m_keyReference->registerShortcut(action); Chris@2093: m_toolActions.push_back({ ViewManager::DrawMode, action }); Chris@0: Chris@705: m_keyReference->setCategory Chris@705: (tr("Draw Tool Mouse Actions")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Draw"), tr("Left"), Chris@705: tr("Click left button and drag to create new item")); Chris@705: Chris@705: m_keyReference->setCategory(tr("Tool Selection")); Chris@2093: action = toolbar->addAction(il.load("erase"), tr("Erase")); Chris@217: action->setCheckable(true); Chris@217: action->setShortcut(tr("5")); Chris@217: action->setStatusTip(tr("Erase items from layer")); Chris@217: connect(action, SIGNAL(triggered()), this, SLOT(toolEraseSelected())); Chris@217: connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); Chris@217: group->addAction(action); Chris@217: m_keyReference->registerShortcut(action); Chris@2093: m_toolActions.push_back({ ViewManager::EraseMode, action }); Chris@217: Chris@705: m_keyReference->setCategory Chris@705: (tr("Erase Tool Mouse Actions")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Erase"), tr("Left"), Chris@705: tr("Click left button on an item to remove it from the layer")); Chris@705: Chris@705: m_keyReference->setCategory(tr("Tool Selection")); Chris@265: action = toolbar->addAction(il.load("measure"), tr("Measure")); Chris@151: action->setCheckable(true); Chris@217: action->setShortcut(tr("6")); Chris@151: action->setStatusTip(tr("Make measurements in layer")); Chris@151: connect(action, SIGNAL(triggered()), this, SLOT(toolMeasureSelected())); Chris@169: connect(this, SIGNAL(canMeasureLayer(bool)), action, SLOT(setEnabled(bool))); Chris@151: group->addAction(action); Chris@162: m_keyReference->registerShortcut(action); Chris@2093: m_toolActions.push_back({ ViewManager::MeasureMode, action }); Chris@151: Chris@705: m_keyReference->setCategory Chris@705: (tr("Measure Tool Mouse Actions")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Measure Area"), tr("Left"), Chris@705: tr("Click left button and drag to measure a rectangular area")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Measure Item"), tr("Double-Click Left"), Chris@705: tr("Click left button and drag to measure extents of an item or shape")); Chris@705: m_keyReference->registerShortcut Chris@705: (tr("Zoom to Area"), tr("Shift+Left"), Chris@705: tr("Shift-click left button and drag to zoom to a rectangular area")); Chris@705: Chris@0: toolNavigateSelected(); Chris@163: Chris@163: Pane::registerShortcuts(*m_keyReference); Chris@0: } Chris@0: Chris@0: void Chris@265: MainWindow::connectLayerEditDialog(ModelDataTableDialog *dialog) Chris@265: { Chris@265: MainWindowBase::connectLayerEditDialog(dialog); Chris@265: QToolBar *toolbar = dialog->getPlayToolbar(); Chris@265: if (toolbar) { Chris@265: toolbar->addAction(m_rwdStartAction); Chris@265: toolbar->addAction(m_rwdAction); Chris@265: toolbar->addAction(m_playAction); Chris@265: toolbar->addAction(m_ffwdAction); Chris@265: toolbar->addAction(m_ffwdEndAction); Chris@265: } Chris@265: } Chris@265: Chris@265: void Chris@0: MainWindow::updateMenuStates() Chris@0: { Chris@200: MainWindowBase::updateMenuStates(); Chris@200: Chris@2126: Pane *currentPane = nullptr; Chris@2126: Layer *currentLayer = nullptr; Chris@117: Chris@117: if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); Chris@117: if (currentPane) currentLayer = currentPane->getSelectedLayer(); Chris@117: Chris@0: bool haveCurrentPane = Chris@2126: (currentPane != nullptr); Chris@0: bool haveCurrentLayer = Chris@117: (haveCurrentPane && Chris@2126: (currentLayer != nullptr)); Chris@206: bool havePlayTarget = Chris@2126: (m_playTarget != nullptr || m_audioIO != nullptr); Chris@0: bool haveSelection = Chris@1770: (m_viewManager && Chris@1770: !m_viewManager->getSelections().empty()); Chris@0: bool haveCurrentEditableLayer = Chris@1770: (haveCurrentLayer && Chris@1770: currentLayer->isLayerEditable()); Chris@0: bool haveCurrentTimeInstantsLayer = Chris@1770: (haveCurrentLayer && Chris@1770: dynamic_cast(currentLayer)); Chris@0: bool haveCurrentTimeValueLayer = Chris@1770: (haveCurrentLayer && Chris@1770: dynamic_cast(currentLayer)); Chris@207: Chris@314: bool alignMode = m_viewManager && m_viewManager->getAlignMode(); Chris@314: emit canChangeSolo(havePlayTarget && !alignMode); Chris@207: emit canAlign(havePlayTarget && m_document && m_document->canAlign()); Chris@206: Chris@200: emit canChangePlaybackSpeed(true); Chris@200: int v = m_playSpeed->value(); Chris@200: emit canSpeedUpPlayback(v < m_playSpeed->maximum()); Chris@200: emit canSlowDownPlayback(v > m_playSpeed->minimum()); Chris@155: Chris@164: if (m_viewManager && Chris@164: (m_viewManager->getToolMode() == ViewManager::MeasureMode)) { Chris@164: emit canDeleteSelection(haveCurrentLayer); Chris@164: m_deleteSelectedAction->setText(tr("&Delete Current Measurement")); Chris@164: m_deleteSelectedAction->setStatusTip(tr("Delete the measurement currently under the mouse pointer")); Chris@164: } else { Chris@164: emit canDeleteSelection(haveSelection && haveCurrentEditableLayer); Chris@164: m_deleteSelectedAction->setText(tr("&Delete Selected Items")); Chris@164: m_deleteSelectedAction->setStatusTip(tr("Delete items in current selection from the current layer")); Chris@164: } Chris@164: Chris@155: if (m_ffwdAction && m_rwdAction) { Chris@155: if (haveCurrentTimeInstantsLayer) { Chris@155: m_ffwdAction->setText(tr("Fast Forward to Next Instant")); Chris@155: m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer")); Chris@155: m_rwdAction->setText(tr("Rewind to Previous Instant")); Chris@155: m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer")); Chris@155: } else if (haveCurrentTimeValueLayer) { Chris@155: m_ffwdAction->setText(tr("Fast Forward to Next Point")); Chris@155: m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer")); Chris@155: m_rwdAction->setText(tr("Rewind to Previous Point")); Chris@155: m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer")); Chris@155: } else { Chris@155: m_ffwdAction->setText(tr("Fast Forward")); Chris@155: m_ffwdAction->setStatusTip(tr("Fast forward")); Chris@155: m_rwdAction->setText(tr("Rewind")); Chris@155: m_rwdAction->setStatusTip(tr("Rewind")); Chris@155: } Chris@155: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::updateDescriptionLabel() Chris@0: { Chris@0: if (!getMainModel()) { Chris@1770: m_descriptionLabel->setText(tr("No audio file loaded.")); Chris@1770: return; Chris@0: } Chris@0: Chris@0: QString description; Chris@0: Chris@1404: //!!!??? Chris@1404: Chris@922: sv_samplerate_t ssr = getMainModel()->getSampleRate(); Chris@922: sv_samplerate_t tsr = ssr; Chris@1405: if (m_playSource) tsr = m_playSource->getDeviceSampleRate(); Chris@0: Chris@0: if (ssr != tsr) { Chris@1770: description = tr("%1Hz (resampling to %2Hz)").arg(ssr).arg(tsr); Chris@0: } else { Chris@1770: description = QString("%1Hz").arg(ssr); Chris@0: } Chris@0: Chris@0: description = QString("%1 - %2") Chris@1770: .arg(RealTime::frame2RealTime(getMainModel()->getEndFrame(), ssr) Chris@1770: .toText(false).c_str()) Chris@1770: .arg(description); Chris@0: Chris@0: m_descriptionLabel->setText(description); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::documentModified() Chris@0: { Chris@200: //!!! Chris@200: MainWindowBase::documentModified(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::documentRestored() Chris@0: { Chris@200: //!!! Chris@200: MainWindowBase::documentRestored(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::toolNavigateSelected() Chris@0: { Chris@0: m_viewManager->setToolMode(ViewManager::NavigateMode); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::toolSelectSelected() Chris@0: { Chris@0: m_viewManager->setToolMode(ViewManager::SelectMode); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::toolEditSelected() Chris@0: { Chris@0: m_viewManager->setToolMode(ViewManager::EditMode); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::toolDrawSelected() Chris@0: { Chris@0: m_viewManager->setToolMode(ViewManager::DrawMode); Chris@0: } Chris@0: Chris@151: void Chris@217: MainWindow::toolEraseSelected() Chris@217: { Chris@217: m_viewManager->setToolMode(ViewManager::EraseMode); Chris@217: } Chris@217: Chris@217: void Chris@151: MainWindow::toolMeasureSelected() Chris@151: { Chris@151: m_viewManager->setToolMode(ViewManager::MeasureMode); Chris@151: } Chris@151: Chris@0: void Chris@0: MainWindow::importAudio() Chris@0: { Chris@88: QString path = getOpenFileName(FileFinder::AudioFile); Chris@0: Chris@0: if (path != "") { Chris@1770: if (openAudio(path, ReplaceSession) == FileOpenFailed) { Chris@247: emit hideSplash(); Chris@1770: QMessageBox::critical(this, tr("Failed to open file"), Chris@1770: tr("File open failed

Audio file \"%1\" could not be opened").arg(path)); Chris@1770: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::importMoreAudio() Chris@0: { Chris@88: QString path = getOpenFileName(FileFinder::AudioFile); Chris@0: Chris@0: if (path != "") { Chris@1770: if (openAudio(path, CreateAdditionalModel) == FileOpenFailed) { Chris@247: emit hideSplash(); Chris@1770: QMessageBox::critical(this, tr("Failed to open file"), Chris@1770: tr("File open failed

Audio file \"%1\" could not be opened").arg(path)); Chris@1770: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@508: MainWindow::replaceMainAudio() Chris@508: { Chris@508: QString path = getOpenFileName(FileFinder::AudioFile); Chris@508: Chris@508: if (path != "") { Chris@1770: if (openAudio(path, ReplaceMainModel) == FileOpenFailed) { Chris@508: emit hideSplash(); Chris@1770: QMessageBox::critical(this, tr("Failed to open file"), Chris@1770: tr("File open failed

Audio file \"%1\" could not be opened").arg(path)); Chris@1770: } Chris@508: } Chris@508: } Chris@508: Chris@508: void Chris@0: MainWindow::exportAudio() Chris@0: { Chris@631: exportAudio(false); Chris@631: } Chris@631: Chris@631: void Chris@631: MainWindow::exportAudioData() Chris@631: { Chris@631: exportAudio(true); Chris@631: } Chris@631: Chris@631: void Chris@631: MainWindow::exportAudio(bool asData) Chris@631: { Chris@2300: auto modelId = getMainModelId(); Chris@2300: if (modelId.isNone()) return; Chris@2300: Chris@2300: std::set otherModelIds; Chris@2300: ModelId current = modelId; Chris@2300: Chris@320: if (m_paneStack) { Chris@320: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@320: Pane *pane = m_paneStack->getPane(i); Chris@320: if (!pane) continue; Chris@320: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@320: Layer *layer = pane->getLayer(j); Chris@320: if (!layer) continue; Chris@432: cerr << "layer = " << layer->objectName() << endl; Chris@2300: ModelId m = layer->getModel(); Chris@2300: if (ModelById::isa(m)) { Chris@2300: otherModelIds.insert(m); Chris@320: if (pane == m_paneStack->getCurrentPane()) { Chris@2300: current = m; Chris@320: } Chris@320: } Chris@320: } Chris@320: } Chris@320: } Chris@2300: if (!otherModelIds.empty()) { Chris@2300: std::map m; Chris@2300: QString unnamed = tr(""); Chris@2300: QString oname = unnamed; Chris@2300: if (auto mp = ModelById::get(modelId)) { Chris@2300: oname = mp->objectName(); Chris@2300: } Chris@2300: m[tr("1. %2").arg(oname)] = modelId; Chris@320: int n = 2; Chris@320: int c = 0; Chris@2300: for (auto otherModelId: otherModelIds) { Chris@2300: if (otherModelId == modelId) continue; Chris@2300: oname = unnamed; Chris@2300: if (auto mp = ModelById::get(otherModelId)) { Chris@2300: oname = mp->objectName(); Chris@2300: } Chris@2300: m[tr("%1. %2").arg(n).arg(oname)] = otherModelId; Chris@320: ++n; Chris@2300: if (otherModelId == current) c = n-1; Chris@320: } Chris@320: QStringList items; Chris@2300: for (auto i: m) { Chris@2300: items << i.first; Chris@320: } Chris@325: if (items.size() > 1) { Chris@325: bool ok = false; Chris@325: QString item = QInputDialog::getItem Chris@325: (this, tr("Select audio file to export"), Chris@325: tr("Which audio file do you want to export from?"), Chris@325: items, c, false, &ok); Chris@325: if (!ok || item.isEmpty()) return; Chris@325: if (m.find(item) == m.end()) { Chris@2300: SVCERR << "WARNING: Model " << item Chris@2300: << " not found in list!" << endl; Chris@325: } else { Chris@2300: modelId = m[item]; Chris@325: } Chris@320: } Chris@320: } Chris@320: Chris@2300: auto model = ModelById::getAs(modelId); Chris@2300: if (!model) { Chris@2300: SVCERR << "ERROR: Chosen model is not a DenseTimeValueModel!" << endl; Chris@2300: return; Chris@2300: } Chris@2300: Chris@631: QString path; Chris@631: if (asData) { Chris@631: path = getSaveFileName(FileFinder::CSVFile); Chris@631: } else { Chris@631: path = getSaveFileName(FileFinder::AudioFile); Chris@631: } Chris@0: if (path == "") return; Chris@0: Chris@0: bool ok = false; Chris@0: QString error; Chris@0: Chris@0: MultiSelection ms = m_viewManager->getSelection(); Chris@0: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@0: Chris@0: bool multiple = false; Chris@0: Chris@2126: MultiSelection *selectionToWrite = nullptr; Chris@38: Chris@38: if (selections.size() == 1) { Chris@0: Chris@1770: QStringList items; Chris@1770: items << tr("Export the selected region only") Chris@1770: << tr("Export the whole audio file"); Chris@1770: Chris@1770: bool ok = false; Chris@1770: QString item = ListInputDialog::getItem Chris@1770: (this, tr("Select region to export"), Chris@1770: tr("Which region from the original audio file do you want to export?"), Chris@1770: items, 0, &ok); Chris@1770: Chris@1770: if (!ok || item.isEmpty()) return; Chris@1770: Chris@1770: if (item == items[0]) selectionToWrite = &ms; Chris@38: Chris@38: } else if (selections.size() > 1) { Chris@0: Chris@631: if (!asData) { // Multi-file export not supported for data Chris@631: Chris@631: QStringList items; Chris@631: items << tr("Export the selected regions into a single file") Chris@631: << tr("Export the selected regions into separate files") Chris@631: << tr("Export the whole file"); Chris@631: Chris@631: QString item = ListInputDialog::getItem Chris@631: (this, tr("Select region to export"), Chris@631: tr("Multiple regions of the original audio file are selected.\nWhat do you want to export?"), Chris@631: items, 0, &ok); Chris@1770: Chris@631: if (!ok || item.isEmpty()) return; Chris@631: Chris@631: if (item == items[0]) { Chris@631: selectionToWrite = &ms; Chris@631: } else if (item == items[1]) { Chris@631: multiple = true; Chris@631: } Chris@631: Chris@631: } else { // asData Chris@38: selectionToWrite = &ms; Chris@631: } Chris@631: Chris@631: if (multiple) { // Can only happen when asData false Chris@0: Chris@1770: int n = 1; Chris@1770: QString base = path; Chris@1770: base.replace(".wav", ""); Chris@1770: Chris@1770: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@1770: i != selections.end(); ++i) { Chris@1770: Chris@1770: MultiSelection subms; Chris@1770: subms.setSelection(*i); Chris@1770: Chris@1770: QString subpath = QString("%1.%2.wav").arg(base).arg(n); Chris@1770: ++n; Chris@1770: Chris@1770: if (QFileInfo(subpath).exists()) { Chris@1770: error = tr("Fragment file %1 already exists, aborting").arg(subpath); Chris@1770: break; Chris@1770: } Chris@1770: Chris@1770: WavFileWriter subwriter(subpath, Chris@320: model->getSampleRate(), Chris@428: model->getChannelCount(), Chris@428: WavFileWriter::WriteToTemporary); Chris@2300: subwriter.writeModel(model.get(), &subms); Chris@1770: ok = subwriter.isOK(); Chris@1770: Chris@1770: if (!ok) { Chris@1770: error = subwriter.getError(); Chris@1770: break; Chris@1770: } Chris@1770: } Chris@1770: } Chris@0: } Chris@0: Chris@38: if (!multiple) { Chris@631: if (asData) { Chris@1779: stop(); Chris@1779: ProgressDialog dialog { Chris@1779: QObject::tr("Exporting audio data..."), Chris@1779: true, Chris@1779: 0, Chris@1779: this, Chris@1779: Qt::ApplicationModal Chris@1779: }; Chris@2300: CSVFileWriter writer(path, model.get(), &dialog, Chris@631: ((QFileInfo(path).suffix() == "csv") ? Chris@631: "," : "\t")); Chris@631: if (selectionToWrite) { Chris@1779: writer.writeSelection(*selectionToWrite); Chris@631: } else { Chris@631: writer.write(); Chris@631: } Chris@631: ok = writer.isOK(); Chris@631: error = writer.getError(); Chris@631: } else { Chris@631: WavFileWriter writer(path, Chris@631: model->getSampleRate(), Chris@631: model->getChannelCount(), Chris@631: WavFileWriter::WriteToTemporary); Chris@2300: writer.writeModel(model.get(), selectionToWrite); Chris@631: ok = writer.isOK(); Chris@631: error = writer.getError(); Chris@631: } Chris@0: } Chris@0: Chris@0: if (ok) { Chris@310: if (multiple) { Chris@310: emit activity(tr("Export multiple audio files")); Chris@310: } else { Chris@310: emit activity(tr("Export audio to \"%1\"").arg(path)); Chris@34: m_recentFiles.addFile(path); Chris@0: } Chris@0: } else { Chris@1770: QMessageBox::critical(this, tr("Failed to write file"), error); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@1995: MainWindow::convertAudio() Chris@1902: { Chris@1902: QString path = getOpenFileName(FileFinder::CSVFile); Chris@1902: if (path == "") return; Chris@1902: Chris@1995: sv_samplerate_t defaultRate = 44100; Chris@1902: Chris@1902: CSVFormat format(path); Chris@1902: format.setModelType(CSVFormat::WaveFileModel); Chris@1902: format.setTimingType(CSVFormat::ImplicitTiming); Chris@1902: format.setTimeUnits(CSVFormat::TimeAudioFrames); Chris@1995: format.setSampleRate(defaultRate); // as a default for the dialog Chris@1995: Chris@1987: { Chris@1989: CSVAudioFormatDialog *dialog = new CSVAudioFormatDialog(this, format); Chris@1987: if (dialog->exec() != QDialog::Accepted) { Chris@1987: delete dialog; Chris@1987: return; Chris@1987: } Chris@1987: format = dialog->getFormat(); Chris@1987: delete dialog; Chris@1987: } Chris@1987: Chris@1902: FileOpenStatus status = FileOpenSucceeded; Chris@1911: Chris@1987: ProgressDialog *progress = new ProgressDialog Chris@2001: (tr("Converting audio data..."), true, 0, this, Qt::ApplicationModal); Chris@1902: Chris@1902: WaveFileModel *model = qobject_cast Chris@1902: (DataFileReaderFactory::loadCSV Chris@1902: (path, format, Chris@1995: getMainModel() ? getMainModel()->getSampleRate() : defaultRate, Chris@1987: progress)); Chris@1987: Chris@1987: if (progress->wasCancelled()) { Chris@1911: Chris@1911: delete model; Chris@1911: status = FileOpenCancelled; Chris@1911: Chris@1911: } else if (!model || !model->isOK()) { Chris@1902: Chris@1902: delete model; Chris@1902: status = FileOpenFailed; Chris@1902: Chris@1902: } else { Chris@1902: Chris@2300: auto modelId = ModelById::add(std::shared_ptr(model)); Chris@2300: Chris@1902: status = addOpenedAudioModel(path, Chris@2300: modelId, Chris@1902: CreateAdditionalModel, Chris@1902: getDefaultSessionTemplate(), Chris@1902: false); Chris@1902: } Chris@1902: Chris@1987: delete progress; Chris@1911: Chris@1902: if (status == FileOpenFailed) { Chris@1902: emit hideSplash(); Chris@1902: QMessageBox::critical(this, tr("Failed to open file"), Chris@1902: tr("File open failed

Audio data file %1 could not be opened.").arg(path)); Chris@1902: } Chris@1902: } Chris@1902: Chris@1902: void Chris@0: MainWindow::importLayer() Chris@0: { Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: Chris@0: if (!pane) { Chris@1770: // shouldn't happen, as the menu action should have been disabled Chris@1770: cerr << "WARNING: MainWindow::importLayer: no current pane" << endl; Chris@1770: return; Chris@0: } Chris@0: Chris@0: if (!getMainModel()) { Chris@1770: // shouldn't happen, as the menu action should have been disabled Chris@1770: cerr << "WARNING: MainWindow::importLayer: No main model -- hence no default sample rate available" << endl; Chris@1770: return; Chris@0: } Chris@0: Chris@88: QString path = getOpenFileName(FileFinder::LayerFile); Chris@0: Chris@0: if (path != "") { Chris@0: Chris@197: FileOpenStatus status = openLayer(path); Chris@193: Chris@193: if (status == FileOpenFailed) { Chris@247: emit hideSplash(); Chris@0: QMessageBox::critical(this, tr("Failed to open file"), Chris@193: tr("File open failed

Layer file %1 could not be opened.").arg(path)); Chris@0: return; Chris@193: } else if (status == FileOpenWrongMode) { Chris@247: emit hideSplash(); Chris@193: QMessageBox::critical(this, tr("Failed to open file"), Chris@294: 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@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::exportLayer() Chris@0: { Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: if (!pane) return; Chris@0: Chris@0: Layer *layer = pane->getSelectedLayer(); Chris@0: if (!layer) return; Chris@0: Chris@2300: ModelId modelId = layer->getModel(); Chris@2300: if (modelId.isNone()) return; Chris@0: Chris@185: FileFinder::FileType type = FileFinder::LayerFileNoMidi; Chris@2300: if (ModelById::isa(modelId)) type = FileFinder::LayerFile; Chris@185: QString path = getSaveFileName(type); Chris@0: Chris@0: if (path == "") return; Chris@0: Chris@0: QString error; Chris@0: Chris@2242: if (!exportLayerTo(layer, path, error)) { Chris@0: QMessageBox::critical(this, tr("Failed to write file"), error); Chris@0: } else { Chris@34: m_recentFiles.addFile(path); Chris@310: emit activity(tr("Export layer to \"%1\"").arg(path)); Chris@0: } Chris@0: } Chris@0: Chris@121: void Chris@121: MainWindow::exportImage() Chris@121: { Chris@121: Pane *pane = m_paneStack->getCurrentPane(); Chris@121: if (!pane) return; Chris@121: Chris@121: QString path = getSaveFileName(FileFinder::ImageFile); Chris@121: if (path == "") return; Chris@1460: if (QFileInfo(path).suffix() == "") path += ".png"; Chris@1451: Chris@123: bool haveSelection = m_viewManager && !m_viewManager->getSelections().empty(); Chris@123: Chris@123: QSize total, visible, selected; Chris@1451: total = pane->getRenderedImageSize(); Chris@1451: visible = pane->getRenderedPartImageSize(pane->getFirstVisibleFrame(), Chris@1451: pane->getLastVisibleFrame()); Chris@123: Chris@922: sv_frame_t sf0 = 0, sf1 = 0; Chris@123: Chris@123: if (haveSelection) { Chris@123: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@123: sf0 = selections.begin()->getStartFrame(); Chris@123: MultiSelection::SelectionList::iterator e = selections.end(); Chris@123: --e; Chris@123: sf1 = e->getEndFrame(); Chris@1451: selected = pane->getRenderedPartImageSize(sf0, sf1); Chris@123: } Chris@123: Chris@123: QStringList items; Chris@125: items << tr("Export the whole pane (%1x%2 pixels)") Chris@123: .arg(total.width()).arg(total.height()); Chris@123: items << tr("Export the visible area only (%1x%2 pixels)") Chris@123: .arg(visible.width()).arg(visible.height()); Chris@123: if (haveSelection) { Chris@123: items << tr("Export the selection extent (%1x%2 pixels)") Chris@123: .arg(selected.width()).arg(selected.height()); Chris@124: } else { Chris@124: items << tr("Export the selection extent"); Chris@123: } Chris@123: Chris@123: QSettings settings; Chris@123: settings.beginGroup("MainWindow"); Chris@123: int deflt = settings.value("lastimageexportregion", 0).toInt(); Chris@123: if (deflt == 2 && !haveSelection) deflt = 1; Chris@1460: if (deflt == 0 && total.width() > 32767) deflt = 1; Chris@124: Chris@124: ListInputDialog *lid = new ListInputDialog Chris@123: (this, tr("Select region to export"), Chris@123: tr("Which region of the current pane do you want to export as an image?"), Chris@124: items, deflt); Chris@124: Chris@124: if (!haveSelection) { Chris@124: lid->setItemAvailability(2, false); Chris@124: } Chris@1460: if (total.width() > 32767) { // appears to be limit of a QImage Chris@124: lid->setItemAvailability(0, false); Chris@124: lid->setFootnote(tr("Note: the whole pane is too wide to be exported as a single image.")); Chris@124: } Chris@124: Chris@124: bool ok = lid->exec(); Chris@124: QString item = lid->getCurrentString(); Chris@124: delete lid; Chris@1770: Chris@123: if (!ok || item.isEmpty()) return; Chris@123: Chris@123: settings.setValue("lastimageexportregion", deflt); Chris@123: Chris@2126: QImage *image = nullptr; Chris@1451: Chris@1460: if (item == items[0]) { Chris@1460: image = pane->renderToNewImage(); Chris@1460: } else if (item == items[1]) { Chris@1460: image = pane->renderPartToNewImage(pane->getFirstVisibleFrame(), Chris@1460: pane->getLastVisibleFrame()); Chris@1460: } else if (haveSelection) { Chris@1460: image = pane->renderPartToNewImage(sf0, sf1); Chris@1451: } Chris@1460: Chris@1460: if (!image) return; Chris@1460: Chris@1460: if (!image->save(path, "PNG")) { Chris@1460: QMessageBox::critical(this, tr("Failed to save image file"), Chris@1460: tr("Failed to save image file %1").arg(path)); Chris@1460: } Chris@1460: Chris@1460: delete image; Chris@1451: } Chris@1451: Chris@1451: void Chris@1451: MainWindow::exportSVG() Chris@1451: { Chris@1451: Pane *pane = m_paneStack->getCurrentPane(); Chris@1451: if (!pane) return; Chris@1451: Chris@1451: QString path = getSaveFileName(FileFinder::SVGFile); Chris@1451: if (path == "") return; Chris@1451: if (QFileInfo(path).suffix() == "") path += ".svg"; Chris@1451: Chris@1451: bool haveSelection = m_viewManager && !m_viewManager->getSelections().empty(); Chris@1451: Chris@1451: sv_frame_t sf0 = 0, sf1 = 0; Chris@1451: Chris@1451: if (haveSelection) { Chris@1451: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@1451: sf0 = selections.begin()->getStartFrame(); Chris@1451: MultiSelection::SelectionList::iterator e = selections.end(); Chris@1451: --e; Chris@1451: sf1 = e->getEndFrame(); Chris@1451: } Chris@1451: Chris@1451: QStringList items; Chris@1451: items << tr("Export the whole pane"); Chris@1451: items << tr("Export the visible area only"); Chris@1451: items << tr("Export the selection extent"); Chris@1451: Chris@1451: QSettings settings; Chris@1451: settings.beginGroup("MainWindow"); Chris@1451: int deflt = settings.value("lastsvgexportregion", 0).toInt(); Chris@1451: if (deflt == 2 && !haveSelection) deflt = 1; Chris@1451: Chris@1451: ListInputDialog *lid = new ListInputDialog Chris@1451: (this, tr("Select region to export"), Chris@1451: tr("Which region of the current pane do you want to export as a scalable SVG image?"), Chris@1451: items, deflt); Chris@1451: Chris@1451: if (!haveSelection) { Chris@1451: lid->setItemAvailability(2, false); Chris@1451: } Chris@1451: Chris@1451: bool ok = lid->exec(); Chris@1451: QString item = lid->getCurrentString(); Chris@1451: delete lid; Chris@1770: Chris@1451: if (!ok || item.isEmpty()) return; Chris@1451: Chris@1451: settings.setValue("lastsvgexportregion", deflt); Chris@1451: Chris@1451: bool result = false; Chris@1451: Chris@123: if (item == items[0]) { Chris@1451: result = pane->renderToSvgFile(path ); Chris@123: } else if (item == items[1]) { Chris@1451: result = pane->renderPartToSvgFile(path, Chris@1451: pane->getFirstVisibleFrame(), Chris@1451: pane->getLastVisibleFrame()); Chris@123: } else if (haveSelection) { Chris@1451: result = pane->renderPartToSvgFile(path, sf0, sf1); Chris@121: } Chris@121: Chris@1451: if (!result) { Chris@1451: QMessageBox::critical(this, tr("Failed to save SVG file"), Chris@1451: tr("Failed to save SVG file %1").arg(path)); Chris@1451: } Chris@121: } Chris@121: Chris@0: void Chris@1056: MainWindow::browseRecordedAudio() Chris@1056: { Chris@1995: QString path = RecordDirectory::getRecordContainerDirectory(); Chris@1995: if (path == "") path = RecordDirectory::getRecordDirectory(); Chris@1056: if (path == "") return; Chris@1056: Chris@1056: openLocalFolder(path); Chris@1056: } Chris@1056: Chris@1056: void Chris@0: MainWindow::newSession() Chris@0: { Chris@0: if (!checkSaveModified()) return; Chris@0: Chris@0: closeSession(); Chris@2062: stop(); Chris@0: createDocument(); Chris@0: Chris@0: Pane *pane = m_paneStack->addPane(); Chris@0: Chris@90: connect(pane, SIGNAL(contextHelpChanged(const QString &)), Chris@116: this, SLOT(contextHelpChanged(const QString &))); Chris@90: Chris@0: if (!m_timeRulerLayer) { Chris@1770: m_timeRulerLayer = m_document->createMainModelLayer Chris@1770: (LayerFactory::TimeRuler); Chris@0: } Chris@0: Chris@0: m_document->addLayerToView(pane, m_timeRulerLayer); Chris@0: Chris@0: Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); Chris@0: m_document->addLayerToView(pane, waveform); Chris@0: Chris@65: 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@303: MainWindow::documentReplaced() Chris@303: { Chris@303: if (m_document) { Chris@303: connect(m_document, SIGNAL(activity(QString)), Chris@303: m_activityLog, SLOT(activityHappened(QString))); Chris@303: } Chris@303: } Chris@303: Chris@303: void Chris@0: MainWindow::closeSession() Chris@0: { Chris@0: if (!checkSaveModified()) return; Chris@0: Chris@0: while (m_paneStack->getPaneCount() > 0) { Chris@0: Chris@1770: Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); Chris@1770: Chris@1770: while (pane->getLayerCount() > 0) { Chris@1770: m_document->removeLayerFromView Chris@1770: (pane, pane->getLayer(pane->getLayerCount() - 1)); Chris@1770: } Chris@1770: Chris@1770: m_overview->unregisterView(pane); Chris@1770: m_paneStack->deletePane(pane); Chris@0: } Chris@0: Chris@0: while (m_paneStack->getHiddenPaneCount() > 0) { Chris@0: Chris@1770: Pane *pane = m_paneStack->getHiddenPane Chris@1770: (m_paneStack->getHiddenPaneCount() - 1); Chris@1770: Chris@1770: while (pane->getLayerCount() > 0) { Chris@1770: m_document->removeLayerFromView Chris@1770: (pane, pane->getLayer(pane->getLayerCount() - 1)); Chris@1770: } Chris@1770: Chris@1770: m_overview->unregisterView(pane); Chris@1770: m_paneStack->deletePane(pane); Chris@0: } Chris@0: Chris@504: delete m_layerTreeDialog.data(); Chris@504: delete m_preferencesDialog.data(); Chris@504: Chris@504: m_activityLog->hide(); Chris@891: m_unitConverter->hide(); Chris@504: m_keyReference->hide(); Chris@504: Chris@0: delete m_document; Chris@2126: m_document = nullptr; Chris@0: m_viewManager->clearSelections(); Chris@2126: m_timeRulerLayer = nullptr; // document owned this Chris@0: Chris@0: m_sessionFile = ""; Chris@518: setWindowTitle(QApplication::applicationName()); 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::openSomething() Chris@0: { Chris@0: QString orig = m_audioFile; Chris@0: if (orig == "") orig = "."; Chris@0: else orig = QFileInfo(orig).absoluteDir().canonicalPath(); Chris@0: Chris@88: QString path = getOpenFileName(FileFinder::AnyFile); Chris@0: Chris@0: if (path.isEmpty()) return; Chris@0: Chris@844: FileOpenStatus status = openPath(path, ReplaceSession); Chris@193: Chris@193: if (status == FileOpenFailed) { Chris@247: emit hideSplash(); Chris@193: QMessageBox::critical(this, tr("Failed to open file"), Chris@193: tr("File open failed

File \"%1\" could not be opened").arg(path)); Chris@193: } else if (status == FileOpenWrongMode) { Chris@247: emit hideSplash(); Chris@193: QMessageBox::critical(this, tr("Failed to open file"), Chris@294: 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@0: } Chris@0: } Chris@0: Chris@0: void Chris@86: MainWindow::openLocation() Chris@86: { Chris@103: QSettings settings; Chris@103: settings.beginGroup("MainWindow"); Chris@103: QString lastLocation = settings.value("lastremote", "").toString(); Chris@103: Chris@86: bool ok = false; Chris@86: QString text = QInputDialog::getText Chris@86: (this, tr("Open Location"), Chris@86: tr("Please enter the URL of the location to open:"), Chris@103: QLineEdit::Normal, lastLocation, &ok); Chris@103: Chris@103: if (!ok) return; Chris@103: Chris@103: settings.setValue("lastremote", text); Chris@103: Chris@103: if (text.isEmpty()) return; Chris@86: Chris@844: FileOpenStatus status = openPath(text, AskUser); Chris@193: Chris@193: if (status == FileOpenFailed) { Chris@247: emit hideSplash(); Chris@86: QMessageBox::critical(this, tr("Failed to open location"), Chris@193: tr("Open failed

URL \"%1\" could not be opened").arg(text)); Chris@193: } else if (status == FileOpenWrongMode) { Chris@247: emit hideSplash(); Chris@193: QMessageBox::critical(this, tr("Failed to open location"), Chris@294: 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(text)); Chris@86: } Chris@86: } Chris@86: Chris@86: void Chris@2149: MainWindow::openRecentFile() Chris@0: { Chris@0: QObject *obj = sender(); Chris@0: QAction *action = dynamic_cast(obj); Chris@0: Chris@0: if (!action) { Chris@1770: cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" Chris@2149: << endl; Chris@1770: return; Chris@0: } Chris@0: Chris@2149: QString path = action->objectName(); Chris@2149: Chris@2149: if (path == "") { Chris@2149: cerr << "WARNING: MainWindow::openRecentFile: action incorrectly named" Chris@2149: << endl; Chris@2149: return; Chris@2149: } Chris@0: Chris@844: FileOpenStatus status = openPath(path, ReplaceSession); Chris@193: Chris@193: if (status == FileOpenFailed) { Chris@247: emit hideSplash(); Chris@193: QMessageBox::critical(this, tr("Failed to open location"), Chris@193: tr("Open failed

File or URL \"%1\" could not be opened").arg(path)); Chris@193: } else if (status == FileOpenWrongMode) { Chris@247: emit hideSplash(); Chris@193: QMessageBox::critical(this, tr("Failed to open location"), Chris@294: 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@0: } Chris@0: } Chris@0: Chris@0: void Chris@435: MainWindow::applyTemplate() Chris@435: { Chris@435: QObject *s = sender(); Chris@435: QAction *action = qobject_cast(s); Chris@435: Chris@435: if (!action) { Chris@1770: cerr << "WARNING: MainWindow::applyTemplate: sender is not an action" Chris@1770: << endl; Chris@1770: return; Chris@435: } Chris@435: Chris@435: QString n = action->objectName(); Chris@435: if (n == "") n = action->text(); Chris@435: Chris@435: if (n == "") { Chris@665: cerr << "WARNING: MainWindow::applyTemplate: sender has no name" Chris@665: << endl; Chris@435: return; Chris@435: } Chris@435: Chris@435: QString mainModelLocation; Chris@2300: auto mm = getMainModel(); Chris@435: if (mm) mainModelLocation = mm->getLocation(); Chris@435: if (mainModelLocation != "") { Chris@435: openAudio(mainModelLocation, ReplaceSession, n); Chris@435: } else { Chris@435: openSessionTemplate(n); Chris@435: } Chris@435: } Chris@435: Chris@435: void Chris@425: MainWindow::saveSessionAsTemplate() Chris@425: { Chris@762: QDialog *d = new QDialog(this); Chris@483: d->setWindowTitle(tr("Enter template name")); Chris@483: Chris@483: QGridLayout *layout = new QGridLayout; Chris@483: d->setLayout(layout); Chris@483: Chris@483: layout->addWidget(new QLabel(tr("Please enter a name for the saved template:")), Chris@483: 0, 0); Chris@483: QLineEdit *lineEdit = new QLineEdit; Chris@483: layout->addWidget(lineEdit, 1, 0); Chris@483: QCheckBox *makeDefault = new QCheckBox(tr("Set as default template for future audio files")); Chris@483: layout->addWidget(makeDefault, 2, 0); Chris@425: Chris@483: QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok | Chris@483: QDialogButtonBox::Cancel); Chris@483: layout->addWidget(bb, 3, 0); Chris@483: connect(bb, SIGNAL(accepted()), d, SLOT(accept())); Chris@483: connect(bb, SIGNAL(accepted()), d, SLOT(accept())); Chris@483: connect(bb, SIGNAL(rejected()), d, SLOT(reject())); Chris@483: Chris@483: if (d->exec() == QDialog::Accepted) { Chris@483: Chris@483: QString name = lineEdit->text(); Chris@483: name.replace(QRegExp("[^\\w\\s\\.\"'-]"), "_"); Chris@483: Chris@483: ResourceFinder rf; Chris@483: QString dir = rf.getResourceSaveDir("templates"); Chris@483: QString filename = QString("%1/%2.svt").arg(dir).arg(name); Chris@483: if (QFile(filename).exists()) { Chris@483: if (QMessageBox::warning(this, Chris@483: tr("Template file exists"), Chris@483: tr("Template file exists

The template \"%1\" already exists.
Overwrite it?").arg(name), Chris@483: QMessageBox::Ok | QMessageBox::Cancel, Chris@483: QMessageBox::Cancel) != QMessageBox::Ok) { Chris@1516: delete d; Chris@483: return; Chris@483: } Chris@483: } Chris@483: Chris@483: if (saveSessionTemplate(filename)) { Chris@483: if (makeDefault->isChecked()) { Chris@483: setDefaultSessionTemplate(name); Chris@483: } Chris@431: } Chris@431: } Chris@1516: Chris@1516: delete d; Chris@425: } Chris@425: Chris@425: void Chris@425: MainWindow::manageSavedTemplates() Chris@425: { Chris@425: ResourceFinder rf; Chris@1930: openLocalFolder(rf.getResourceSaveDir("templates")); Chris@423: } Chris@423: Chris@423: void Chris@200: MainWindow::paneAdded(Pane *pane) Chris@200: { Chris@200: if (m_overview) m_overview->registerView(pane); Chris@2303: if (pane) { Chris@2303: connect(pane, SIGNAL(cancelButtonPressed(Layer *)), Chris@2303: this, SLOT(paneCancelButtonPressed(Layer *))); Chris@2303: } Chris@200: } Chris@200: Chris@200: void Chris@200: MainWindow::paneHidden(Pane *pane) Chris@200: { Chris@200: if (m_overview) m_overview->unregisterView(pane); Chris@200: } Chris@200: Chris@200: void Chris@200: MainWindow::paneAboutToBeDeleted(Pane *pane) Chris@200: { Chris@200: if (m_overview) m_overview->unregisterView(pane); Chris@200: } Chris@200: Chris@200: void Chris@2303: MainWindow::paneCancelButtonPressed(Layer *layer) Chris@2303: { Chris@2303: Pane *pane = qobject_cast(sender()); Chris@2303: bool found = false; Chris@2303: if (pane && layer) { Chris@2303: for (int i = 0; i < pane->getLayerCount(); ++i) { Chris@2303: if (pane->getLayer(i) == layer) { Chris@2303: found = true; Chris@2303: break; Chris@2303: } Chris@2303: } Chris@2303: } Chris@2303: if (!found) { Chris@2303: SVDEBUG << "MainWindow::paneCancelButtonPressed: Unknown layer in pane" Chris@2303: << endl; Chris@2303: return; Chris@2303: } Chris@2303: Chris@2303: SVDEBUG << "MainWindow::paneCancelButtonPressed: Layer " << layer << endl; Chris@2303: Chris@2304: // We need to ensure that the transform that is populating this Chris@2304: // layer's model is stopped - that is the main reason to use Chris@2304: // Cancel after all. It would also be a good idea to remove the Chris@2304: // incomplete layer from both the view and the undo/redo stack. Chris@2304: Chris@2304: // Deleting the target model will ensure that the transform gets Chris@2304: // stopped, but removing the layer from the view is not enough to Chris@2304: // delete the model, because a reference to the layer remains on Chris@2304: // the undo/redo stack. If we also replace the model id with None Chris@2304: // in the layer, that does the trick. Chris@2304: Chris@2304: m_document->setModel(layer, {}); Chris@2303: m_document->removeLayerFromView(pane, layer); Chris@2304: Chris@2304: // We still have a layer with no model on the undo/redo stack, Chris@2304: // which is a pity. I'm not sure we can easily remove it, since Chris@2304: // other commands may have been pushed on the stack since, so Chris@2304: // let's just leave that for now. Chris@2304: Chris@2303: updateMenuStates(); Chris@2303: } Chris@2303: Chris@2303: void Chris@193: MainWindow::paneDropAccepted(Pane *pane, QStringList uriList) Chris@193: { Chris@193: if (pane) m_paneStack->setCurrentPane(pane); Chris@193: Chris@193: for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) { Chris@193: Chris@295: FileOpenStatus status; Chris@295: Chris@295: if (i == uriList.begin()) { Chris@844: status = openPath(*i, ReplaceCurrentPane); Chris@295: } else { Chris@844: status = openPath(*i, CreateAdditionalModel); Chris@295: } Chris@193: Chris@193: if (status == FileOpenFailed) { Chris@247: emit hideSplash(); Chris@193: QMessageBox::critical(this, tr("Failed to open dropped URL"), Chris@193: tr("Open failed

Dropped URL \"%1\" could not be opened").arg(*i)); Chris@295: break; Chris@193: } else if (status == FileOpenWrongMode) { Chris@247: emit hideSplash(); Chris@193: QMessageBox::critical(this, tr("Failed to open dropped URL"), Chris@294: 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(*i)); Chris@295: break; Chris@295: } else if (status == FileOpenCancelled) { Chris@295: break; Chris@193: } Chris@193: } Chris@193: } Chris@193: Chris@193: void Chris@193: MainWindow::paneDropAccepted(Pane *pane, QString text) Chris@193: { Chris@193: if (pane) m_paneStack->setCurrentPane(pane); Chris@193: Chris@193: QUrl testUrl(text); Chris@193: if (testUrl.scheme() == "file" || Chris@193: testUrl.scheme() == "http" || Chris@193: testUrl.scheme() == "ftp") { Chris@193: QStringList list; Chris@193: list.push_back(text); Chris@193: paneDropAccepted(pane, list); Chris@193: return; Chris@193: } Chris@193: Chris@193: //!!! open as text -- but by importing as if a CSV, or just adding Chris@193: //to a text layer? Chris@193: } Chris@193: Chris@193: void Chris@0: MainWindow::closeEvent(QCloseEvent *e) Chris@0: { Chris@2027: SVDEBUG << "MainWindow::closeEvent" << endl; Chris@118: Chris@136: if (m_openingAudioFile) { Chris@2027: SVCERR << "Busy - ignoring close event" << endl; Chris@1770: e->ignore(); Chris@1770: return; Chris@136: } Chris@136: Chris@70: if (!m_abandoning && !checkSaveModified()) { Chris@2027: SVCERR << "Close refused by user - ignoring close event" << endl; Chris@1770: e->ignore(); Chris@1770: return; Chris@0: } Chris@0: Chris@5: QSettings settings; Chris@5: settings.beginGroup("MainWindow"); Chris@624: settings.setValue("maximised", isMaximized()); Chris@624: if (!isMaximized()) { Chris@624: settings.setValue("size", size()); Chris@624: settings.setValue("position", pos()); Chris@624: } Chris@5: settings.endGroup(); Chris@5: Chris@163: if (m_preferencesDialog && Chris@163: m_preferencesDialog->isVisible()) { Chris@1434: m_preferencesDialog->applicationClosing(true); Chris@163: } Chris@163: Chris@2172: stop(); Chris@200: closeSession(); Chris@200: Chris@0: e->accept(); Chris@489: Chris@0: return; Chris@0: } Chris@0: Chris@0: bool Chris@11: MainWindow::commitData(bool mayAskUser) Chris@11: { Chris@11: if (mayAskUser) { Chris@163: bool rv = checkSaveModified(); Chris@163: if (rv) { Chris@163: if (m_preferencesDialog && Chris@163: m_preferencesDialog->isVisible()) { Chris@163: m_preferencesDialog->applicationClosing(false); Chris@163: } Chris@163: } Chris@163: return rv; Chris@11: } else { Chris@163: if (m_preferencesDialog && Chris@163: m_preferencesDialog->isVisible()) { Chris@163: m_preferencesDialog->applicationClosing(true); Chris@163: } Chris@11: if (!m_documentModified) return true; Chris@11: Chris@11: // If we can't check with the user first, then we can't save Chris@11: // to the original session file (even if we have it) -- have Chris@11: // to use a temporary file Chris@11: Chris@11: QString svDirBase = ".sv1"; Chris@11: QString svDir = QDir::home().filePath(svDirBase); Chris@11: Chris@11: if (!QFileInfo(svDir).exists()) { Chris@11: if (!QDir::home().mkdir(svDirBase)) return false; Chris@11: } else { Chris@11: if (!QFileInfo(svDir).isDir()) return false; Chris@11: } Chris@11: Chris@11: // This name doesn't have to be unguessable Chris@93: #ifndef _WIN32 Chris@11: QString fname = QString("tmp-%1-%2.sv") Chris@11: .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")) Chris@11: .arg(QProcess().pid()); Chris@93: #else Chris@93: QString fname = QString("tmp-%1.sv") Chris@93: .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")); Chris@93: #endif Chris@11: QString fpath = QDir(svDir).filePath(fname); Chris@11: if (saveSessionFile(fpath)) { Chris@34: m_recentFiles.addFile(fpath); Chris@310: emit activity(tr("Export image to \"%1\"").arg(fpath)); Chris@11: return true; Chris@11: } else { Chris@11: return false; Chris@11: } Chris@11: } Chris@11: } Chris@11: Chris@11: bool Chris@0: MainWindow::checkSaveModified() Chris@0: { Chris@0: // Called before some destructive operation (e.g. new session, Chris@0: // exit program). Return true if we can safely proceed, false to Chris@0: // cancel. Chris@0: Chris@0: if (!m_documentModified) return true; Chris@0: Chris@247: emit hideSplash(); Chris@247: Chris@0: int button = Chris@1770: QMessageBox::warning(this, Chris@1770: tr("Session modified"), Chris@1770: tr("Session modified

The current session has been modified.
Do you want to save it?"), Chris@1770: QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, Chris@165: QMessageBox::Yes); Chris@0: Chris@0: if (button == QMessageBox::Yes) { Chris@1770: saveSession(); Chris@1770: if (m_documentModified) { // save failed -- don't proceed! Chris@1770: return false; Chris@1770: } else { Chris@0: return true; // saved, so it's safe to continue now Chris@0: } Chris@0: } else if (button == QMessageBox::No) { Chris@1770: m_documentModified = false; // so we know to abandon it Chris@1770: return true; Chris@0: } Chris@0: Chris@0: // else cancel Chris@0: return false; Chris@0: } Chris@0: Chris@290: bool Chris@294: MainWindow::shouldCreateNewSessionForRDFAudio(bool *cancel) Chris@290: { Chris@294: //!!! This is very similar to part of MainWindowBase::openAudio -- Chris@294: //!!! make them a bit more uniform Chris@294: Chris@294: QSettings settings; Chris@294: settings.beginGroup("MainWindow"); Chris@294: bool prevNewSession = settings.value("newsessionforrdfaudio", true).toBool(); Chris@294: settings.endGroup(); Chris@294: bool newSession = true; Chris@294: Chris@294: QStringList items; Chris@294: items << tr("Close the current session and create a new one") Chris@294: << tr("Add this data to the current session"); Chris@294: Chris@294: bool ok = false; Chris@294: QString item = ListInputDialog::getItem Chris@294: (this, tr("Select target for import"), Chris@294: tr("Select a target for import

This RDF document refers to one or more audio files.
You already have an audio waveform loaded.
What would you like to do with the new data?"), Chris@294: items, prevNewSession ? 0 : 1, &ok); Chris@294: Chris@294: if (!ok || item.isEmpty()) { Chris@294: *cancel = true; Chris@290: return false; Chris@290: } Chris@294: Chris@294: newSession = (item == items[0]); Chris@294: settings.beginGroup("MainWindow"); Chris@294: settings.setValue("newsessionforrdfaudio", newSession); Chris@294: settings.endGroup(); Chris@294: Chris@294: if (newSession) return true; Chris@294: else return false; Chris@290: } Chris@290: Chris@0: void Chris@0: MainWindow::saveSession() Chris@0: { Chris@0: if (m_sessionFile != "") { Chris@1770: if (!saveSessionFile(m_sessionFile)) { Chris@1770: QMessageBox::critical(this, tr("Failed to save file"), Chris@1770: tr("Save failed

Session file \"%1\" could not be saved.").arg(m_sessionFile)); Chris@1770: } else { Chris@1770: CommandHistory::getInstance()->documentSaved(); Chris@1770: documentRestored(); Chris@1770: } Chris@0: } else { Chris@1770: saveSessionAs(); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::saveSessionAs() Chris@0: { Chris@0: QString orig = m_audioFile; Chris@0: if (orig == "") orig = "."; Chris@0: else orig = QFileInfo(orig).absoluteDir().canonicalPath(); Chris@0: Chris@88: QString path = getSaveFileName(FileFinder::SessionFile); Chris@81: Chris@81: if (path == "") return; Chris@0: Chris@0: if (!saveSessionFile(path)) { Chris@1770: QMessageBox::critical(this, tr("Failed to save file"), Chris@1770: tr("Save failed

Session file \"%1\" could not be saved.").arg(path)); Chris@0: } else { Chris@1770: setWindowTitle(tr("%1: %2") Chris@518: .arg(QApplication::applicationName()) Chris@1770: .arg(QFileInfo(path).fileName())); Chris@1770: m_sessionFile = path; Chris@1770: CommandHistory::getInstance()->documentSaved(); Chris@1770: documentRestored(); Chris@34: m_recentFiles.addFile(path); Chris@310: emit activity(tr("Save session as \"%1\"").arg(path)); Chris@0: } Chris@0: } Chris@0: Chris@90: void Chris@72: MainWindow::preferenceChanged(PropertyContainer::PropertyName name) Chris@72: { Chris@200: MainWindowBase::preferenceChanged(name); Chris@200: Chris@1448: if (name == "Background Mode") { Chris@1448: coloursChanged(); Chris@200: } Chris@0: } Chris@0: Chris@0: void Chris@1448: MainWindow::coloursChanged() Chris@1448: { Chris@1448: QSettings settings; Chris@1448: settings.beginGroup("Preferences"); Chris@1448: QString defaultColourName(tr("Green")); Chris@1448: if (m_viewManager && m_viewManager->getGlobalDarkBackground()) { Chris@1448: defaultColourName = tr("Bright Green"); Chris@1448: } Chris@1448: ColourDatabase *cdb = ColourDatabase::getInstance(); cannam@1463: QColor colour = QColor cannam@1463: (settings.value("overview-colour", cannam@1463: cdb->getColour(defaultColourName).name()).toString()); Chris@1448: settings.endGroup(); Chris@1448: Chris@1448: int index = cdb->getColourIndex(colour); Chris@1448: if (index >= 0) { Chris@1448: m_panLayer->setBaseColour(index); Chris@1448: } Chris@1448: } Chris@1448: Chris@1448: void Chris@239: MainWindow::propertyStacksResized(int width) Chris@239: { Chris@438: // SVDEBUG << "MainWindow::propertyStacksResized(" << width << ")" << endl; Chris@239: Chris@239: if (!m_playControlsSpacer) return; Chris@239: Chris@239: int spacerWidth = width - m_playControlsWidth - 4; Chris@239: Chris@438: // SVDEBUG << "resizing spacer from " << m_playControlsSpacer->width() << " to " << spacerWidth << endl; Chris@239: Chris@239: m_playControlsSpacer->setFixedSize(QSize(spacerWidth, 2)); Chris@239: } Chris@239: Chris@239: void Chris@0: MainWindow::addPane() Chris@0: { Chris@0: QObject *s = sender(); Chris@0: QAction *action = dynamic_cast(s); Chris@753: Chris@753: cerr << "addPane: sender is " << s << ", action is " << action << ", name " << action->text() << endl; Chris@0: Chris@0: if (!action) { Chris@1770: cerr << "WARNING: MainWindow::addPane: sender is not an action" Chris@1770: << endl; Chris@1770: return; Chris@0: } Chris@0: Chris@2093: PaneActions::iterator i = m_paneActions.begin(); Chris@2093: while (i != m_paneActions.end()) { Chris@2093: if (i->first == action) break; Chris@2093: ++i; Chris@2093: } Chris@0: Chris@0: if (i == m_paneActions.end()) { Chris@1770: cerr << "WARNING: MainWindow::addPane: unknown action " Chris@2093: << action->objectName() << endl; Chris@753: cerr << "known actions are:" << endl; Chris@2093: for (PaneActions::const_iterator i = m_paneActions.begin(); Chris@753: i != m_paneActions.end(); ++i) { Chris@753: cerr << i->first << ", name " << i->first->text() << endl; Chris@753: } Chris@1770: return; Chris@0: } Chris@0: Chris@69: addPane(i->second, action->text()); Chris@69: } Chris@69: Chris@69: void Chris@232: MainWindow::addPane(const LayerConfiguration &configuration, QString text) Chris@69: { Chris@69: CommandHistory::getInstance()->startCompoundOperation(text, true); Chris@0: Chris@0: AddPaneCommand *command = new AddPaneCommand(this); Chris@0: CommandHistory::getInstance()->addCommand(command); Chris@0: Chris@0: Pane *pane = command->getPane(); Chris@0: Chris@69: if (configuration.layer == LayerFactory::Spectrum) { Chris@109: pane->setPlaybackFollow(PlaybackScrollContinuous); Chris@110: pane->setFollowGlobalZoom(false); Chris@2011: pane->setZoomLevel(ZoomLevel(ZoomLevel::FramesPerPixel, 512)); Chris@7: } Chris@7: Chris@69: if (configuration.layer != LayerFactory::TimeRuler && Chris@69: configuration.layer != LayerFactory::Spectrum) { Chris@8: Chris@1770: if (!m_timeRulerLayer) { Chris@1770: // cerr << "no time ruler layer, creating one" << endl; Chris@1770: m_timeRulerLayer = m_document->createMainModelLayer Chris@1770: (LayerFactory::TimeRuler); Chris@1770: } Chris@1770: Chris@1770: // SVDEBUG << "adding time ruler layer " << m_timeRulerLayer << endl; Chris@1770: Chris@1770: m_document->addLayerToView(pane, m_timeRulerLayer); Chris@0: } Chris@0: Chris@69: Layer *newLayer = m_document->createLayer(configuration.layer); Chris@69: Chris@2300: ModelId suggestedModelId = configuration.sourceModel; Chris@2300: ModelId modelId; Chris@2300: Chris@2300: if (!suggestedModelId.isNone()) { Chris@66: Chris@66: // check its validity Chris@2300: std::vector inputModels = m_document->getTransformInputModels(); Chris@2300: for (auto im: inputModels) { Chris@2300: if (im == suggestedModelId) { Chris@2300: modelId = suggestedModelId; Chris@66: } Chris@66: } Chris@66: Chris@2300: if (modelId.isNone()) { Chris@2300: cerr << "WARNING: Model " << modelId Chris@2300: << " appears in pane action map, but is not reported " Chris@2300: << "by document as a valid transform source" << endl; Chris@66: } Chris@66: } Chris@66: Chris@2300: if (modelId.isNone()) { Chris@2300: modelId = m_document->getMainModel(); Chris@346: } Chris@66: Chris@2300: m_document->setModel(newLayer, modelId); Chris@66: Chris@69: m_document->setChannel(newLayer, configuration.channel); Chris@0: m_document->addLayerToView(pane, newLayer); Chris@0: Chris@0: m_paneStack->setCurrentPane(pane); Chris@70: m_paneStack->setCurrentLayer(pane, newLayer); Chris@0: Chris@438: // SVDEBUG << "MainWindow::addPane: global centre frame is " Chris@433: // << m_viewManager->getGlobalCentreFrame() << endl; Chris@130: // pane->setCentreFrame(m_viewManager->getGlobalCentreFrame()); Chris@73: Chris@0: CommandHistory::getInstance()->endCompoundOperation(); Chris@0: Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::addLayer() Chris@0: { Chris@0: QObject *s = sender(); Chris@0: QAction *action = dynamic_cast(s); Chris@0: Chris@0: if (!action) { Chris@1770: cerr << "WARNING: MainWindow::addLayer: sender is not an action" Chris@1770: << endl; Chris@1770: return; Chris@0: } Chris@0: Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: Chris@0: if (!pane) { Chris@1770: cerr << "WARNING: MainWindow::addLayer: no current pane" << endl; Chris@1770: return; Chris@0: } Chris@0: Chris@2093: ExistingLayerActions::iterator ei = m_existingLayerActions.begin(); Chris@2093: while (ei != m_existingLayerActions.end()) { Chris@2093: if (ei->first == action) break; Chris@2093: ++ei; Chris@2093: } Chris@0: Chris@0: if (ei != m_existingLayerActions.end()) { Chris@1770: Layer *newLayer = ei->second; Chris@1770: m_document->addLayerToView(pane, newLayer); Chris@1770: m_paneStack->setCurrentLayer(pane, newLayer); Chris@1770: return; Chris@0: } Chris@0: Chris@2093: ei = m_sliceActions.begin(); Chris@2093: while (ei != m_sliceActions.end()) { Chris@2093: if (ei->first == action) break; Chris@2093: ++ei; Chris@2093: } Chris@95: Chris@95: if (ei != m_sliceActions.end()) { Chris@95: Layer *newLayer = m_document->createLayer(LayerFactory::Slice); Chris@95: // document->setModel(newLayer, ei->second->getModel()); Chris@95: SliceableLayer *source = dynamic_cast(ei->second); Chris@95: SliceLayer *dest = dynamic_cast(newLayer); Chris@95: if (source && dest) { Chris@205: //!!!??? Chris@95: dest->setSliceableModel(source->getSliceableModel()); Chris@95: connect(source, SIGNAL(sliceableModelReplaced(const Model *, const Model *)), Chris@95: dest, SLOT(sliceableModelReplaced(const Model *, const Model *))); Chris@95: connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)), Chris@95: dest, SLOT(modelAboutToBeDeleted(Model *))); Chris@95: } Chris@1770: m_document->addLayerToView(pane, newLayer); Chris@1770: m_paneStack->setCurrentLayer(pane, newLayer); Chris@1770: return; Chris@95: } Chris@95: Chris@2093: TransformActions::iterator i = m_transformActions.begin(); Chris@2093: while (i != m_transformActions.end()) { Chris@2093: if (i->first == action) break; Chris@2093: ++i; Chris@2093: } Chris@34: Chris@34: if (i == m_transformActions.end()) { Chris@0: Chris@2093: LayerActions::iterator i = m_layerActions.begin(); Chris@2093: while (i != m_layerActions.end()) { Chris@2093: if (i->first == action) break; Chris@2093: ++i; Chris@2093: } Chris@1770: Chris@1770: if (i == m_layerActions.end()) { Chris@1770: cerr << "WARNING: MainWindow::addLayer: unknown action " Chris@1770: << action->objectName() << endl; Chris@1770: return; Chris@1770: } Chris@1770: Chris@1770: LayerFactory::LayerType type = i->second.layer; Chris@1770: Chris@1770: LayerFactory::LayerTypeSet emptyTypes = Chris@1770: LayerFactory::getInstance()->getValidEmptyLayerTypes(); Chris@1770: Chris@2126: Layer *newLayer = nullptr; Chris@1770: Chris@1770: if (emptyTypes.find(type) != emptyTypes.end()) { Chris@1770: Chris@1770: newLayer = m_document->createEmptyLayer(type); Chris@217: if (newLayer) { Chris@2093: for (auto &a : m_toolActions) { Chris@2093: if (a.first == ViewManager::DrawMode) { Chris@2093: a.second->trigger(); Chris@2093: break; Chris@2093: } Chris@2093: } Chris@217: } Chris@0: Chris@1770: } else { Chris@0: Chris@2300: ModelId modelId = i->second.sourceModel; Chris@2300: Chris@2300: if (modelId.isNone()) { Chris@346: if (type == LayerFactory::TimeRuler) { Chris@346: newLayer = m_document->createMainModelLayer(type); Chris@346: } else { Chris@346: // if model is unspecified and this is not a Chris@415: // time-ruler layer, use any plausible model from Chris@415: // the current pane -- this is the case for Chris@415: // right-button menu layer additions Chris@415: Pane::ModelSet ms = pane->getModels(); Chris@2300: for (ModelId m: ms) { Chris@2300: if (ModelById::isa(m)) { Chris@2300: modelId = m; Chris@2300: } Chris@346: } Chris@2300: if (modelId.isNone()) { Chris@2300: modelId = getMainModelId(); Chris@2300: } Chris@346: } Chris@346: } Chris@346: Chris@2300: if (!modelId.isNone()) { Chris@238: newLayer = m_document->createLayer(type); Chris@2300: if (m_document->isKnownModel(modelId)) { Chris@238: m_document->setChannel(newLayer, i->second.channel); Chris@2300: m_document->setModel(newLayer, modelId); Chris@238: } else { Chris@2300: SVCERR << "WARNING: MainWindow::addLayer: unknown model " Chris@2300: << modelId << " in layer action map" << endl; Chris@238: } Chris@232: } Chris@238: } Chris@0: Chris@217: if (newLayer) { Chris@217: m_document->addLayerToView(pane, newLayer); Chris@217: m_paneStack->setCurrentLayer(pane, newLayer); Chris@217: } Chris@0: Chris@1770: return; Chris@0: } Chris@0: Chris@224: //!!! want to do something like this, but it's not supported in Chris@224: //ModelTransformerFactory yet Chris@224: /* Chris@0: int channel = -1; Chris@0: // pick up the default channel from any existing layers on the same pane Chris@0: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@1770: int c = LayerFactory::getInstance()->getChannel(pane->getLayer(j)); Chris@1770: if (c != -1) { Chris@1770: channel = c; Chris@1770: break; Chris@1770: } Chris@0: } Chris@224: */ Chris@0: Chris@33: // We always ask for configuration, even if the plugin isn't Chris@33: // supposed to be configurable, because we need to let the user Chris@33: // change the execution context (block size etc). Chris@0: Chris@224: QString transformId = i->second; Chris@274: Chris@274: addLayer(transformId); Chris@274: } Chris@274: Chris@274: void Chris@274: MainWindow::addLayer(QString transformId) Chris@274: { Chris@274: Pane *pane = m_paneStack->getCurrentPane(); Chris@274: if (!pane) { Chris@1770: cerr << "WARNING: MainWindow::addLayer: no current pane" << endl; Chris@1770: return; Chris@274: } Chris@274: Chris@1558: Transform transform; Chris@1558: try { Chris@1558: transform = TransformFactory::getInstance()-> Chris@1558: getDefaultTransformFor(transformId); Chris@1558: } catch (std::exception &e) { // e.g. Piper server failure Chris@1558: QMessageBox::critical Chris@1558: (this, tr("Failed to query transform attributes"), Chris@1558: tr("Failed to query transform attributes

Plugin or server error: %1

") Chris@1558: .arg(e.what())); Chris@1558: return; Chris@1558: } Chris@27: Chris@2300: std::vector candidateInputModels = Chris@224: m_document->getTransformInputModels(); Chris@53: Chris@2300: ModelId defaultInputModelId; Chris@895: Chris@219: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@895: Chris@219: Layer *layer = pane->getLayer(j); Chris@219: if (!layer) continue; Chris@895: Chris@243: if (LayerFactory::getInstance()->getLayerType(layer) != Chris@243: LayerFactory::Waveform && Chris@2300: !layer->isLayerOpaque()) { Chris@2300: continue; Chris@2300: } Chris@2300: Chris@2300: ModelId modelId = layer->getModel(); Chris@2300: if (modelId.isNone()) continue; Chris@2300: Chris@2300: for (ModelId candidateId: candidateInputModels) { Chris@2300: if (candidateId == modelId) { Chris@2300: defaultInputModelId = modelId; Chris@219: break; Chris@219: } Chris@219: } Chris@895: Chris@2300: if (!defaultInputModelId.isNone()) break; Chris@219: } Chris@895: Chris@2300: ModelId aggregate; Chris@1620: Chris@895: if (candidateInputModels.size() > 1) { Chris@895: // Add an aggregate model as another option Chris@895: AggregateWaveModel::ChannelSpecList sl; Chris@2300: for (ModelId mid: candidateInputModels) { Chris@2300: if (ModelById::isa(mid)) { Chris@2300: sl.push_back(AggregateWaveModel::ModelChannelSpec(mid, -1)); Chris@895: } Chris@895: } Chris@895: if (!sl.empty()) { Chris@2300: auto aggregate = std::make_shared(sl); Chris@895: aggregate->setObjectName(tr("Multiplex all of the above")); Chris@2300: candidateInputModels.push_back(ModelById::add(aggregate)); Chris@895: } Chris@895: } Chris@219: Chris@914: sv_frame_t startFrame = 0, duration = 0; Chris@914: sv_frame_t endFrame = 0; Chris@184: m_viewManager->getSelection().getExtents(startFrame, endFrame); Chris@184: if (endFrame > startFrame) duration = endFrame - startFrame; Chris@184: else startFrame = 0; Chris@184: Chris@357: TransformUserConfigurator configurator; Chris@357: Chris@224: ModelTransformer::Input input = ModelTransformerFactory::getInstance()-> Chris@224: getConfigurationForTransform Chris@211: (transform, Chris@211: candidateInputModels, Chris@2300: defaultInputModelId, Chris@211: m_playSource, Chris@211: startFrame, Chris@357: duration, Chris@357: &configurator); Chris@211: Chris@2300: if (!aggregate.isNone()) { Chris@1623: if (input.getModel() == aggregate) { Chris@2300: if (auto aggregateModel = ModelById::get(aggregate)) { Chris@2300: aggregateModel->setObjectName(tr("Multiplexed audio")); Chris@2300: } Chris@2303: m_document->addNonDerivedModel(aggregate); Chris@1623: } else { Chris@2300: ModelById::release(aggregate); Chris@1623: } Chris@1620: } Chris@1620: Chris@2300: if (input.getModel().isNone()) return; Chris@224: Chris@438: // SVDEBUG << "MainWindow::addLayer: Input model is " << input.getModel() << " \"" << input.getModel()->objectName() << "\"" << endl << "transform:" << endl << transform.toXmlString() << endl; Chris@224: Chris@1558: try { Chris@1558: Layer *newLayer = m_document->createDerivedLayer(transform, input); Chris@1558: if (newLayer) { Chris@1558: m_document->addLayerToView(pane, newLayer); Chris@1558: m_document->setChannel(newLayer, input.getChannel()); Chris@1558: m_recentTransforms.add(transformId); Chris@1558: m_paneStack->setCurrentLayer(pane, newLayer); Chris@1558: } Chris@1558: } catch (std::exception &e) { // e.g. Piper server failure Chris@1558: QMessageBox::critical Chris@1558: (this, tr("Transform failed"), Chris@1558: tr("Failed to run transform

Plugin or server error: %1

") Chris@1558: .arg(e.what())); Chris@1558: return; Chris@0: } Chris@1558: Chris@0: updateMenuStates(); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::renameCurrentLayer() Chris@0: { Chris@0: Pane *pane = m_paneStack->getCurrentPane(); Chris@0: if (pane) { Chris@1770: Layer *layer = pane->getSelectedLayer(); Chris@1770: if (layer) { Chris@1770: bool ok = false; Chris@1770: QString newName = QInputDialog::getText Chris@1770: (this, tr("Rename Layer"), Chris@1770: tr("New name for this layer:"), Chris@1770: QLineEdit::Normal, layer->objectName(), &ok); Chris@1770: if (ok) { Chris@1770: layer->setPresentationName(newName); Chris@1770: setupExistingLayersMenus(); Chris@1770: } Chris@1770: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@272: MainWindow::findTransform() Chris@272: { Chris@274: TransformFinder *finder = new TransformFinder(this); Chris@274: if (!finder->exec()) { Chris@274: delete finder; Chris@274: return; Chris@273: } Chris@274: TransformId transform = finder->getTransform(); Chris@274: delete finder; Chris@287: Chris@2126: if (getMainModel() != nullptr && m_paneStack->getCurrentPane() != nullptr) { Chris@287: addLayer(transform); Chris@287: } Chris@272: } Chris@272: Chris@272: void Chris@207: MainWindow::playSoloToggled() Chris@207: { Chris@207: MainWindowBase::playSoloToggled(); Chris@207: m_soloModified = true; Chris@207: } Chris@207: Chris@207: void Chris@206: MainWindow::alignToggled() Chris@206: { Chris@206: QAction *action = dynamic_cast(sender()); Chris@206: Chris@207: if (!m_viewManager) return; Chris@207: Chris@206: if (action) { Chris@1770: m_viewManager->setAlignMode(action->isChecked()); Chris@206: } else { Chris@1770: m_viewManager->setAlignMode(!m_viewManager->getAlignMode()); Chris@206: } Chris@206: Chris@206: if (m_viewManager->getAlignMode()) { Chris@207: m_prevSolo = m_soloAction->isChecked(); Chris@208: if (!m_soloAction->isChecked()) { Chris@208: m_soloAction->setChecked(true); Chris@208: MainWindowBase::playSoloToggled(); Chris@208: } Chris@207: m_soloModified = false; Chris@207: emit canChangeSolo(false); Chris@206: m_document->alignModels(); Chris@206: m_document->setAutoAlignment(true); Chris@206: } else { Chris@207: if (!m_soloModified) { Chris@208: if (m_soloAction->isChecked() != m_prevSolo) { Chris@208: m_soloAction->setChecked(m_prevSolo); Chris@208: MainWindowBase::playSoloToggled(); Chris@208: } Chris@207: } Chris@207: emit canChangeSolo(true); Chris@206: m_document->setAutoAlignment(false); Chris@206: } Chris@206: Chris@206: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@206: Chris@1770: Pane *pane = m_paneStack->getPane(i); Chris@1770: if (!pane) continue; Chris@206: Chris@206: pane->update(); Chris@206: } Chris@206: } Chris@206: Chris@206: void Chris@59: MainWindow::playSpeedChanged(int position) Chris@0: { Chris@1031: PlaySpeedRangeMapper mapper; Chris@60: Chris@922: double percent = m_playSpeed->mappedValue(); Chris@922: double factor = mapper.getFactorForValue(percent); Chris@60: Chris@1031: // cerr << "play speed position = " << position << " (range 0-120) percent = " << percent << " factor = " << factor << endl; Chris@1031: Chris@1031: int centre = m_playSpeed->defaultValue(); Chris@1031: Chris@1031: // Percentage is shown to 0dp if >100, to 1dp if <100; factor is Chris@1031: // shown to 3sf Chris@1031: Chris@1031: char pcbuf[30]; Chris@1031: char facbuf[30]; Chris@1031: Chris@1031: if (position == centre) { Chris@155: contextHelpChanged(tr("Playback speed: Normal")); Chris@1031: } else if (position < centre) { Chris@1031: sprintf(pcbuf, "%.1f", percent); Chris@1031: sprintf(facbuf, "%.3g", 1.0 / factor); Chris@1031: contextHelpChanged(tr("Playback speed: %1% (%2x slower)") Chris@1031: .arg(pcbuf) Chris@1031: .arg(facbuf)); Chris@155: } else { Chris@1031: sprintf(pcbuf, "%.0f", percent); Chris@1031: sprintf(facbuf, "%.3g", factor); Chris@1031: contextHelpChanged(tr("Playback speed: %1% (%2x faster)") Chris@1031: .arg(pcbuf) Chris@1031: .arg(facbuf)); Chris@155: } Chris@155: Chris@1031: m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup Chris@155: Chris@155: updateMenuStates(); Chris@16: } Chris@16: Chris@26: void Chris@155: MainWindow::speedUpPlayback() Chris@155: { Chris@155: int value = m_playSpeed->value(); Chris@155: value = value + m_playSpeed->pageStep(); Chris@155: if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum(); Chris@155: m_playSpeed->setValue(value); Chris@155: } Chris@155: Chris@155: void Chris@155: MainWindow::slowDownPlayback() Chris@155: { Chris@155: int value = m_playSpeed->value(); Chris@155: value = value - m_playSpeed->pageStep(); Chris@155: if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum(); Chris@155: m_playSpeed->setValue(value); Chris@155: } Chris@155: Chris@155: void Chris@155: MainWindow::restoreNormalPlayback() Chris@155: { Chris@155: m_playSpeed->setValue(m_playSpeed->defaultValue()); Chris@155: } Chris@155: Chris@155: void Chris@227: MainWindow::currentPaneChanged(Pane *pane) Chris@227: { Chris@228: MainWindowBase::currentPaneChanged(pane); Chris@228: Chris@227: if (!pane || !m_panLayer) return; Chris@761: Chris@761: // If this pane contains the main model, it usually makes sense to Chris@761: // show the main model in the pan layer even if it isn't the top Chris@761: // layer in the pane (e.g. if the top layer is one derived from Chris@761: // the main model). Chris@761: bool containsMainModel = false; Chris@761: for (int i = pane->getLayerCount(); i > 0; ) { Chris@761: --i; Chris@761: Layer *layer = pane->getLayer(i); Chris@761: if (layer && Chris@761: LayerFactory::getInstance()->getLayerType(layer) == Chris@761: LayerFactory::Waveform && Chris@2300: layer->getModel() == getMainModelId()) { Chris@761: containsMainModel = true; Chris@761: break; Chris@761: } Chris@761: } Chris@761: Chris@1794: bool panLayerSet = false; Chris@1794: Chris@227: for (int i = pane->getLayerCount(); i > 0; ) { Chris@227: --i; Chris@227: Layer *layer = pane->getLayer(i); Chris@2300: ModelId modelId = layer->getModel(); Chris@2300: if (ModelById::isa(modelId)) { Chris@1794: auto type = LayerFactory::getInstance()->getLayerType(layer); Chris@1794: if (type != LayerFactory::TimeRuler) { Chris@2300: updateLayerShortcutsFor(modelId); Chris@1794: } Chris@1794: if (type == LayerFactory::Waveform) { Chris@2300: m_panLayer->setModel(modelId); Chris@1794: panLayerSet = true; Chris@1794: break; Chris@227: } Chris@227: } Chris@227: } Chris@1794: Chris@1794: if (containsMainModel && !panLayerSet) { Chris@2300: m_panLayer->setModel(getMainModelId()); Chris@1794: } Chris@227: } Chris@227: Chris@227: void Chris@116: MainWindow::updateVisibleRangeDisplay(Pane *p) const Chris@116: { Chris@2300: sv_samplerate_t sampleRate = 0; Chris@2300: if (auto mm = getMainModel()) { Chris@2300: sampleRate = mm->getSampleRate(); Chris@2300: } else { Chris@2300: return; Chris@2300: } Chris@2300: if (!p) { Chris@116: return; Chris@116: } Chris@116: Chris@117: bool haveSelection = false; Chris@922: sv_frame_t startFrame = 0, endFrame = 0; Chris@117: Chris@117: if (m_viewManager && m_viewManager->haveInProgressSelection()) { Chris@117: Chris@117: bool exclusive = false; Chris@117: Selection s = m_viewManager->getInProgressSelection(exclusive); Chris@117: Chris@117: if (!s.isEmpty()) { Chris@117: haveSelection = true; Chris@117: startFrame = s.getStartFrame(); Chris@117: endFrame = s.getEndFrame(); Chris@117: } Chris@117: } Chris@117: Chris@117: if (!haveSelection) { Chris@117: startFrame = p->getFirstVisibleFrame(); Chris@117: endFrame = p->getLastVisibleFrame(); Chris@117: } Chris@117: Chris@2300: RealTime start = RealTime::frame2RealTime(startFrame, sampleRate); Chris@2300: RealTime end = RealTime::frame2RealTime(endFrame, sampleRate); Chris@116: RealTime duration = end - start; Chris@116: Chris@116: QString startStr, endStr, durationStr; Chris@116: startStr = start.toText(true).c_str(); Chris@116: endStr = end.toText(true).c_str(); Chris@116: durationStr = duration.toText(true).c_str(); Chris@116: Chris@117: if (haveSelection) { Chris@117: m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)") Chris@117: .arg(startStr).arg(endStr).arg(durationStr); Chris@117: } else { Chris@117: m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)") Chris@117: .arg(startStr).arg(endStr).arg(durationStr); Chris@117: } Chris@116: Chris@739: if (getStatusLabel()->text() != m_myStatusMessage) { Chris@739: getStatusLabel()->setText(m_myStatusMessage); Chris@737: } Chris@340: Chris@340: updatePositionStatusDisplays(); Chris@340: } Chris@340: Chris@340: void Chris@340: MainWindow::updatePositionStatusDisplays() const Chris@340: { Chris@340: if (!statusBar()->isVisible()) return; Chris@340: Chris@2126: Pane *pane = nullptr; Chris@922: sv_frame_t frame = m_viewManager->getPlaybackFrame(); Chris@340: Chris@340: if (m_paneStack) pane = m_paneStack->getCurrentPane(); Chris@340: if (!pane) return; Chris@340: Chris@340: int layers = pane->getLayerCount(); Chris@340: if (layers == 0) m_currentLabel->setText(""); Chris@340: Chris@340: for (int i = layers-1; i >= 0; --i) { Chris@340: Layer *layer = pane->getLayer(i); Chris@340: if (!layer) continue; Chris@340: if (!layer->isLayerEditable()) continue; Chris@340: QString label = layer->getLabelPreceding Chris@340: (pane->alignFromReference(frame)); Chris@340: m_currentLabel->setText(label); Chris@340: break; Chris@340: } Chris@116: } Chris@116: Chris@116: void Chris@1476: MainWindow::monitoringLevelsChanged(float left, float right) Chris@0: { Chris@1386: m_mainLevelPan->setMonitoringLevels(left, right); Chris@0: } Chris@0: Chris@0: void Chris@921: MainWindow::sampleRateMismatch(sv_samplerate_t requested, Chris@921: sv_samplerate_t actual, Chris@0: bool willResample) Chris@0: { Chris@0: if (!willResample) { Chris@247: emit hideSplash(); Chris@0: QMessageBox::information Chris@0: (this, tr("Sample rate mismatch"), Chris@193: tr("Wrong sample rate

The sample rate of this audio file (%1 Hz) does not match\nthe current playback rate (%2 Hz).

The file will play at the wrong speed and pitch.

Change the Resample mismatching files on import option under File -> Preferences if you want to alter this behaviour.") Chris@0: .arg(requested).arg(actual)); Chris@0: } Chris@0: Chris@0: updateDescriptionLabel(); Chris@0: } Chris@0: Chris@0: void Chris@42: MainWindow::audioOverloadPluginDisabled() Chris@42: { Chris@42: QMessageBox::information Chris@42: (this, tr("Audio processing overload"), Chris@193: tr("Overloaded

Audio effects plugin auditioning has been disabled due to a processing overload.")); Chris@42: } Chris@42: Chris@42: void Chris@266: MainWindow::audioTimeStretchMultiChannelDisabled() Chris@266: { Chris@266: static bool shownOnce = false; Chris@266: if (shownOnce) return; Chris@266: QMessageBox::information Chris@266: (this, tr("Audio processing overload"), Chris@266: tr("Overloaded

Audio playback speed processing has been reduced to a single channel, due to a processing overload.")); Chris@266: shownOnce = true; Chris@266: } Chris@266: Chris@1630: /* Chris@266: void Chris@1521: MainWindow::betaReleaseWarning() Chris@1521: { Chris@1521: QMessageBox::information Chris@1521: (this, tr("Beta release"), Chris@1521: tr("This is a beta release of Sonic Visualiser

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

")); Chris@1521: } Chris@1630: */ Chris@1521: Chris@1521: void Chris@1148: MainWindow::pluginPopulationWarning() Chris@1087: { Chris@1277: QString scanWarning = PluginScan::getInstance()->getStartupFailureReport(); Chris@1277: QString factWarning = TransformFactory::getInstance()->getStartupFailureReport(); Chris@1277: QString warning; Chris@1277: if (factWarning != "") { Chris@1277: // The order of events on startup implies that, if scanWarning Chris@1277: // and factWarning are both present, then we have already been Chris@1277: // called once for scanWarning so don't want to report it again Chris@1277: warning = factWarning; Chris@1277: } else if (scanWarning != "") { Chris@1277: warning = scanWarning; Chris@1277: } Chris@1277: if (warning != "") { Chris@1277: emit hideSplash(); Chris@2067: QMessageBox box; Chris@2067: box.setWindowTitle(tr("Problems loading plugins")); Chris@2067: box.setText(tr("Failed to load plugins")); Chris@2067: box.setInformativeText(warning); Chris@2067: box.setIcon(QMessageBox::Warning); Chris@2067: box.setStandardButtons(QMessageBox::Ok); Chris@2067: box.exec(); Chris@1277: } Chris@1087: } Chris@1087: Chris@1087: void Chris@304: MainWindow::midiEventsAvailable() Chris@304: { Chris@2126: Pane *currentPane = nullptr; Chris@2126: NoteLayer *currentNoteLayer = nullptr; Chris@2126: TimeValueLayer *currentTimeValueLayer = nullptr; Chris@307: Chris@793: if (m_paneStack) { Chris@793: currentPane = m_paneStack->getCurrentPane(); Chris@793: } Chris@793: Chris@307: if (currentPane) { Chris@307: currentNoteLayer = dynamic_cast Chris@307: (currentPane->getSelectedLayer()); Chris@309: currentTimeValueLayer = dynamic_cast Chris@309: (currentPane->getSelectedLayer()); Chris@838: } else { Chris@793: // discard these events Chris@793: while (m_midiInput->getEventsAvailable() > 0) { Chris@793: (void)m_midiInput->readEvent(); Chris@793: } Chris@793: return; Chris@793: } Chris@793: Chris@305: // This is called through a serialised signal/slot invocation Chris@305: // (across threads). It could happen quite some time after the Chris@305: // event was actually received, which is why event timestamping Chris@305: // happens in the MIDI input class and not here. Chris@307: Chris@305: while (m_midiInput->getEventsAvailable() > 0) { Chris@308: Chris@305: MIDIEvent ev(m_midiInput->readEvent()); Chris@307: Chris@922: sv_frame_t frame = currentPane->alignFromReference(ev.getTime()); Chris@309: Chris@308: bool noteOn = (ev.getMessageType() == MIDIConstants::MIDI_NOTE_ON && Chris@308: ev.getVelocity() > 0); Chris@308: Chris@308: bool noteOff = (ev.getMessageType() == MIDIConstants::MIDI_NOTE_OFF || Chris@308: (ev.getMessageType() == MIDIConstants::MIDI_NOTE_ON && Chris@308: ev.getVelocity() == 0)); Chris@308: Chris@307: if (currentNoteLayer) { Chris@307: Chris@310: if (!m_playSource || !m_playSource->isPlaying()) continue; Chris@310: Chris@308: if (noteOn) { Chris@307: Chris@309: currentNoteLayer->addNoteOn(frame, Chris@307: ev.getPitch(), Chris@307: ev.getVelocity()); Chris@307: Chris@308: } else if (noteOff) { Chris@307: Chris@309: currentNoteLayer->addNoteOff(frame, Chris@307: ev.getPitch()); Chris@307: Chris@307: } Chris@307: Chris@309: continue; Chris@309: } Chris@309: Chris@309: if (currentTimeValueLayer) { Chris@308: Chris@308: if (!noteOn) continue; Chris@310: Chris@310: if (!m_playSource || !m_playSource->isPlaying()) continue; Chris@310: Chris@2300: ModelId modelId = currentTimeValueLayer->getModel(); Chris@2300: if (ModelById::isa(modelId)) { Chris@2226: Event point(frame, float(ev.getPitch() % 12), ""); Chris@2226: AddEventCommand *command = new AddEventCommand Chris@2300: (modelId.untyped, point, tr("Add Point")); Chris@309: CommandHistory::getInstance()->addCommand(command); Chris@309: } Chris@838: Chris@309: continue; Chris@305: } Chris@309: Chris@838: // This is reached only if !currentNoteLayer and Chris@838: // !currentTimeValueLayer, i.e. there is some other sort of Chris@838: // layer that may be insertable-into Chris@838: Chris@309: if (!noteOn) continue; Chris@309: insertInstantAt(ev.getTime()); Chris@304: } Chris@304: } Chris@304: Chris@304: void Chris@730: MainWindow::playStatusChanged(bool ) Chris@305: { Chris@2126: Pane *currentPane = nullptr; Chris@2126: NoteLayer *currentNoteLayer = nullptr; Chris@307: Chris@307: if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); Chris@307: if (currentPane) { Chris@307: currentNoteLayer = dynamic_cast(currentPane->getSelectedLayer()); Chris@307: } Chris@307: Chris@307: if (currentNoteLayer) { Chris@307: currentNoteLayer->abandonNoteOns(); Chris@307: } Chris@305: } Chris@305: Chris@305: void Chris@200: MainWindow::layerRemoved(Layer *layer) Chris@0: { Chris@95: setupExistingLayersMenus(); Chris@200: MainWindowBase::layerRemoved(layer); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::layerInAView(Layer *layer, bool inAView) Chris@0: { Chris@95: setupExistingLayersMenus(); Chris@200: MainWindowBase::layerInAView(layer, inAView); Chris@0: } Chris@0: Chris@0: void Chris@2300: MainWindow::modelAdded(ModelId modelId) Chris@0: { Chris@2300: MainWindowBase::modelAdded(modelId); Chris@2300: if (ModelById::isa(modelId)) { Chris@66: setupPaneAndLayerMenus(); Chris@66: } Chris@0: } Chris@0: Chris@0: void Chris@2300: MainWindow::mainModelChanged(ModelId modelId) Chris@0: { Chris@2300: m_panLayer->setModel(modelId); Chris@2300: Chris@2300: MainWindowBase::mainModelChanged(modelId); Chris@200: Chris@1055: if (m_playTarget || m_audioIO) { Chris@1386: connect(m_mainLevelPan, SIGNAL(levelChanged(float)), Chris@1035: this, SLOT(mainModelGainChanged(float))); Chris@1386: connect(m_mainLevelPan, SIGNAL(panChanged(float)), Chris@1386: this, SLOT(mainModelPanChanged(float))); Chris@1035: } Chris@1035: } Chris@1035: Chris@1035: void Chris@1035: MainWindow::mainModelGainChanged(float gain) Chris@1035: { Chris@1035: if (m_playTarget) { Chris@1035: m_playTarget->setOutputGain(gain); Chris@1055: } else if (m_audioIO) { Chris@1055: m_audioIO->setOutputGain(gain); Chris@200: } Chris@0: } Chris@0: Chris@0: void Chris@1386: MainWindow::mainModelPanChanged(float balance) Chris@1386: { Chris@1386: // this is indeed stereo balance rather than pan Chris@1386: if (m_playTarget) { Chris@1386: m_playTarget->setOutputBalance(balance); Chris@1386: } else if (m_audioIO) { Chris@1386: m_audioIO->setOutputBalance(balance); Chris@1386: } Chris@1386: } Chris@1386: Chris@1386: void Chris@200: MainWindow::setInstantsNumbering() Chris@0: { Chris@200: QAction *a = dynamic_cast(sender()); Chris@200: if (!a) return; Chris@200: Chris@2093: int type = 0; Chris@2093: for (auto &ai : m_numberingActions) { Chris@2093: if (ai.first == a) type = ai.second; Chris@2093: } Chris@200: Chris@200: if (m_labeller) m_labeller->setType(Labeller::ValueType(type)); Chris@200: Chris@200: QSettings settings; Chris@200: settings.beginGroup("MainWindow"); Chris@200: settings.setValue("labellertype", type); Chris@200: settings.endGroup(); Chris@200: } Chris@200: Chris@200: void Chris@200: MainWindow::setInstantsCounterCycle() Chris@200: { Chris@200: QAction *a = dynamic_cast(sender()); Chris@200: if (!a) return; Chris@200: Chris@200: int cycle = a->text().toInt(); Chris@200: if (cycle == 0) return; Chris@200: Chris@200: if (m_labeller) m_labeller->setCounterCycleSize(cycle); Chris@200: Chris@200: QSettings settings; Chris@200: settings.beginGroup("MainWindow"); Chris@200: settings.setValue("labellercycle", cycle); Chris@200: settings.endGroup(); Chris@200: } Chris@200: Chris@200: void Chris@597: MainWindow::setInstantsCounters() Chris@200: { Chris@200: LabelCounterInputDialog dialog(m_labeller, this); Chris@241: dialog.setWindowTitle(tr("Reset Counters")); Chris@200: dialog.exec(); Chris@0: } Chris@0: Chris@0: void Chris@597: MainWindow::resetInstantsCounters() Chris@597: { Chris@597: if (m_labeller) m_labeller->resetCounters(); Chris@597: } Chris@597: Chris@597: void Chris@1355: MainWindow::subdivideInstants() Chris@1355: { Chris@1355: QSettings settings; Chris@1355: settings.beginGroup("MainWindow"); Chris@1355: int n = settings.value("subdivisions", 4).toInt(); Chris@1355: Chris@1355: bool ok; Chris@1355: Chris@1355: n = QInputDialog::getInt(this, Chris@1355: tr("Subdivide instants"), Chris@1355: tr("Number of subdivisions:"), Chris@1355: n, 2, 96, 1, &ok); Chris@1355: Chris@1355: if (ok) { Chris@1355: settings.setValue("subdivisions", n); Chris@1355: subdivideInstantsBy(n); Chris@1355: } Chris@1355: Chris@1355: settings.endGroup(); Chris@1355: } Chris@1355: Chris@1355: void Chris@1356: MainWindow::winnowInstants() Chris@1356: { Chris@1356: QSettings settings; Chris@1356: settings.beginGroup("MainWindow"); Chris@1356: int n = settings.value("winnow-subdivisions", 4).toInt(); Chris@1356: Chris@1356: bool ok; Chris@1356: Chris@1356: n = QInputDialog::getInt(this, Chris@1356: tr("Winnow instants"), Chris@1356: tr("Remove all instants apart from multiples of:"), Chris@1356: n, 2, 96, 1, &ok); Chris@1356: Chris@1356: if (ok) { Chris@1356: settings.setValue("winnow-subdivisions", n); Chris@1356: winnowInstantsBy(n); Chris@1356: } Chris@1356: Chris@1356: settings.endGroup(); Chris@1356: } Chris@1356: Chris@1356: void Chris@233: MainWindow::modelGenerationFailed(QString transformName, QString message) Chris@233: { Chris@247: emit hideSplash(); Chris@247: Chris@983: QString quoted; Chris@983: if (transformName != "") { Chris@983: quoted = QString("\"%1\" ").arg(transformName); Chris@983: } Chris@983: Chris@233: if (message != "") { Chris@233: Chris@233: QMessageBox::warning Chris@233: (this, Chris@233: tr("Failed to generate layer"), Chris@983: tr("Layer generation failed

Failed to generate derived layer.

The layer transform %1failed:

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

Failed to generate a derived layer.

The layer transform %1failed.

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

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

The layer transform \"%2\" failed:

%3") Chris@233: .arg(layerName).arg(transformName).arg(message), Chris@233: QMessageBox::Ok); Chris@233: } else { Chris@233: QMessageBox::warning Chris@233: (this, Chris@233: tr("Failed to regenerate layer"), Chris@233: 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@233: .arg(layerName).arg(transformName), Chris@233: QMessageBox::Ok); Chris@233: } Chris@233: } Chris@233: Chris@233: void Chris@233: MainWindow::modelRegenerationWarning(QString layerName, Chris@730: QString /* transformName */, Chris@730: QString message) Chris@233: { Chris@247: emit hideSplash(); Chris@247: Chris@233: QMessageBox::warning Chris@233: (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@233: } Chris@233: Chris@233: void Chris@1151: MainWindow::alignmentFailed(QString message) Chris@0: { Chris@0: QMessageBox::warning Chris@0: (this, Chris@233: tr("Failed to calculate alignment"), Chris@1151: tr("Alignment calculation failed

Failed to calculate an audio alignment:

%1") Chris@1151: .arg(message), Chris@165: QMessageBox::Ok); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position) Chris@0: { Chris@438: // SVDEBUG << "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@0: MainWindow::showLayerTree() Chris@0: { Chris@219: if (!m_layerTreeDialog.isNull()) { Chris@219: m_layerTreeDialog->show(); Chris@219: m_layerTreeDialog->raise(); Chris@177: return; Chris@177: } Chris@177: Chris@762: m_layerTreeDialog = new LayerTreeDialog(m_paneStack, this); Chris@232: m_layerTreeDialog->setAttribute(Qt::WA_DeleteOnClose); // see below Chris@219: m_layerTreeDialog->show(); Chris@0: } Chris@0: Chris@0: void Chris@306: MainWindow::showActivityLog() Chris@306: { Chris@306: m_activityLog->show(); Chris@306: m_activityLog->raise(); Chris@306: m_activityLog->scrollToEnd(); Chris@306: } Chris@306: Chris@306: void Chris@891: MainWindow::showUnitConverter() Chris@891: { Chris@891: m_unitConverter->show(); Chris@891: m_unitConverter->raise(); Chris@891: } Chris@891: Chris@891: void Chris@0: MainWindow::preferences() Chris@0: { Chris@436: bool goToTemplateTab = Chris@436: (sender() && sender()->objectName() == "set_default_template"); Chris@436: Chris@0: if (!m_preferencesDialog.isNull()) { Chris@0: m_preferencesDialog->show(); Chris@0: m_preferencesDialog->raise(); Chris@436: if (goToTemplateTab) { Chris@436: m_preferencesDialog->switchToTab(PreferencesDialog::TemplateTab); Chris@436: } Chris@0: return; Chris@0: } Chris@0: Chris@0: m_preferencesDialog = new PreferencesDialog(this); Chris@0: Chris@1413: connect(m_preferencesDialog, SIGNAL(audioDeviceChanged()), Chris@1413: this, SLOT(recreateAudioIO())); Chris@1448: connect(m_preferencesDialog, SIGNAL(coloursChanged()), Chris@1448: this, SLOT(coloursChanged())); Chris@1413: Chris@0: // DeleteOnClose is safe here, because m_preferencesDialog is a Chris@0: // QPointer that will be zeroed when the dialog is deleted. We Chris@0: // use it in preference to leaving the dialog lying around because Chris@0: // if you Cancel the dialog, it resets the preferences state Chris@0: // without resetting its own widgets, so its state will be Chris@0: // incorrect when next shown unless we construct it afresh Chris@0: m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose); Chris@0: Chris@0: m_preferencesDialog->show(); Chris@436: if (goToTemplateTab) { Chris@436: m_preferencesDialog->switchToTab(PreferencesDialog::TemplateTab); Chris@436: } Chris@0: } Chris@0: Chris@0: void Chris@90: MainWindow::mouseEnteredWidget() Chris@90: { Chris@90: QWidget *w = dynamic_cast(sender()); Chris@90: if (!w) return; Chris@90: Chris@2079: QString mainText, editText; Chris@2079: Chris@1386: if (w == m_mainLevelPan) { Chris@2079: mainText = tr("Adjust the master playback level and pan"); Chris@2079: editText = tr("click then drag to adjust, ctrl+click to reset"); Chris@90: } else if (w == m_playSpeed) { Chris@2079: mainText = tr("Adjust the master playback speed"); Chris@2079: editText = tr("drag up/down to adjust, ctrl+click to reset"); Chris@2079: } Chris@2079: Chris@2079: if (mainText != "") { Chris@2079: contextHelpChanged(tr("%1: %2").arg(mainText).arg(editText)); Chris@90: } Chris@90: } Chris@90: Chris@90: void Chris@90: MainWindow::mouseLeftWidget() Chris@90: { Chris@116: contextHelpChanged(""); Chris@116: } Chris@116: Chris@116: void Chris@0: MainWindow::website() Chris@0: { Chris@0: openHelpUrl(tr("http://www.sonicvisualiser.org/")); Chris@0: } Chris@0: Chris@0: void Chris@0: MainWindow::help() Chris@0: { Chris@318: openHelpUrl(tr("http://www.sonicvisualiser.org/doc/reference/%1/en/").arg(SV_VERSION)); Chris@0: } Chris@0: Chris@0: void Chris@1516: MainWindow::whatsNew() Chris@1516: { Chris@1516: QFile changelog(":CHANGELOG"); Chris@1516: changelog.open(QFile::ReadOnly); Chris@1516: QByteArray content = changelog.readAll(); Chris@1516: QString text = QString::fromUtf8(content); Chris@1516: Chris@1516: QDialog *d = new QDialog(this); Chris@1516: d->setWindowTitle(tr("What's New")); Chris@1516: Chris@1516: QGridLayout *layout = new QGridLayout; Chris@1516: d->setLayout(layout); Chris@1516: Chris@1516: int row = 0; Chris@1516: Chris@1516: QLabel *iconLabel = new QLabel; Chris@1516: iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64)); Chris@1516: layout->addWidget(iconLabel, row, 0); Chris@1516: Chris@1516: layout->addWidget Chris@1516: (new QLabel(tr("

What's New in %1

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

    \\1

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

    About Sonic Visualiser

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

    Sonic Visualiser is a program for viewing and exploring audio data for semantic music analysis and annotation.
    http://www.sonicvisualiser.org/

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

    %1

    ").arg(getReleaseText()); Chris@0: Chris@1516: if (m_oscQueue && m_oscQueue->isOK()) { Chris@1516: aboutText += tr("

    The OSC URL for this instance is: \"%1\"").arg(m_oscQueue->getOSCURL()); Chris@1516: } Chris@1516: Chris@1516: aboutText += "

    "; Chris@285: Chris@1155: aboutText += tr("With Qt v%1 © The Qt Company").arg(QT_VERSION_STR); Chris@285: Chris@1516: aboutText += ""; Chris@1516: Chris@0: #ifdef HAVE_JACK Chris@93: #ifdef JACK_VERSION Chris@285: aboutText += tr("
    With JACK audio output library v%1 © Paul Davis and Jack O'Quin").arg(JACK_VERSION); Chris@285: #else // !JACK_VERSION Chris@257: aboutText += tr("
    With JACK audio output library © Paul Davis and Jack O'Quin"); Chris@285: #endif // JACK_VERSION Chris@285: #endif // HAVE_JACK Chris@0: #ifdef HAVE_PORTAUDIO Chris@257: aboutText += tr("
    With PortAudio audio output library © Ross Bencina and Phil Burk"); Chris@285: #endif // HAVE_PORTAUDIO Chris@257: #ifdef HAVE_LIBPULSE Chris@285: #ifdef LIBPULSE_VERSION Chris@285: aboutText += tr("
    With PulseAudio audio output library v%1 © Lennart Poettering and Pierre Ossman").arg(LIBPULSE_VERSION); Chris@285: #else // !LIBPULSE_VERSION Chris@257: aboutText += tr("
    With PulseAudio audio output library © Lennart Poettering and Pierre Ossman"); Chris@285: #endif // LIBPULSE_VERSION Chris@285: #endif // HAVE_LIBPULSE Chris@0: #ifdef HAVE_OGGZ Chris@93: #ifdef OGGZ_VERSION Chris@0: aboutText += tr("
    With Ogg file decoder (oggz v%1, fishsound v%2) © CSIRO Australia").arg(OGGZ_VERSION).arg(FISHSOUND_VERSION); Chris@285: #else // !OGGZ_VERSION Chris@93: aboutText += tr("
    With Ogg file decoder © CSIRO Australia"); Chris@285: #endif // OGGZ_VERSION Chris@285: #endif // HAVE_OGGZ Chris@0: #ifdef HAVE_MAD Chris@93: #ifdef MAD_VERSION Chris@285: aboutText += tr("
    With MAD mp3 decoder v%1 © Underbit Technologies Inc").arg(MAD_VERSION); Chris@285: #else // !MAD_VERSION Chris@93: aboutText += tr("
    With MAD mp3 decoder © Underbit Technologies Inc"); Chris@285: #endif // MAD_VERSION Chris@285: #endif // HAVE_MAD Chris@0: #ifdef HAVE_SAMPLERATE Chris@93: #ifdef SAMPLERATE_VERSION Chris@285: aboutText += tr("
    With libsamplerate v%1 © Erik de Castro Lopo").arg(SAMPLERATE_VERSION); Chris@285: #else // !SAMPLERATE_VERSION Chris@93: aboutText += tr("
    With libsamplerate © Erik de Castro Lopo"); Chris@285: #endif // SAMPLERATE_VERSION Chris@285: #endif // HAVE_SAMPLERATE Chris@0: #ifdef HAVE_SNDFILE Chris@93: #ifdef SNDFILE_VERSION Chris@285: aboutText += tr("
    With libsndfile v%1 © Erik de Castro Lopo").arg(SNDFILE_VERSION); Chris@285: #else // !SNDFILE_VERSION Chris@93: aboutText += tr("
    With libsndfile © Erik de Castro Lopo"); Chris@285: #endif // SNDFILE_VERSION Chris@285: #endif // HAVE_SNDFILE Chris@127: #ifdef HAVE_FFTW3F Chris@93: #ifdef FFTW3_VERSION Chris@285: aboutText += tr("
    With FFTW3 v%1 © Matteo Frigo and MIT").arg(FFTW3_VERSION); Chris@285: #else // !FFTW3_VERSION Chris@93: aboutText += tr("
    With FFTW3 © Matteo Frigo and MIT"); Chris@285: #endif // FFTW3_VERSION Chris@285: #endif // HAVE_FFTW3F Chris@267: #ifdef HAVE_RUBBERBAND Chris@267: #ifdef RUBBERBAND_VERSION Chris@1315: aboutText += tr("
    With Rubber Band Library v%1 © Particular Programs Ltd").arg(RUBBERBAND_VERSION); Chris@285: #else // !RUBBERBAND_VERSION Chris@1315: aboutText += tr("
    With Rubber Band Library © Particular Programs Ltd"); Chris@285: #endif // RUBBERBAND_VERSION Chris@285: #endif // HAVE_RUBBERBAND Chris@1315: aboutText += tr("
    With Vamp plugin support (API v%1, host SDK v%2) © Chris Cannam and QMUL").arg(VAMP_API_VERSION).arg(VAMP_SDK_VERSION); Chris@1636: aboutText += tr("
    With Piper Vamp protocol bridge © QMUL"); Chris@0: aboutText += tr("
    With LADSPA plugin support (API v%1) © Richard Furse, Paul Davis, Stefan Westerfeld").arg(LADSPA_VERSION); Chris@0: aboutText += tr("
    With DSSI plugin support (API v%1) © Chris Cannam, Steve Harris, Sean Bolton").arg(DSSI_VERSION); Chris@285: #ifdef REDLAND_VERSION Chris@285: aboutText += tr("
    With Redland RDF datastore v%1 © Dave Beckett and the University of Bristol").arg(REDLAND_VERSION); Chris@285: #else // !REDLAND_VERSION Chris@285: aboutText += tr("
    With Redland RDF datastore © Dave Beckett and the University of Bristol"); Chris@285: #endif // REDLAND_VERSION Chris@523: aboutText += tr("
    With Serd and Sord RDF parser and store © David Robillard"); Chris@1315: aboutText += tr("
    With Dataquay Qt/RDF library © Particular Programs Ltd"); Chris@1315: aboutText += tr("
    With Cap'n Proto serialisation © Sandstorm Development Group"); Chris@300: aboutText += tr("
    With RtMidi © Gary P. Scavone"); Chris@300: Chris@69: #ifdef HAVE_LIBLO Chris@93: #ifdef LIBLO_VERSION Chris@285: aboutText += tr("
    With liblo Lite OSC library v%1 © Steve Harris").arg(LIBLO_VERSION); Chris@285: #else // !LIBLO_VERSION Chris@327: aboutText += tr("
    With liblo Lite OSC library © Steve Harris"); Chris@285: #endif // LIBLO_VERSION Chris@285: Chris@285: aboutText += "

    "; Chris@285: #endif // HAVE_LIBLO Chris@285: Chris@1667: aboutText += "

    "; Chris@1667: aboutText += tr("Russian UI translation contributed by Alexandre Prokoudine."); Chris@1667: aboutText += "
    "; Chris@1667: aboutText += tr("Czech UI translation contributed by Pavel Fric."); Chris@1667: aboutText += "

    "; Chris@1667: Chris@0: aboutText += Chris@2178: "

    Sonic Visualiser Copyright © 2005–2019 Chris Cannam and " Chris@1667: "Queen Mary, University of London.

    "; Chris@1667: Chris@1667: aboutText += Chris@285: "

    This program is free software; you can redistribute it and/or " Chris@231: "modify it under the terms of the GNU General Public License as " Chris@231: "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@285: "COPYING included with this distribution for more information.

    "; Chris@1516: Chris@1516: // use our own dialog so we can influence the size Chris@1516: Chris@1516: QDialog *d = new QDialog(this); Chris@1516: Chris@1516: d->setWindowTitle(tr("About %1").arg(QApplication::applicationName())); Chris@1516: Chris@1516: QGridLayout *layout = new QGridLayout; Chris@1516: d->setLayout(layout); Chris@1516: Chris@1516: int row = 0; Chris@0: Chris@1516: QLabel *iconLabel = new QLabel; Chris@1516: iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64)); Chris@1516: layout->addWidget(iconLabel, row, 0, Qt::AlignTop); Chris@1516: Chris@1516: QLabel *mainText = new QLabel(); Chris@1516: layout->addWidget(mainText, row, 1, 1, 2); Chris@1516: Chris@1516: layout->setRowStretch(row, 10); Chris@1516: layout->setColumnStretch(1, 10); Chris@1516: Chris@1516: ++row; Chris@1516: Chris@1516: QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); Chris@1516: layout->addWidget(bb, row++, 0, 1, 3); Chris@1516: connect(bb, SIGNAL(accepted()), d, SLOT(accept())); Chris@1516: Chris@1516: // mainText->setHtml(aboutText); Chris@1516: // mainText->setReadOnly(true); Chris@1516: mainText->setWordWrap(true); Chris@1516: mainText->setOpenExternalLinks(true); Chris@1516: mainText->setText(aboutText); Chris@1516: Chris@1516: d->setMinimumSize(m_viewManager->scalePixelSize(420), Chris@1516: m_viewManager->scalePixelSize(200)); Chris@1516: Chris@1516: d->exec(); Chris@1516: Chris@1516: delete d; Chris@1516: /* Chris@1516: QMessageBox about(QMessageBox::Information, Chris@1516: tr("About Sonic Visualiser"), Chris@1516: aboutText, Chris@1516: QMessageBox::StandardButtons(QMessageBox::Ok), Chris@1516: this); Chris@1516: Chris@1516: QIcon icon = QApplication::windowIcon(); Chris@1516: QSize size = icon.actualSize(QSize(64, 64)); Chris@1516: about.setIconPixmap(icon.pixmap(size)); Chris@1516: Chris@1516: about.setMinimumSize(m_viewManager->scalePixelSize(400), Chris@1516: m_viewManager->scalePixelSize(400)); Chris@1516: Chris@1516: about.exec(); Chris@1516: */ Chris@0: } Chris@0: Chris@162: void Chris@162: MainWindow::keyReference() Chris@162: { Chris@162: m_keyReference->show(); Chris@162: } Chris@162: Chris@333: void Chris@333: MainWindow::newerVersionAvailable(QString version) Chris@333: { Chris@1516: m_newerVersionIs = version; Chris@1516: Chris@334: QSettings settings; Chris@334: settings.beginGroup("NewerVersionWarning"); Chris@334: QString tag = QString("version-%1-available-show").arg(version); Chris@334: if (settings.value(tag, true).toBool()) { Chris@334: QString title(tr("Newer version available")); Chris@663: QString text(tr("

    Newer version available

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

    Please see the Sonic Visualiser website for more information.

    ").arg(SV_VERSION).arg(version)); Chris@334: QMessageBox::information(this, title, text); Chris@334: settings.setValue(tag, false); Chris@334: } Chris@334: settings.endGroup(); Chris@333: } Chris@333: Chris@333: