annotate main/MainWindow.cpp @ 2596:04d381f0d89a tip

Default branch is now named default on git as well as hg, in case we ever want to switch to mirroring in the other direction
author Chris Cannam
date Thu, 27 Aug 2020 15:58:56 +0100
parents bc882961a233
children
rev   line source
Chris@0 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@0 4 Sonic Visualiser
Chris@0 5 An audio file viewer and annotation editor.
Chris@0 6 Centre for Digital Music, Queen Mary, University of London.
Chris@200 7 This file copyright 2006-2007 Chris Cannam and QMUL.
Chris@0 8
Chris@0 9 This program is free software; you can redistribute it and/or
Chris@0 10 modify it under the terms of the GNU General Public License as
Chris@0 11 published by the Free Software Foundation; either version 2 of the
Chris@0 12 License, or (at your option) any later version. See the file
Chris@0 13 COPYING included with this distribution for more information.
Chris@0 14 */
Chris@0 15
Chris@144 16 #include "../version.h"
Chris@0 17
Chris@0 18 #include "MainWindow.h"
Chris@0 19 #include "PreferencesDialog.h"
Chris@0 20
Chris@1 21 #include "view/Pane.h"
Chris@1 22 #include "view/PaneStack.h"
Chris@1 23 #include "data/model/WaveFileModel.h"
Chris@1 24 #include "data/model/SparseOneDimensionalModel.h"
Chris@415 25 #include "data/model/RangeSummarisableTimeValueModel.h"
Chris@185 26 #include "data/model/NoteModel.h"
Chris@895 27 #include "data/model/AggregateWaveModel.h"
Chris@189 28 #include "data/model/Labeller.h"
Chris@222 29 #include "data/osc/OSCQueue.h"
Chris@216 30 #include "framework/Document.h"
Chris@357 31 #include "framework/TransformUserConfigurator.h"
Chris@1 32 #include "view/ViewManager.h"
Chris@0 33 #include "base/Preferences.h"
Chris@423 34 #include "base/ResourceFinder.h"
Chris@1995 35 #include "base/RecordDirectory.h"
Chris@0 36 #include "layer/WaveformLayer.h"
Chris@0 37 #include "layer/TimeRulerLayer.h"
Chris@0 38 #include "layer/TimeInstantLayer.h"
Chris@0 39 #include "layer/TimeValueLayer.h"
Chris@307 40 #include "layer/NoteLayer.h"
Chris@0 41 #include "layer/Colour3DPlotLayer.h"
Chris@95 42 #include "layer/SliceLayer.h"
Chris@95 43 #include "layer/SliceableLayer.h"
Chris@193 44 #include "layer/ImageLayer.h"
Chris@340 45 #include "layer/RegionLayer.h"
Chris@65 46 #include "view/Overview.h"
Chris@0 47 #include "widgets/PropertyBox.h"
Chris@0 48 #include "widgets/PropertyStack.h"
Chris@0 49 #include "widgets/AudioDial.h"
Chris@1386 50 #include "widgets/LevelPanWidget.h"
Chris@1431 51 #include "widgets/LevelPanToolButton.h"
Chris@168 52 #include "widgets/IconLoader.h"
Chris@219 53 #include "widgets/LayerTreeDialog.h"
Chris@0 54 #include "widgets/ListInputDialog.h"
Chris@36 55 #include "widgets/SubdividingMenu.h"
Chris@90 56 #include "widgets/NotifyingPushButton.h"
Chris@162 57 #include "widgets/KeyReference.h"
Chris@273 58 #include "widgets/TransformFinder.h"
Chris@192 59 #include "widgets/LabelCounterInputDialog.h"
Chris@302 60 #include "widgets/ActivityLog.h"
Chris@891 61 #include "widgets/UnitConverter.h"
Chris@1779 62 #include "widgets/ProgressDialog.h"
Chris@1989 63 #include "widgets/CSVAudioFormatDialog.h"
Chris@1035 64 #include "audio/AudioCallbackPlaySource.h"
Chris@1476 65 #include "audio/AudioCallbackRecordTarget.h"
Chris@1035 66 #include "audio/PlaySpeedRangeMapper.h"
Chris@1 67 #include "data/fileio/DataFileReaderFactory.h"
Chris@180 68 #include "data/fileio/PlaylistFileReader.h"
Chris@1 69 #include "data/fileio/WavFileWriter.h"
Chris@1 70 #include "data/fileio/CSVFileWriter.h"
Chris@185 71 #include "data/fileio/MIDIFileWriter.h"
Chris@1 72 #include "data/fileio/BZipFileDevice.h"
Chris@198 73 #include "data/fileio/FileSource.h"
Chris@304 74 #include "data/midi/MIDIInput.h"
Chris@1 75 #include "base/RecentFiles.h"
Chris@1145 76 #include "plugin/PluginScan.h"
Chris@249 77 #include "transform/TransformFactory.h"
Chris@249 78 #include "transform/ModelTransformerFactory.h"
Chris@0 79 #include "base/XmlExportable.h"
Chris@248 80 #include "widgets/CommandHistory.h"
Chris@0 81 #include "base/Profiler.h"
Chris@0 82 #include "base/Clipboard.h"
Chris@165 83 #include "base/UnitDatabase.h"
Chris@248 84 #include "layer/ColourDatabase.h"
Chris@265 85 #include "widgets/ModelDataTableDialog.h"
Chris@2449 86 #include "widgets/CSVExportDialog.h"
Chris@2479 87 #include "widgets/MenuTitle.h"
Chris@289 88 #include "rdf/PluginRDFIndexer.h"
Chris@276 89
Chris@662 90 #include "Surveyer.h"
Chris@663 91 #include "NetworkPermissionTester.h"
Chris@334 92 #include "framework/VersionTester.h"
Chris@333 93
Chris@0 94 // For version information
Chris@280 95 #include <vamp/vamp.h>
Chris@280 96 #include <vamp-hostsdk/PluginBase.h>
Chris@0 97 #include "plugin/api/ladspa.h"
Chris@0 98 #include "plugin/api/dssi.h"
Chris@0 99
Chris@1035 100 #include <bqaudioio/SystemPlaybackTarget.h>
Chris@1055 101 #include <bqaudioio/SystemAudioIO.h>
Chris@1035 102
Chris@0 103 #include <QApplication>
Chris@0 104 #include <QMessageBox>
Chris@0 105 #include <QGridLayout>
Chris@0 106 #include <QLabel>
Chris@0 107 #include <QAction>
Chris@0 108 #include <QMenuBar>
Chris@0 109 #include <QToolBar>
Chris@0 110 #include <QInputDialog>
Chris@0 111 #include <QStatusBar>
Chris@0 112 #include <QTreeView>
Chris@0 113 #include <QFile>
Chris@88 114 #include <QFileInfo>
Chris@88 115 #include <QDir>
Chris@0 116 #include <QTextStream>
Chris@911 117 #include <QTextCodec>
Chris@0 118 #include <QProcess>
Chris@7 119 #include <QShortcut>
Chris@5 120 #include <QSettings>
Chris@11 121 #include <QDateTime>
Chris@11 122 #include <QProcess>
Chris@16 123 #include <QCheckBox>
Chris@55 124 #include <QRegExp>
Chris@158 125 #include <QScrollArea>
Chris@1432 126 #include <QCloseEvent>
Chris@483 127 #include <QDialogButtonBox>
Chris@426 128 #include <QFileSystemWatcher>
Chris@1516 129 #include <QTextEdit>
Chris@2475 130 #include <QWidgetAction>
Chris@0 131
Chris@0 132 #include <iostream>
Chris@0 133 #include <cstdio>
Chris@0 134 #include <errno.h>
Chris@0 135
Chris@33 136 using std::vector;
Chris@33 137 using std::map;
Chris@33 138 using std::set;
Chris@33 139
Chris@0 140
Chris@2375 141 MainWindow::MainWindow(AudioMode audioMode, MIDIMode midiMode, bool withOSCSupport) :
Chris@2375 142 MainWindowBase(audioMode, midiMode, int(PaneStack::Option::Default)),
Chris@2126 143 m_overview(nullptr),
Chris@0 144 m_mainMenusCreated(false),
Chris@2126 145 m_paneMenu(nullptr),
Chris@2126 146 m_layerMenu(nullptr),
Chris@2126 147 m_transformsMenu(nullptr),
Chris@2126 148 m_playbackMenu(nullptr),
Chris@2126 149 m_existingLayersMenu(nullptr),
Chris@2126 150 m_sliceMenu(nullptr),
Chris@2126 151 m_recentFilesMenu(nullptr),
Chris@2126 152 m_recentTransformsMenu(nullptr),
Chris@2126 153 m_templatesMenu(nullptr),
Chris@2126 154 m_rightButtonMenu(nullptr),
Chris@2126 155 m_rightButtonLayerMenu(nullptr),
Chris@2126 156 m_rightButtonTransformsMenu(nullptr),
Chris@2126 157 m_rightButtonPlaybackMenu(nullptr),
Chris@2475 158 m_lastRightButtonPropertyMenu(nullptr),
Chris@2126 159 m_soloAction(nullptr),
Chris@2126 160 m_rwdStartAction(nullptr),
Chris@2126 161 m_rwdSimilarAction(nullptr),
Chris@2126 162 m_rwdAction(nullptr),
Chris@2126 163 m_ffwdAction(nullptr),
Chris@2126 164 m_ffwdSimilarAction(nullptr),
Chris@2126 165 m_ffwdEndAction(nullptr),
Chris@2126 166 m_playAction(nullptr),
Chris@2126 167 m_recordAction(nullptr),
Chris@2126 168 m_playSelectionAction(nullptr),
Chris@2126 169 m_playLoopAction(nullptr),
Chris@705 170 m_soloModified(false),
Chris@705 171 m_prevSolo(false),
Chris@2126 172 m_playControlsSpacer(nullptr),
Chris@239 173 m_playControlsWidth(0),
Chris@2126 174 m_preferencesDialog(nullptr),
Chris@2126 175 m_layerTreeDialog(nullptr),
Chris@302 176 m_activityLog(new ActivityLog()),
Chris@891 177 m_unitConverter(new UnitConverter()),
Chris@426 178 m_keyReference(new KeyReference()),
Chris@2550 179 m_templateWatcher(nullptr),
Chris@2550 180 m_transformPopulater(nullptr)
Chris@0 181 {
Chris@253 182 Profiler profiler("MainWindow::MainWindow");
Chris@253 183
Chris@1610 184 SVDEBUG << "MainWindow: " << getReleaseText() << endl;
Chris@1610 185
Chris@518 186 setWindowTitle(QApplication::applicationName());
Chris@0 187
Chris@165 188 UnitDatabase *udb = UnitDatabase::getInstance();
Chris@165 189 udb->registerUnit("Hz");
Chris@165 190 udb->registerUnit("dB");
Chris@165 191 udb->registerUnit("s");
Chris@165 192
Chris@165 193 ColourDatabase *cdb = ColourDatabase::getInstance();
Chris@165 194 cdb->addColour(Qt::black, tr("Black"));
Chris@165 195 cdb->addColour(Qt::darkRed, tr("Red"));
Chris@165 196 cdb->addColour(Qt::darkBlue, tr("Blue"));
Chris@165 197 cdb->addColour(Qt::darkGreen, tr("Green"));
Chris@165 198 cdb->addColour(QColor(200, 50, 255), tr("Purple"));
Chris@165 199 cdb->addColour(QColor(255, 150, 50), tr("Orange"));
Chris@166 200 cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true);
Chris@166 201 cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true);
Chris@166 202 cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true);
Chris@2466 203 cdb->setUseDarkBackground(cdb->addColour(QColor(20, 255, 90), tr("Bright Green")), true);
Chris@166 204 cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true);
Chris@166 205 cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true);
Chris@0 206
Chris@1639 207 SVDEBUG << "MainWindow: Creating main user interface layout" << endl;
Chris@1639 208
Chris@0 209 QFrame *frame = new QFrame;
Chris@0 210 setCentralWidget(frame);
Chris@0 211
Chris@0 212 QGridLayout *layout = new QGridLayout;
Chris@180 213
Chris@519 214 m_descriptionLabel = new QLabel;
Chris@0 215
Chris@489 216 m_mainScroll = new QScrollArea(frame);
Chris@489 217 m_mainScroll->setWidgetResizable(true);
Chris@489 218 m_mainScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
Chris@489 219 m_mainScroll->setFrameShape(QFrame::NoFrame);
Chris@489 220
Chris@489 221 m_mainScroll->setWidget(m_paneStack);
Chris@159 222
Chris@65 223 m_overview = new Overview(frame);
Chris@65 224 m_overview->setViewManager(m_viewManager);
Chris@1431 225 int overviewHeight = m_viewManager->scalePixelSize(35);
Chris@1431 226 if (overviewHeight < 40) overviewHeight = 40;
Chris@1431 227 m_overview->setFixedHeight(overviewHeight);
Chris@144 228 #ifndef _WIN32
Chris@144 229 // For some reason, the contents of the overview never appear if we
Chris@144 230 // make this setting on Windows. I have no inclination at the moment
Chris@144 231 // to track down the reason why.
Chris@129 232 m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
Chris@144 233 #endif
Chris@90 234 connect(m_overview, SIGNAL(contextHelpChanged(const QString &)),
Chris@116 235 this, SLOT(contextHelpChanged(const QString &)));
Chris@0 236
Chris@0 237 m_panLayer = new WaveformLayer;
Chris@0 238 m_panLayer->setChannelMode(WaveformLayer::MergeChannels);
Chris@0 239 m_panLayer->setAggressiveCacheing(true);
Chris@65 240 m_overview->addLayer(m_panLayer);
Chris@174 241
Chris@1448 242 coloursChanged(); // sets pan layer colour from preferences
Chris@0 243
Chris@0 244 m_playSpeed = new AudioDial(frame);
Chris@12 245 m_playSpeed->setMinimum(0);
Chris@1031 246 m_playSpeed->setMaximum(120);
Chris@1031 247 m_playSpeed->setValue(60);
Chris@1431 248 m_playSpeed->setFixedWidth(overviewHeight);
Chris@1431 249 m_playSpeed->setFixedHeight(overviewHeight);
Chris@0 250 m_playSpeed->setNotchesVisible(true);
Chris@25 251 m_playSpeed->setPageStep(10);
Chris@1031 252 m_playSpeed->setObjectName(tr("Playback Speed"));
Chris@1031 253 m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper);
Chris@1031 254 m_playSpeed->setDefaultValue(60);
Chris@60 255 m_playSpeed->setShowToolTip(true);
Chris@0 256 connect(m_playSpeed, SIGNAL(valueChanged(int)),
Chris@1770 257 this, SLOT(playSpeedChanged(int)));
Chris@90 258 connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
Chris@90 259 connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
Chris@90 260
Chris@1431 261 m_mainLevelPan = new LevelPanToolButton(frame);
Chris@1386 262 connect(m_mainLevelPan, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
Chris@1386 263 connect(m_mainLevelPan, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
Chris@1431 264 m_mainLevelPan->setFixedHeight(overviewHeight);
Chris@1431 265 m_mainLevelPan->setFixedWidth(overviewHeight);
Chris@1431 266 m_mainLevelPan->setImageSize((overviewHeight * 3) / 4);
Chris@1431 267 m_mainLevelPan->setBigImageSize(overviewHeight * 3);
Chris@168 268
Chris@239 269 m_playControlsSpacer = new QFrame;
Chris@239 270
Chris@1383 271 layout->setSpacing(m_viewManager->scalePixelSize(4));
Chris@1383 272 layout->addWidget(m_mainScroll, 0, 0, 1, 4);
Chris@1383 273 layout->addWidget(m_overview, 1, 0);
Chris@1431 274 layout->addWidget(m_playSpeed, 1, 1);
Chris@1431 275 layout->addWidget(m_playControlsSpacer, 1, 2);
Chris@1386 276 layout->addWidget(m_mainLevelPan, 1, 3);
Chris@239 277
Chris@239 278 m_playControlsWidth =
Chris@1386 279 m_mainLevelPan->width() + m_playSpeed->width() + layout->spacing() * 2;
Chris@239 280
Chris@239 281 m_paneStack->setPropertyStackMinWidth(m_playControlsWidth
Chris@239 282 + 2 + layout->spacing());
Chris@239 283 m_playControlsSpacer->setFixedSize(QSize(2, 2));
Chris@239 284
Chris@1383 285 layout->setColumnStretch(0, 10);
Chris@239 286
Chris@239 287 connect(m_paneStack, SIGNAL(propertyStacksResized(int)),
Chris@239 288 this, SLOT(propertyStacksResized(int)));
Chris@16 289
Chris@0 290 frame->setLayout(layout);
Chris@0 291
Chris@1639 292 SVDEBUG << "MainWindow: Creating menus and toolbars" << endl;
Chris@1639 293
cannam@1461 294 #ifdef Q_OS_MAC
cannam@1461 295 // Mac doesn't align menu labels when icons are shown: result is messy
cannam@1461 296 QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
cannam@1461 297 setIconsVisibleInMenus(false);
cannam@1461 298 #endif
cannam@1461 299
Chris@0 300 setupMenus();
Chris@0 301 setupToolbars();
Chris@155 302 setupHelpMenu();
Chris@0 303
Chris@90 304 statusBar();
Chris@340 305 m_currentLabel = new QLabel;
Chris@340 306 statusBar()->addPermanentWidget(m_currentLabel);
Chris@0 307
Chris@728 308 finaliseMenus();
Chris@728 309
Chris@302 310 connect(m_viewManager, SIGNAL(activity(QString)),
Chris@302 311 m_activityLog, SLOT(activityHappened(QString)));
Chris@302 312 connect(m_playSource, SIGNAL(activity(QString)),
Chris@302 313 m_activityLog, SLOT(activityHappened(QString)));
Chris@302 314 connect(CommandHistory::getInstance(), SIGNAL(activity(QString)),
Chris@302 315 m_activityLog, SLOT(activityHappened(QString)));
Chris@310 316 connect(this, SIGNAL(activity(QString)),
Chris@310 317 m_activityLog, SLOT(activityHappened(QString)));
Chris@303 318 connect(this, SIGNAL(replacedDocument()), this, SLOT(documentReplaced()));
Chris@1087 319
Chris@306 320 m_activityLog->hide();
Chris@303 321
Chris@891 322 m_unitConverter->hide();
Chris@1056 323
Chris@1056 324 setAudioRecordMode(RecordCreateAdditionalModel);
Chris@1639 325
Chris@1639 326 SVDEBUG << "MainWindow: Creating new session" << endl;
Chris@1639 327
Chris@303 328 newSession();
Chris@303 329
Chris@304 330 connect(m_midiInput, SIGNAL(eventsAvailable()),
Chris@304 331 this, SLOT(midiEventsAvailable()));
Chris@663 332
Chris@1639 333 SVDEBUG << "MainWindow: Creating network permission tester" << endl;
Chris@1639 334
Chris@1613 335 NetworkPermissionTester tester(withOSCSupport);
Chris@663 336 bool networkPermission = tester.havePermission();
Chris@663 337 if (networkPermission) {
Chris@2550 338 SVDEBUG << "MainWindow: Starting uninstalled-transform population thread" << endl;
Chris@663 339 TransformFactory::getInstance()->startPopulationThread();
Chris@1639 340
Chris@2535 341 m_surveyer = nullptr;
Chris@2535 342
Chris@2536 343 #ifdef WITH_FEEDBACK_REQUEST
Chris@2541 344 SVDEBUG << "MainWindow: Feedback request enabled" << endl;
Chris@2541 345 if (QDate::currentDate().year() > 2020) {
Chris@2541 346 SVDEBUG << "MainWindow: Feedback request expired, not running it" << endl;
Chris@2541 347 } else {
Chris@2541 348 SVDEBUG << "MainWindow: Creating surveyer" << endl;
Chris@2541 349 Surveyer::Config config;
Chris@2541 350 config.hostname = "sonicvisualiser.org";
Chris@2541 351 config.testPath = "feedback41-present.txt";
Chris@2541 352 config.surveyPath = "feedback41.php";
Chris@2541 353 config.countdownKey = "countdown41";
Chris@2541 354 config.countdownFrom = 4;
Chris@2541 355 config.title = "Sonic Visualiser - Can you help?";
Chris@2541 356 config.text = "<h3>Sonic Visualiser: Can you help?</h3><p>"
Chris@2541 357 "<p>Are you using Sonic Visualiser in academic research or for commercial purposes? Or do you intend to do so?</p>"
Chris@2541 358 "<p>If so, would you be interested in telling us something about your work? We are gathering case studies to gauge our impact and inform our future actions.</p><p>Anything you tell us will be used solely to guide research and development at the Centre for Digital Music, Queen Mary University of London.</p>";
Chris@2541 359 config.acceptLabel = tr("Yes, I'd be happy to");
Chris@2541 360 config.rejectLabel = tr("No, thank you");
Chris@2541 361 config.includeSystemInfo = false;
Chris@2541 362 m_surveyer = new Surveyer(config);
Chris@2541 363 }
Chris@2535 364 #endif
Chris@1639 365
Chris@1639 366 SVDEBUG << "MainWindow: Creating version tester" << endl;
Chris@663 367 m_versionTester = new VersionTester
Chris@663 368 ("sonicvisualiser.org", "latest-version.txt", SV_VERSION);
Chris@663 369 connect(m_versionTester, SIGNAL(newerVersionAvailable(QString)),
Chris@663 370 this, SLOT(newerVersionAvailable(QString)));
Chris@663 371 } else {
Chris@2126 372 m_surveyer = nullptr;
Chris@2126 373 m_versionTester = nullptr;
Chris@663 374 }
Chris@1087 375
Chris@2240 376 if (withOSCSupport && networkPermission) {
Chris@2240 377 SVDEBUG << "MainWindow: Creating OSC queue with network port"
Chris@2240 378 << endl;
Chris@2240 379 startOSCQueue(true);
Chris@2240 380 } else {
Chris@2240 381 SVDEBUG << "MainWindow: Creating internal-only OSC queue without port"
Chris@2240 382 << endl;
Chris@2240 383 startOSCQueue(false);
Chris@2240 384 }
Chris@2240 385
Chris@2552 386 // QTimer::singleShot(500, this, SLOT(betaReleaseWarning()));
Chris@1521 387
Chris@1639 388 SVDEBUG << "MainWindow: Constructor done" << endl;
Chris@0 389 }
Chris@0 390
Chris@0 391 MainWindow::~MainWindow()
Chris@0 392 {
Chris@438 393 // SVDEBUG << "MainWindow::~MainWindow" << endl;
Chris@2550 394
Chris@2550 395 if (m_transformPopulater) {
Chris@2550 396 m_transformPopulater->wait();
Chris@2550 397 delete m_transformPopulater;
Chris@2550 398 }
Chris@2550 399
Chris@162 400 delete m_keyReference;
Chris@504 401 delete m_activityLog;
Chris@891 402 delete m_unitConverter;
Chris@163 403 delete m_preferencesDialog;
Chris@219 404 delete m_layerTreeDialog;
Chris@573 405 delete m_versionTester;
Chris@662 406 delete m_surveyer;
Chris@0 407 Profiles::getInstance()->dump();
Chris@438 408 // SVDEBUG << "MainWindow::~MainWindow finishing" << endl;
Chris@0 409 }
Chris@0 410
Chris@81 411 void
Chris@0 412 MainWindow::setupMenus()
Chris@0 413 {
Chris@2550 414 SVDEBUG << "MainWindow::setupMenus" << endl;
Chris@2550 415
Chris@0 416 if (!m_mainMenusCreated) {
Chris@779 417
Chris@779 418 #ifdef Q_OS_LINUX
Chris@779 419 // In Ubuntu 14.04 the window's menu bar goes missing entirely
Chris@779 420 // if the user is running any desktop environment other than Unity
Chris@779 421 // (in which the faux single-menubar appears). The user has a
Chris@779 422 // workaround, to remove the appmenu-qt5 package, but that is
Chris@779 423 // awkward and the problem is so severe that it merits disabling
Chris@779 424 // the system menubar integration altogether. Like this:
Chris@1770 425 menuBar()->setNativeMenuBar(false); // fix #1039
Chris@779 426 #endif
Chris@1458 427
Chris@0 428 m_rightButtonMenu = new QMenu();
Chris@104 429
Chris@1458 430 // We don't want tear-off enabled on the right-button menu.
Chris@1458 431 // If it is enabled, then simply right-clicking and releasing
Chris@1458 432 // will pop up the menu, activate the tear-off, and leave the
Chris@1458 433 // torn-off menu window in front of the main window. That
Chris@1458 434 // isn't desirable.
Chris@1458 435 m_rightButtonMenu->setTearOffEnabled(false);
Chris@0 436 }
Chris@0 437
Chris@211 438 if (m_rightButtonTransformsMenu) {
Chris@211 439 m_rightButtonTransformsMenu->clear();
Chris@34 440 } else {
Chris@211 441 m_rightButtonTransformsMenu = m_rightButtonMenu->addMenu(tr("&Transform"));
Chris@211 442 m_rightButtonTransformsMenu->setTearOffEnabled(true);
Chris@34 443 m_rightButtonMenu->addSeparator();
Chris@34 444 }
Chris@34 445
Chris@346 446 // This will be created (if not found) or cleared (if found) in
Chris@346 447 // setupPaneAndLayerMenus, but we want to ensure it's created
Chris@346 448 // sooner so it can go nearer the top of the right button menu
Chris@346 449 if (m_rightButtonLayerMenu) {
Chris@346 450 m_rightButtonLayerMenu->clear();
Chris@346 451 } else {
Chris@346 452 m_rightButtonLayerMenu = m_rightButtonMenu->addMenu(tr("&Layer"));
Chris@346 453 m_rightButtonLayerMenu->setTearOffEnabled(true);
Chris@346 454 m_rightButtonMenu->addSeparator();
Chris@346 455 }
Chris@346 456
Chris@0 457 if (!m_mainMenusCreated) {
Chris@0 458 CommandHistory::getInstance()->registerMenu(m_rightButtonMenu);
Chris@0 459 m_rightButtonMenu->addSeparator();
Chris@66 460 }
Chris@66 461
Chris@66 462 setupFileMenu();
Chris@66 463 setupEditMenu();
Chris@66 464 setupViewMenu();
Chris@66 465 setupPaneAndLayerMenus();
Chris@2550 466 prepareTransformsMenu();
Chris@66 467
Chris@66 468 m_mainMenusCreated = true;
Chris@2550 469
Chris@2550 470 SVDEBUG << "MainWindow::setupMenus: done" << endl;
Chris@66 471 }
Chris@66 472
Chris@66 473 void
Chris@489 474 MainWindow::goFullScreen()
Chris@489 475 {
Chris@588 476 if (m_viewManager->getZoomWheelsEnabled()) {
Chris@588 477 // The wheels seem to end up in the wrong place in full-screen mode
Chris@588 478 toggleZoomWheels();
Chris@588 479 }
Chris@588 480
Chris@492 481 QWidget *ps = m_mainScroll->takeWidget();
Chris@2126 482 ps->setParent(nullptr);
Chris@494 483
Chris@494 484 QShortcut *sc;
Chris@494 485
Chris@494 486 sc = new QShortcut(QKeySequence("Esc"), ps);
Chris@494 487 connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
Chris@494 488
Chris@494 489 sc = new QShortcut(QKeySequence("F11"), ps);
Chris@494 490 connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
Chris@494 491
Chris@494 492 QAction *acts[] = {
Chris@494 493 m_playAction, m_zoomInAction, m_zoomOutAction, m_zoomFitAction,
Chris@494 494 m_scrollLeftAction, m_scrollRightAction, m_showPropertyBoxesAction
Chris@494 495 };
Chris@494 496
Chris@705 497 for (int i = 0; i < int(sizeof(acts)/sizeof(acts[0])); ++i) {
Chris@494 498 sc = new QShortcut(acts[i]->shortcut(), ps);
Chris@494 499 connect(sc, SIGNAL(activated()), acts[i], SLOT(trigger()));
Chris@494 500 }
Chris@494 501
Chris@492 502 ps->showFullScreen();
Chris@492 503 }
Chris@492 504
Chris@492 505 void
Chris@492 506 MainWindow::endFullScreen()
Chris@492 507 {
Chris@494 508 // these were only created in goFullScreen:
Chris@494 509 QObjectList cl = m_paneStack->children();
Chris@494 510 foreach (QObject *o, cl) {
Chris@494 511 QShortcut *sc = qobject_cast<QShortcut *>(o);
Chris@494 512 if (sc) delete sc;
Chris@494 513 }
Chris@494 514
Chris@588 515 m_paneStack->showNormal();
Chris@492 516 m_mainScroll->setWidget(m_paneStack);
Chris@489 517 }
Chris@489 518
Chris@489 519 void
Chris@66 520 MainWindow::setupFileMenu()
Chris@66 521 {
Chris@2550 522 SVDEBUG << "MainWindow::setupFileMenu" << endl;
Chris@2550 523
Chris@66 524 if (m_mainMenusCreated) return;
Chris@66 525
Chris@66 526 QMenu *menu = menuBar()->addMenu(tr("&File"));
Chris@97 527 menu->setTearOffEnabled(true);
Chris@66 528 QToolBar *toolbar = addToolBar(tr("File Toolbar"));
Chris@66 529
Chris@162 530 m_keyReference->setCategory(tr("File and Session Management"));
Chris@162 531
Chris@168 532 IconLoader il;
Chris@168 533
Chris@168 534 QIcon icon = il.load("filenew");
Chris@66 535 QAction *action = new QAction(icon, tr("&New Session"), this);
Chris@66 536 action->setShortcut(tr("Ctrl+N"));
Chris@518 537 action->setStatusTip(tr("Abandon the current %1 session and start a new one").arg(QApplication::applicationName()));
Chris@66 538 connect(action, SIGNAL(triggered()), this, SLOT(newSession()));
Chris@162 539 m_keyReference->registerShortcut(action);
Chris@66 540 menu->addAction(action);
Chris@66 541 toolbar->addAction(action);
Chris@518 542
Chris@168 543 icon = il.load("fileopen");
Chris@66 544 action = new QAction(icon, tr("&Open..."), this);
Chris@418 545 action->setShortcut(tr("Ctrl+O"));
Chris@66 546 action->setStatusTip(tr("Open a session file, audio file, or layer"));
Chris@66 547 connect(action, SIGNAL(triggered()), this, SLOT(openSomething()));
Chris@705 548 m_keyReference->registerShortcut(action);
Chris@66 549 toolbar->addAction(action);
Chris@418 550 menu->addAction(action);
Chris@418 551
Chris@418 552 // We want this one to go on the toolbar now, if we add it at all,
Chris@418 553 // but on the menu later
Chris@418 554 QAction *iaction = new QAction(tr("&Import More Audio..."), this);
Chris@418 555 iaction->setShortcut(tr("Ctrl+I"));
Chris@418 556 iaction->setStatusTip(tr("Import an extra audio file into a new pane"));
Chris@418 557 connect(iaction, SIGNAL(triggered()), this, SLOT(importMoreAudio()));
Chris@418 558 connect(this, SIGNAL(canImportMoreAudio(bool)), iaction, SLOT(setEnabled(bool)));
Chris@418 559 m_keyReference->registerShortcut(iaction);
Chris@418 560
Chris@508 561 // We want this one to go on the toolbar now, if we add it at all,
Chris@508 562 // but on the menu later
Chris@508 563 QAction *raction = new QAction(tr("Replace &Main Audio..."), this);
Chris@508 564 raction->setStatusTip(tr("Replace the main audio file of the session with a different file"));
Chris@508 565 connect(raction, SIGNAL(triggered()), this, SLOT(replaceMainAudio()));
Chris@508 566 connect(this, SIGNAL(canReplaceMainAudio(bool)), raction, SLOT(setEnabled(bool)));
Chris@508 567
Chris@418 568 action = new QAction(tr("Open Lo&cation..."), this);
Chris@418 569 action->setShortcut(tr("Ctrl+Shift+O"));
Chris@418 570 action->setStatusTip(tr("Open or import a file from a remote URL"));
Chris@418 571 connect(action, SIGNAL(triggered()), this, SLOT(openLocation()));
Chris@418 572 m_keyReference->registerShortcut(action);
Chris@418 573 menu->addAction(action);
Chris@418 574
Chris@420 575 m_recentFilesMenu = menu->addMenu(tr("Open &Recent"));
Chris@418 576 m_recentFilesMenu->setTearOffEnabled(true);
Chris@418 577 setupRecentFilesMenu();
Chris@418 578 connect(&m_recentFiles, SIGNAL(recentChanged()),
Chris@418 579 this, SLOT(setupRecentFilesMenu()));
Chris@418 580
Chris@418 581 menu->addSeparator();
Chris@415 582
Chris@168 583 icon = il.load("filesave");
Chris@66 584 action = new QAction(icon, tr("&Save Session"), this);
Chris@66 585 action->setShortcut(tr("Ctrl+S"));
Chris@518 586 action->setStatusTip(tr("Save the current session into a %1 session file").arg(QApplication::applicationName()));
Chris@66 587 connect(action, SIGNAL(triggered()), this, SLOT(saveSession()));
Chris@66 588 connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool)));
Chris@162 589 m_keyReference->registerShortcut(action);
Chris@66 590 menu->addAction(action);
Chris@66 591 toolbar->addAction(action);
Chris@1770 592
Chris@168 593 icon = il.load("filesaveas");
Chris@66 594 action = new QAction(icon, tr("Save Session &As..."), this);
Chris@336 595 action->setShortcut(tr("Ctrl+Shift+S"));
Chris@518 596 action->setStatusTip(tr("Save the current session into a new %1 session file").arg(QApplication::applicationName()));
Chris@66 597 connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs()));
Chris@66 598 menu->addAction(action);
Chris@66 599 toolbar->addAction(action);
Chris@66 600
Chris@66 601 menu->addSeparator();
Chris@66 602
Chris@508 603 // the Replace action we made earlier
Chris@508 604 menu->addAction(raction);
Chris@508 605
Chris@418 606 // the Import action we made earlier
Chris@418 607 menu->addAction(iaction);
Chris@66 608
Chris@66 609 action = new QAction(tr("&Export Audio File..."), this);
Chris@66 610 action->setStatusTip(tr("Export selection as an audio file"));
Chris@66 611 connect(action, SIGNAL(triggered()), this, SLOT(exportAudio()));
Chris@66 612 connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool)));
Chris@66 613 menu->addAction(action);
Chris@66 614
Chris@66 615 menu->addSeparator();
Chris@66 616
Chris@66 617 action = new QAction(tr("Import Annotation &Layer..."), this);
Chris@66 618 action->setShortcut(tr("Ctrl+L"));
Chris@66 619 action->setStatusTip(tr("Import layer data from an existing file"));
Chris@66 620 connect(action, SIGNAL(triggered()), this, SLOT(importLayer()));
Chris@66 621 connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@162 622 m_keyReference->registerShortcut(action);
Chris@66 623 menu->addAction(action);
Chris@66 624
Chris@977 625 action = new QAction(tr("Export Annotation La&yer..."), this);
Chris@977 626 action->setShortcut(tr("Ctrl+Y"));
Chris@66 627 action->setStatusTip(tr("Export layer data to a file"));
Chris@66 628 connect(action, SIGNAL(triggered()), this, SLOT(exportLayer()));
Chris@66 629 connect(this, SIGNAL(canExportLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@977 630 m_keyReference->registerShortcut(action);
Chris@66 631 menu->addAction(action);
Chris@66 632
Chris@66 633 menu->addSeparator();
Chris@1912 634
Chris@1995 635 action = new QAction(tr("Convert Audio from Data File..."), this);
Chris@1995 636 action->setStatusTip(tr("Convert and import audio sample values from a CSV data file"));
Chris@1995 637 connect(action, SIGNAL(triggered()), this, SLOT(convertAudio()));
Chris@1912 638 menu->addAction(action);
Chris@1912 639
Chris@1987 640 action = new QAction(tr("Export Audio to Data File..."), this);
Chris@1912 641 action->setStatusTip(tr("Export audio from selection into a CSV data file"));
Chris@1912 642 connect(action, SIGNAL(triggered()), this, SLOT(exportAudioData()));
Chris@1912 643 connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool)));
Chris@1912 644 menu->addAction(action);
Chris@1912 645
Chris@1912 646 menu->addSeparator();
Chris@86 647
Chris@121 648 action = new QAction(tr("Export Image File..."), this);
Chris@121 649 action->setStatusTip(tr("Export a single pane to an image file"));
Chris@121 650 connect(action, SIGNAL(triggered()), this, SLOT(exportImage()));
Chris@121 651 connect(this, SIGNAL(canExportImage(bool)), action, SLOT(setEnabled(bool)));
Chris@121 652 menu->addAction(action);
Chris@121 653
Chris@1451 654 action = new QAction(tr("Export SVG File..."), this);
Chris@1451 655 action->setStatusTip(tr("Export a single pane to a scalable SVG image file"));
Chris@1451 656 connect(action, SIGNAL(triggered()), this, SLOT(exportSVG()));
Chris@1451 657 connect(this, SIGNAL(canExportImage(bool)), action, SLOT(setEnabled(bool)));
Chris@1451 658 menu->addAction(action);
Chris@1451 659
Chris@121 660 menu->addSeparator();
Chris@121 661
Chris@1995 662 action = new QAction(tr("Browse Recorded and Converted Audio"), this);
Chris@1056 663 action->setStatusTip(tr("Open the Recorded Audio folder in the system file browser"));
Chris@1056 664 connect(action, SIGNAL(triggered()), this, SLOT(browseRecordedAudio()));
Chris@1056 665 menu->addAction(action);
Chris@1056 666
Chris@1056 667 menu->addSeparator();
Chris@1056 668
Chris@435 669 QString templatesMenuLabel = tr("Apply Session Template");
Chris@425 670 m_templatesMenu = menu->addMenu(templatesMenuLabel);
Chris@425 671 m_templatesMenu->setTearOffEnabled(true);
Chris@436 672 // We need to have a main model for this option to be useful:
Chris@436 673 // canExportAudio captures that
Chris@436 674 connect(this, SIGNAL(canExportAudio(bool)), m_templatesMenu, SLOT(setEnabled(bool)));
Chris@436 675
Chris@436 676 // Set up the menu in a moment, after m_manageTemplatesAction constructed
Chris@435 677
Chris@435 678 action = new QAction(tr("Export Session as Template..."), this);
Chris@435 679 connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAsTemplate()));
Chris@436 680 // We need to have something in the session for this to be useful:
Chris@436 681 // canDeleteCurrentLayer captures that
Chris@436 682 connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool)));
Chris@435 683 menu->addAction(action);
Chris@435 684
Chris@436 685 m_manageTemplatesAction = new QAction(tr("Manage Exported Templates"), this);
Chris@436 686 connect(m_manageTemplatesAction, SIGNAL(triggered()), this, SLOT(manageSavedTemplates()));
Chris@436 687 menu->addAction(m_manageTemplatesAction);
Chris@436 688
Chris@436 689 setupTemplatesMenu();
Chris@425 690
Chris@66 691 action = new QAction(tr("&Preferences..."), this);
Chris@66 692 action->setStatusTip(tr("Adjust the application preferences"));
Chris@66 693 connect(action, SIGNAL(triggered()), this, SLOT(preferences()));
Chris@66 694 menu->addAction(action);
Chris@1770 695
Chris@66 696 menu->addSeparator();
Chris@2550 697 action = new QAction(il.load("exit"), tr("&Quit"), this);
Chris@66 698 action->setShortcut(tr("Ctrl+Q"));
Chris@518 699 action->setStatusTip(tr("Exit %1").arg(QApplication::applicationName()));
Chris@500 700 connect(action, SIGNAL(triggered()), qApp, SLOT(closeAllWindows()));
Chris@162 701 m_keyReference->registerShortcut(action);
Chris@66 702 menu->addAction(action);
Chris@66 703 }
Chris@66 704
Chris@66 705 void
Chris@66 706 MainWindow::setupEditMenu()
Chris@66 707 {
Chris@2550 708 SVDEBUG << "MainWindow::setupEditMenu" << endl;
Chris@2550 709
Chris@66 710 if (m_mainMenusCreated) return;
Chris@66 711
Chris@66 712 QMenu *menu = menuBar()->addMenu(tr("&Edit"));
Chris@97 713 menu->setTearOffEnabled(true);
Chris@66 714 CommandHistory::getInstance()->registerMenu(menu);
Chris@66 715
Chris@162 716 m_keyReference->setCategory(tr("Editing"));
Chris@162 717
Chris@66 718 menu->addSeparator();
Chris@66 719
Chris@168 720 IconLoader il;
Chris@168 721
Chris@168 722 QAction *action = new QAction(il.load("editcut"),
Chris@66 723 tr("Cu&t"), this);
Chris@66 724 action->setShortcut(tr("Ctrl+X"));
Chris@90 725 action->setStatusTip(tr("Cut the selection from the current layer to the clipboard"));
Chris@66 726 connect(action, SIGNAL(triggered()), this, SLOT(cut()));
Chris@66 727 connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool)));
Chris@162 728 m_keyReference->registerShortcut(action);
Chris@66 729 menu->addAction(action);
Chris@66 730 m_rightButtonMenu->addAction(action);
Chris@66 731
Chris@168 732 action = new QAction(il.load("editcopy"),
Chris@66 733 tr("&Copy"), this);
Chris@66 734 action->setShortcut(tr("Ctrl+C"));
Chris@90 735 action->setStatusTip(tr("Copy the selection from the current layer to the clipboard"));
Chris@66 736 connect(action, SIGNAL(triggered()), this, SLOT(copy()));
Chris@66 737 connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool)));
Chris@162 738 m_keyReference->registerShortcut(action);
Chris@66 739 menu->addAction(action);
Chris@66 740 m_rightButtonMenu->addAction(action);
Chris@66 741
Chris@168 742 action = new QAction(il.load("editpaste"),
Chris@66 743 tr("&Paste"), this);
Chris@66 744 action->setShortcut(tr("Ctrl+V"));
Chris@90 745 action->setStatusTip(tr("Paste from the clipboard to the current layer"));
Chris@66 746 connect(action, SIGNAL(triggered()), this, SLOT(paste()));
Chris@66 747 connect(this, SIGNAL(canPaste(bool)), action, SLOT(setEnabled(bool)));
Chris@162 748 m_keyReference->registerShortcut(action);
Chris@66 749 menu->addAction(action);
Chris@66 750 m_rightButtonMenu->addAction(action);
Chris@66 751
Chris@395 752 action = new QAction(tr("Paste at Playback Position"), this);
Chris@395 753 action->setShortcut(tr("Ctrl+Shift+V"));
Chris@395 754 action->setStatusTip(tr("Paste from the clipboard to the current layer, placing the first item at the playback position"));
Chris@395 755 connect(action, SIGNAL(triggered()), this, SLOT(pasteAtPlaybackPosition()));
Chris@395 756 connect(this, SIGNAL(canPaste(bool)), action, SLOT(setEnabled(bool)));
Chris@395 757 m_keyReference->registerShortcut(action);
Chris@395 758 menu->addAction(action);
Chris@395 759 m_rightButtonMenu->addAction(action);
Chris@395 760
Chris@164 761 m_deleteSelectedAction = new QAction(tr("&Delete Selected Items"), this);
Chris@164 762 m_deleteSelectedAction->setShortcut(tr("Del"));
Chris@164 763 m_deleteSelectedAction->setStatusTip(tr("Delete items in current selection from the current layer"));
Chris@164 764 connect(m_deleteSelectedAction, SIGNAL(triggered()), this, SLOT(deleteSelected()));
Chris@164 765 connect(this, SIGNAL(canDeleteSelection(bool)), m_deleteSelectedAction, SLOT(setEnabled(bool)));
Chris@164 766 m_keyReference->registerShortcut(m_deleteSelectedAction);
Chris@164 767 menu->addAction(m_deleteSelectedAction);
Chris@164 768 m_rightButtonMenu->addAction(m_deleteSelectedAction);
Chris@66 769
Chris@66 770 menu->addSeparator();
Chris@66 771 m_rightButtonMenu->addSeparator();
Chris@162 772
Chris@162 773 m_keyReference->setCategory(tr("Selection"));
Chris@162 774
Chris@66 775 action = new QAction(tr("Select &All"), this);
Chris@66 776 action->setShortcut(tr("Ctrl+A"));
Chris@90 777 action->setStatusTip(tr("Select the whole duration of the current session"));
Chris@66 778 connect(action, SIGNAL(triggered()), this, SLOT(selectAll()));
Chris@66 779 connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool)));
Chris@162 780 m_keyReference->registerShortcut(action);
Chris@66 781 menu->addAction(action);
Chris@66 782 m_rightButtonMenu->addAction(action);
Chris@1770 783
Chris@66 784 action = new QAction(tr("Select &Visible Range"), this);
Chris@66 785 action->setShortcut(tr("Ctrl+Shift+A"));
Chris@90 786 action->setStatusTip(tr("Select the time range corresponding to the current window width"));
Chris@66 787 connect(action, SIGNAL(triggered()), this, SLOT(selectVisible()));
Chris@66 788 connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool)));
Chris@162 789 m_keyReference->registerShortcut(action);
Chris@66 790 menu->addAction(action);
Chris@1770 791
Chris@66 792 action = new QAction(tr("Select to &Start"), this);
Chris@66 793 action->setShortcut(tr("Shift+Left"));
Chris@90 794 action->setStatusTip(tr("Select from the start of the session to the current playback position"));
Chris@66 795 connect(action, SIGNAL(triggered()), this, SLOT(selectToStart()));
Chris@66 796 connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool)));
Chris@162 797 m_keyReference->registerShortcut(action);
Chris@66 798 menu->addAction(action);
Chris@1770 799
Chris@66 800 action = new QAction(tr("Select to &End"), this);
Chris@66 801 action->setShortcut(tr("Shift+Right"));
Chris@90 802 action->setStatusTip(tr("Select from the current playback position to the end of the session"));
Chris@66 803 connect(action, SIGNAL(triggered()), this, SLOT(selectToEnd()));
Chris@66 804 connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool)));
Chris@162 805 m_keyReference->registerShortcut(action);
Chris@66 806 menu->addAction(action);
Chris@66 807
Chris@66 808 action = new QAction(tr("C&lear Selection"), this);
Chris@66 809 action->setShortcut(tr("Esc"));
Chris@90 810 action->setStatusTip(tr("Clear the selection"));
Chris@66 811 connect(action, SIGNAL(triggered()), this, SLOT(clearSelection()));
Chris@66 812 connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool)));
Chris@162 813 m_keyReference->registerShortcut(action);
Chris@66 814 menu->addAction(action);
Chris@66 815 m_rightButtonMenu->addAction(action);
Chris@66 816
Chris@66 817 menu->addSeparator();
Chris@66 818
Chris@162 819 m_keyReference->setCategory(tr("Tapping Time Instants"));
Chris@162 820
Chris@66 821 action = new QAction(tr("&Insert Instant at Playback Position"), this);
Chris@1782 822 action->setShortcut(tr(";"));
Chris@90 823 action->setStatusTip(tr("Insert a new time instant at the current playback position, in a new layer if necessary"));
Chris@66 824 connect(action, SIGNAL(triggered()), this, SLOT(insertInstant()));
Chris@66 825 connect(this, SIGNAL(canInsertInstant(bool)), action, SLOT(setEnabled(bool)));
Chris@162 826 m_keyReference->registerShortcut(action);
Chris@66 827 menu->addAction(action);
Chris@66 828
Chris@1782 829 // Historically this was the main shortcut for "Insert Instant at
Chris@1782 830 // Playback Position". Note that Enter refers to the keypad key,
Chris@1782 831 // rather than the Return key, so this doesn't actually exist on
Chris@1782 832 // many keyboards now. Accordingly the alternative shortcut ";"
Chris@1782 833 // has been promoted to primary, listed above. Same goes for the
Chris@1782 834 // shifted version below
Chris@1782 835 QString shortcut(tr("Enter"));
Chris@162 836 connect(new QShortcut(shortcut, this), SIGNAL(activated()),
Chris@162 837 this, SLOT(insertInstant()));
Chris@162 838 m_keyReference->registerAlternativeShortcut(action, shortcut);
Chris@162 839
Chris@81 840 action = new QAction(tr("Insert Instants at Selection &Boundaries"), this);
Chris@1782 841 action->setShortcut(tr("Shift+;"));
Chris@163 842 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 843 connect(action, SIGNAL(triggered()), this, SLOT(insertInstantsAtBoundaries()));
Chris@81 844 connect(this, SIGNAL(canInsertInstantsAtBoundaries(bool)), action, SLOT(setEnabled(bool)));
Chris@162 845 m_keyReference->registerShortcut(action);
Chris@81 846 menu->addAction(action);
Chris@189 847
Chris@1782 848 shortcut = QString(tr("Shift+Enter"));
Chris@1782 849 connect(new QShortcut(shortcut, this), SIGNAL(activated()),
Chris@1782 850 this, SLOT(insertInstantsAtBoundaries()));
Chris@1782 851 m_keyReference->registerAlternativeShortcut(action, shortcut);
Chris@1782 852
Chris@1782 853 // The previous two actions used shortcuts with the (keypad) Enter
Chris@1782 854 // key, while this one I (bizarrely) switched from Enter to Return
Chris@1782 855 // in September 2014. Let's make it consistent with the above by
Chris@1782 856 // making the primary shortcut for it Ctrl+Shift+; and keeping
Chris@1782 857 // both Return and Enter as synonyms for ;
Chris@338 858 action = new QAction(tr("Insert Item at Selection"), this);
Chris@1782 859 action->setShortcut(tr("Ctrl+Shift+;"));
Chris@338 860 action->setStatusTip(tr("Insert a new note or region item corresponding to the current selection"));
Chris@338 861 connect(action, SIGNAL(triggered()), this, SLOT(insertItemAtSelection()));
Chris@338 862 connect(this, SIGNAL(canInsertItemAtSelection(bool)), action, SLOT(setEnabled(bool)));
Chris@338 863 m_keyReference->registerShortcut(action);
Chris@338 864 menu->addAction(action);
Chris@338 865
Chris@1782 866 shortcut = QString(tr("Ctrl+Shift+Enter"));
Chris@1782 867 connect(new QShortcut(shortcut, this), SIGNAL(activated()),
Chris@1782 868 this, SLOT(insertItemAtSelection()));
Chris@1782 869 m_keyReference->registerAlternativeShortcut(action, shortcut);
Chris@1782 870
Chris@1782 871 shortcut = QString(tr("Ctrl+Shift+Return"));
Chris@1782 872 connect(new QShortcut(shortcut, this), SIGNAL(activated()),
Chris@1782 873 this, SLOT(insertItemAtSelection()));
Chris@1782 874 // we had that one for historical compatibility, but let's not
Chris@1782 875 // register it publicly; having three shortcuts for such an
Chris@1782 876 // obscure function is really over-egging it
Chris@1782 877
Chris@338 878 menu->addSeparator();
Chris@338 879
Chris@190 880 QMenu *numberingMenu = menu->addMenu(tr("Number New Instants with"));
Chris@225 881 numberingMenu->setTearOffEnabled(true);
Chris@189 882 QActionGroup *numberingGroup = new QActionGroup(this);
Chris@2093 883 m_numberingActions.clear();
Chris@189 884
Chris@189 885 Labeller::TypeNameMap types = m_labeller->getTypeNames();
Chris@189 886 for (Labeller::TypeNameMap::iterator i = types.begin(); i != types.end(); ++i) {
Chris@190 887
Chris@190 888 if (i->first == Labeller::ValueFromLabel ||
Chris@190 889 i->first == Labeller::ValueFromExistingNeighbour) continue;
Chris@190 890
Chris@189 891 action = new QAction(i->second, this);
Chris@189 892 connect(action, SIGNAL(triggered()), this, SLOT(setInstantsNumbering()));
Chris@189 893 action->setCheckable(true);
Chris@189 894 action->setChecked(m_labeller->getType() == i->first);
Chris@189 895 numberingGroup->addAction(action);
Chris@189 896 numberingMenu->addAction(action);
Chris@2093 897 m_numberingActions.push_back({ action, (int)i->first });
Chris@190 898
Chris@190 899 if (i->first == Labeller::ValueFromTwoLevelCounter) {
Chris@192 900
Chris@190 901 QMenu *cycleMenu = numberingMenu->addMenu(tr("Cycle size"));
Chris@190 902 QActionGroup *cycleGroup = new QActionGroup(this);
Chris@190 903
Chris@229 904 int cycles[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 16 };
Chris@200 905 for (int i = 0; i < int(sizeof(cycles)/sizeof(cycles[0])); ++i) {
Chris@190 906 action = new QAction(QString("%1").arg(cycles[i]), this);
Chris@190 907 connect(action, SIGNAL(triggered()), this, SLOT(setInstantsCounterCycle()));
Chris@190 908 action->setCheckable(true);
Chris@190 909 action->setChecked(cycles[i] == m_labeller->getCounterCycleSize());
Chris@190 910 cycleGroup->addAction(action);
Chris@190 911 cycleMenu->addAction(action);
Chris@190 912 }
Chris@190 913 }
Chris@190 914
Chris@190 915 if (i->first == Labeller::ValueNone ||
Chris@190 916 i->first == Labeller::ValueFromTwoLevelCounter ||
Chris@190 917 i->first == Labeller::ValueFromRealTime) {
Chris@190 918 numberingMenu->addSeparator();
Chris@190 919 }
Chris@189 920 }
Chris@189 921
Chris@597 922 action = new QAction(tr("Reset Numbering Counters"), this);
Chris@597 923 action->setStatusTip(tr("Reset to 1 all the counters used for counter-based labelling"));
Chris@597 924 connect(action, SIGNAL(triggered()), this, SLOT(resetInstantsCounters()));
Chris@597 925 connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger()));
Chris@597 926 menu->addAction(action);
Chris@597 927
Chris@241 928 action = new QAction(tr("Set Numbering Counters..."), this);
Chris@241 929 action->setStatusTip(tr("Set the counters used for counter-based labelling"));
Chris@597 930 connect(action, SIGNAL(triggered()), this, SLOT(setInstantsCounters()));
Chris@241 931 menu->addAction(action);
Chris@241 932
Chris@241 933 action = new QAction(tr("Renumber Selected Instants"), this);
Chris@241 934 action->setStatusTip(tr("Renumber the selected instants using the current labelling scheme"));
Chris@189 935 connect(action, SIGNAL(triggered()), this, SLOT(renumberInstants()));
Chris@189 936 connect(this, SIGNAL(canRenumberInstants(bool)), action, SLOT(setEnabled(bool)));
Chris@189 937 // m_keyReference->registerShortcut(action);
Chris@189 938 menu->addAction(action);
Chris@1356 939
Chris@1356 940 menu->addSeparator();
Chris@1356 941
Chris@1355 942 action = new QAction(tr("Subdivide Selected Instants..."), this);
Chris@1355 943 action->setStatusTip(tr("Add new instants at regular intervals between the selected instants"));
Chris@1355 944 connect(action, SIGNAL(triggered()), this, SLOT(subdivideInstants()));
Chris@1355 945 connect(this, SIGNAL(canSubdivideInstants(bool)), action, SLOT(setEnabled(bool)));
Chris@1355 946 menu->addAction(action);
Chris@1356 947
Chris@1356 948 action = new QAction(tr("Winnow Selected Instants..."), this);
Chris@1356 949 action->setStatusTip(tr("Remove subdivisions, leaving only every Nth instant"));
Chris@1356 950 connect(action, SIGNAL(triggered()), this, SLOT(winnowInstants()));
Chris@1356 951 connect(this, SIGNAL(canWinnowInstants(bool)), action, SLOT(setEnabled(bool)));
Chris@1356 952 menu->addAction(action);
Chris@66 953 }
Chris@66 954
Chris@66 955 void
Chris@66 956 MainWindow::setupViewMenu()
Chris@66 957 {
Chris@2550 958 SVDEBUG << "MainWindow::setupViewMenu" << endl;
Chris@2550 959
Chris@66 960 if (m_mainMenusCreated) return;
Chris@66 961
Chris@168 962 IconLoader il;
Chris@168 963
Chris@2126 964 QAction *action = nullptr;
Chris@90 965
Chris@162 966 m_keyReference->setCategory(tr("Panning and Navigation"));
Chris@162 967
Chris@66 968 QMenu *menu = menuBar()->addMenu(tr("&View"));
Chris@97 969 menu->setTearOffEnabled(true);
Chris@494 970 m_scrollLeftAction = new QAction(tr("Scroll &Left"), this);
Chris@494 971 m_scrollLeftAction->setShortcut(tr("Left"));
Chris@494 972 m_scrollLeftAction->setStatusTip(tr("Scroll the current pane to the left"));
Chris@494 973 connect(m_scrollLeftAction, SIGNAL(triggered()), this, SLOT(scrollLeft()));
Chris@494 974 connect(this, SIGNAL(canScroll(bool)), m_scrollLeftAction, SLOT(setEnabled(bool)));
Chris@494 975 m_keyReference->registerShortcut(m_scrollLeftAction);
Chris@494 976 menu->addAction(m_scrollLeftAction);
Chris@1770 977
Chris@494 978 m_scrollRightAction = new QAction(tr("Scroll &Right"), this);
Chris@494 979 m_scrollRightAction->setShortcut(tr("Right"));
Chris@494 980 m_scrollRightAction->setStatusTip(tr("Scroll the current pane to the right"));
Chris@494 981 connect(m_scrollRightAction, SIGNAL(triggered()), this, SLOT(scrollRight()));
Chris@494 982 connect(this, SIGNAL(canScroll(bool)), m_scrollRightAction, SLOT(setEnabled(bool)));
Chris@494 983 m_keyReference->registerShortcut(m_scrollRightAction);
Chris@494 984 menu->addAction(m_scrollRightAction);
Chris@1770 985
Chris@90 986 action = new QAction(tr("&Jump Left"), this);
Chris@66 987 action->setShortcut(tr("Ctrl+Left"));
Chris@66 988 action->setStatusTip(tr("Scroll the current pane a big step to the left"));
Chris@66 989 connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft()));
Chris@66 990 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
Chris@162 991 m_keyReference->registerShortcut(action);
Chris@66 992 menu->addAction(action);
Chris@1770 993
Chris@90 994 action = new QAction(tr("J&ump Right"), this);
Chris@66 995 action->setShortcut(tr("Ctrl+Right"));
Chris@66 996 action->setStatusTip(tr("Scroll the current pane a big step to the right"));
Chris@66 997 connect(action, SIGNAL(triggered()), this, SLOT(jumpRight()));
Chris@66 998 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
Chris@162 999 m_keyReference->registerShortcut(action);
Chris@66 1000 menu->addAction(action);
Chris@66 1001
Chris@308 1002 action = new QAction(tr("Peek Left"), this);
Chris@308 1003 action->setShortcut(tr("Alt+Left"));
Chris@308 1004 action->setStatusTip(tr("Scroll the current pane to the left without moving the playback cursor or other panes"));
Chris@308 1005 connect(action, SIGNAL(triggered()), this, SLOT(peekLeft()));
Chris@308 1006 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
Chris@308 1007 m_keyReference->registerShortcut(action);
Chris@308 1008 menu->addAction(action);
Chris@1770 1009
Chris@308 1010 action = new QAction(tr("Peek Right"), this);
Chris@308 1011 action->setShortcut(tr("Alt+Right"));
Chris@308 1012 action->setStatusTip(tr("Scroll the current pane to the right without moving the playback cursor or other panes"));
Chris@308 1013 connect(action, SIGNAL(triggered()), this, SLOT(peekRight()));
Chris@308 1014 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
Chris@308 1015 m_keyReference->registerShortcut(action);
Chris@308 1016 menu->addAction(action);
Chris@308 1017
Chris@66 1018 menu->addSeparator();
Chris@66 1019
Chris@162 1020 m_keyReference->setCategory(tr("Zoom"));
Chris@162 1021
Chris@494 1022 m_zoomInAction = new QAction(il.load("zoom-in"),
Chris@494 1023 tr("Zoom &In"), this);
Chris@494 1024 m_zoomInAction->setShortcut(tr("Up"));
Chris@494 1025 m_zoomInAction->setStatusTip(tr("Increase the zoom level"));
Chris@494 1026 connect(m_zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn()));
Chris@494 1027 connect(this, SIGNAL(canZoom(bool)), m_zoomInAction, SLOT(setEnabled(bool)));
Chris@494 1028 m_keyReference->registerShortcut(m_zoomInAction);
Chris@494 1029 menu->addAction(m_zoomInAction);
Chris@1770 1030
Chris@494 1031 m_zoomOutAction = new QAction(il.load("zoom-out"),
Chris@494 1032 tr("Zoom &Out"), this);
Chris@494 1033 m_zoomOutAction->setShortcut(tr("Down"));
Chris@494 1034 m_zoomOutAction->setStatusTip(tr("Decrease the zoom level"));
Chris@494 1035 connect(m_zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut()));
Chris@494 1036 connect(this, SIGNAL(canZoom(bool)), m_zoomOutAction, SLOT(setEnabled(bool)));
Chris@494 1037 m_keyReference->registerShortcut(m_zoomOutAction);
Chris@494 1038 menu->addAction(m_zoomOutAction);
Chris@1770 1039
Chris@66 1040 action = new QAction(tr("Restore &Default Zoom"), this);
Chris@90 1041 action->setStatusTip(tr("Restore the zoom level to the default"));
Chris@66 1042 connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault()));
Chris@66 1043 connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool)));
Chris@66 1044 menu->addAction(action);
Chris@66 1045
Chris@494 1046 m_zoomFitAction = new QAction(il.load("zoom-fit"),
Chris@494 1047 tr("Zoom to &Fit"), this);
Chris@494 1048 m_zoomFitAction->setShortcut(tr("F"));
Chris@494 1049 m_zoomFitAction->setStatusTip(tr("Zoom to show the whole file"));
Chris@494 1050 connect(m_zoomFitAction, SIGNAL(triggered()), this, SLOT(zoomToFit()));
Chris@494 1051 connect(this, SIGNAL(canZoom(bool)), m_zoomFitAction, SLOT(setEnabled(bool)));
Chris@494 1052 m_keyReference->registerShortcut(m_zoomFitAction);
Chris@494 1053 menu->addAction(m_zoomFitAction);
Chris@90 1054
Chris@90 1055 menu->addSeparator();
Chris@90 1056
Chris@162 1057 m_keyReference->setCategory(tr("Display Features"));
Chris@162 1058
Chris@497 1059 action = new QAction(tr("Show &Centre Line"), this);
Chris@497 1060 action->setShortcut(tr("'"));
Chris@497 1061 action->setStatusTip(tr("Show or hide the centre line"));
Chris@497 1062 connect(action, SIGNAL(triggered()), this, SLOT(toggleCentreLine()));
Chris@497 1063 action->setCheckable(true);
Chris@703 1064 action->setChecked(m_viewManager->shouldShowCentreLine());
Chris@497 1065 m_keyReference->registerShortcut(action);
Chris@497 1066 menu->addAction(action);
Chris@497 1067
Chris@497 1068 action = new QAction(tr("Toggle All Time Rulers"), this);
Chris@497 1069 action->setShortcut(tr("#"));
Chris@497 1070 action->setStatusTip(tr("Show or hide all time rulers"));
Chris@497 1071 connect(action, SIGNAL(triggered()), this, SLOT(toggleTimeRulers()));
Chris@497 1072 m_keyReference->registerShortcut(action);
Chris@497 1073 menu->addAction(action);
Chris@497 1074
Chris@497 1075 menu->addSeparator();
Chris@497 1076
Chris@90 1077 QActionGroup *overlayGroup = new QActionGroup(this);
Chris@90 1078
Chris@703 1079 ViewManager::OverlayMode mode = m_viewManager->getOverlayMode();
Chris@703 1080
Chris@90 1081 action = new QAction(tr("Show &No Overlays"), this);
Chris@90 1082 action->setShortcut(tr("0"));
Chris@497 1083 action->setStatusTip(tr("Hide times, layer names, and scale"));
Chris@90 1084 connect(action, SIGNAL(triggered()), this, SLOT(showNoOverlays()));
Chris@90 1085 action->setCheckable(true);
Chris@703 1086 action->setChecked(mode == ViewManager::NoOverlays);
Chris@90 1087 overlayGroup->addAction(action);
Chris@162 1088 m_keyReference->registerShortcut(action);
Chris@90 1089 menu->addAction(action);
Chris@90 1090
Chris@90 1091 action = new QAction(tr("Show &Minimal Overlays"), this);
Chris@90 1092 action->setShortcut(tr("9"));
Chris@497 1093 action->setStatusTip(tr("Show times and basic scale"));
Chris@90 1094 connect(action, SIGNAL(triggered()), this, SLOT(showMinimalOverlays()));
Chris@90 1095 action->setCheckable(true);
Chris@715 1096 action->setChecked(mode == ViewManager::StandardOverlays);
Chris@90 1097 overlayGroup->addAction(action);
Chris@162 1098 m_keyReference->registerShortcut(action);
Chris@90 1099 menu->addAction(action);
Chris@90 1100
Chris@90 1101 action = new QAction(tr("Show &All Overlays"), this);
Chris@497 1102 action->setShortcut(tr("8"));
Chris@497 1103 action->setStatusTip(tr("Show times, layer names, and scale"));
Chris@90 1104 connect(action, SIGNAL(triggered()), this, SLOT(showAllOverlays()));
Chris@90 1105 action->setCheckable(true);
Chris@703 1106 action->setChecked(mode == ViewManager::AllOverlays);
Chris@90 1107 overlayGroup->addAction(action);
Chris@162 1108 m_keyReference->registerShortcut(action);
Chris@90 1109 menu->addAction(action);
Chris@387 1110
Chris@387 1111 menu->addSeparator();
Chris@497 1112
Chris@66 1113 action = new QAction(tr("Show &Zoom Wheels"), this);
Chris@66 1114 action->setShortcut(tr("Z"));
Chris@66 1115 action->setStatusTip(tr("Show thumbwheels for zooming horizontally and vertically"));
Chris@66 1116 connect(action, SIGNAL(triggered()), this, SLOT(toggleZoomWheels()));
Chris@66 1117 action->setCheckable(true);
Chris@66 1118 action->setChecked(m_viewManager->getZoomWheelsEnabled());
Chris@162 1119 m_keyReference->registerShortcut(action);
Chris@66 1120 menu->addAction(action);
Chris@387 1121
Chris@494 1122 m_showPropertyBoxesAction = new QAction(tr("Show Property Bo&xes"), this);
Chris@494 1123 m_showPropertyBoxesAction->setShortcut(tr("X"));
Chris@494 1124 m_showPropertyBoxesAction->setStatusTip(tr("Show the layer property boxes at the side of the main window"));
Chris@494 1125 connect(m_showPropertyBoxesAction, SIGNAL(triggered()), this, SLOT(togglePropertyBoxes()));
Chris@494 1126 m_showPropertyBoxesAction->setCheckable(true);
Chris@494 1127 m_showPropertyBoxesAction->setChecked(true);
Chris@494 1128 m_keyReference->registerShortcut(m_showPropertyBoxesAction);
Chris@494 1129 menu->addAction(m_showPropertyBoxesAction);
Chris@0 1130
Chris@90 1131 action = new QAction(tr("Show Status &Bar"), this);
Chris@90 1132 action->setStatusTip(tr("Show context help information in the status bar at the bottom of the window"));
Chris@90 1133 connect(action, SIGNAL(triggered()), this, SLOT(toggleStatusBar()));
Chris@90 1134 action->setCheckable(true);
Chris@90 1135 action->setChecked(true);
Chris@90 1136 menu->addAction(action);
Chris@90 1137
Chris@90 1138 QSettings settings;
Chris@90 1139 settings.beginGroup("MainWindow");
Chris@90 1140 bool sb = settings.value("showstatusbar", true).toBool();
Chris@90 1141 if (!sb) {
Chris@90 1142 action->setChecked(false);
Chris@90 1143 statusBar()->hide();
Chris@90 1144 }
Chris@90 1145 settings.endGroup();
Chris@90 1146
Chris@66 1147 menu->addSeparator();
Chris@66 1148
Chris@219 1149 action = new QAction(tr("Show La&yer Summary"), this);
Chris@219 1150 action->setShortcut(tr("Y"));
Chris@90 1151 action->setStatusTip(tr("Open a window displaying the hierarchy of panes and layers in this session"));
Chris@66 1152 connect(action, SIGNAL(triggered()), this, SLOT(showLayerTree()));
Chris@162 1153 m_keyReference->registerShortcut(action);
Chris@66 1154 menu->addAction(action);
Chris@306 1155
Chris@306 1156 action = new QAction(tr("Show Acti&vity Log"), this);
Chris@306 1157 action->setStatusTip(tr("Open a window listing interactions and other events"));
Chris@306 1158 connect(action, SIGNAL(triggered()), this, SLOT(showActivityLog()));
Chris@306 1159 menu->addAction(action);
Chris@493 1160
Chris@891 1161 action = new QAction(tr("Show &Unit Converter"), this);
Chris@891 1162 action->setStatusTip(tr("Open a window of pitch and timing conversion utilities"));
Chris@891 1163 connect(action, SIGNAL(triggered()), this, SLOT(showUnitConverter()));
Chris@891 1164 menu->addAction(action);
Chris@891 1165
Chris@494 1166 menu->addSeparator();
Chris@494 1167
Chris@1387 1168 #ifndef Q_OS_MAC
Chris@1387 1169 // Only on non-Mac platforms -- on the Mac this interacts very
Chris@1387 1170 // badly with the "native" full-screen mode
Chris@493 1171 action = new QAction(tr("Go Full-Screen"), this);
Chris@494 1172 action->setShortcut(tr("F11"));
Chris@494 1173 action->setStatusTip(tr("Expand the pane area to the whole screen"));
Chris@493 1174 connect(action, SIGNAL(triggered()), this, SLOT(goFullScreen()));
Chris@494 1175 m_keyReference->registerShortcut(action);
Chris@493 1176 menu->addAction(action);
Chris@1387 1177 #endif
Chris@66 1178 }
Chris@66 1179
Chris@1794 1180 QString
Chris@1794 1181 MainWindow::shortcutFor(LayerFactory::LayerType layer, bool isPaneMenu)
Chris@1794 1182 {
Chris@1794 1183 QString shortcutText;
Chris@1794 1184
Chris@1794 1185 #ifdef __GNUC__
Chris@1794 1186 #pragma GCC diagnostic ignored "-Wswitch-enum"
Chris@1794 1187 #endif
Chris@1794 1188
Chris@1794 1189 switch (layer) {
Chris@1794 1190 case LayerFactory::Waveform:
Chris@1794 1191 if (isPaneMenu) {
Chris@1794 1192 shortcutText = tr("W");
Chris@1794 1193 } else {
Chris@1794 1194 shortcutText = tr("Shift+W");
Chris@1794 1195 }
Chris@1794 1196 break;
Chris@1794 1197
Chris@1794 1198 case LayerFactory::Spectrogram:
Chris@1794 1199 if (isPaneMenu) {
Chris@1794 1200 shortcutText = tr("G");
Chris@1794 1201 } else {
Chris@1794 1202 shortcutText = tr("Shift+G");
Chris@1794 1203 }
Chris@1794 1204 break;
Chris@1794 1205
Chris@1794 1206 case LayerFactory::MelodicRangeSpectrogram:
Chris@1794 1207 if (isPaneMenu) {
Chris@1794 1208 shortcutText = tr("M");
Chris@1794 1209 } else {
Chris@1794 1210 shortcutText = tr("Shift+M");
Chris@1794 1211 }
Chris@1794 1212 break;
Chris@1794 1213
Chris@1794 1214 case LayerFactory::PeakFrequencySpectrogram:
Chris@1794 1215 if (isPaneMenu) {
Chris@1794 1216 shortcutText = tr("K");
Chris@1794 1217 } else {
Chris@1794 1218 shortcutText = tr("Shift+K");
Chris@1794 1219 }
Chris@1794 1220 break;
Chris@1794 1221
Chris@1794 1222 case LayerFactory::Spectrum:
Chris@1794 1223 if (isPaneMenu) {
Chris@1794 1224 shortcutText = tr("U");
Chris@1794 1225 } else {
Chris@1794 1226 shortcutText = tr("Shift+U");
Chris@1794 1227 }
Chris@1794 1228 break;
Chris@1794 1229
Chris@1794 1230 default:
Chris@1794 1231 break;
Chris@1794 1232 }
Chris@1794 1233
Chris@1794 1234 return shortcutText;
Chris@1794 1235 }
Chris@1794 1236
Chris@66 1237 void
Chris@66 1238 MainWindow::setupPaneAndLayerMenus()
Chris@66 1239 {
Chris@2550 1240 SVDEBUG << "MainWindow::setupPaneAndLayerMenus" << endl;
Chris@2550 1241
Chris@2538 1242 Profiler profiler("MainWindow::setupPaneAndLayerMenus");
Chris@2538 1243
Chris@0 1244 if (m_paneMenu) {
Chris@2538 1245 m_paneMenu->clear();
Chris@2538 1246 for (auto a: m_paneActions) {
Chris@2538 1247 delete a.first;
Chris@2538 1248 }
Chris@1770 1249 m_paneActions.clear();
Chris@0 1250 } else {
Chris@1770 1251 m_paneMenu = menuBar()->addMenu(tr("&Pane"));
Chris@97 1252 m_paneMenu->setTearOffEnabled(true);
Chris@0 1253 }
Chris@0 1254
Chris@345 1255 if (m_rightButtonLayerMenu) {
Chris@345 1256 m_rightButtonLayerMenu->clear();
Chris@345 1257 } else {
Chris@345 1258 m_rightButtonLayerMenu = m_rightButtonMenu->addMenu(tr("&Layer"));
Chris@345 1259 m_rightButtonLayerMenu->setTearOffEnabled(true);
Chris@345 1260 m_rightButtonMenu->addSeparator();
Chris@345 1261 }
Chris@345 1262
Chris@2538 1263 if (m_layerMenu) {
Chris@2538 1264 m_layerMenu->clear();
Chris@2538 1265 for (auto a: m_layerActions) {
Chris@2538 1266 delete a.first;
Chris@2538 1267 }
Chris@2538 1268 m_layerActions.clear();
Chris@2538 1269 } else {
Chris@2538 1270 m_layerMenu = menuBar()->addMenu(tr("&Layer"));
Chris@2538 1271 m_layerMenu->setTearOffEnabled(true);
Chris@2538 1272 }
Chris@2538 1273
Chris@66 1274 QMenu *menu = m_paneMenu;
Chris@66 1275
Chris@168 1276 IconLoader il;
Chris@168 1277
Chris@162 1278 m_keyReference->setCategory(tr("Managing Panes and Layers"));
Chris@162 1279
Chris@2093 1280 m_paneActions.clear();
Chris@2093 1281 m_layerActions.clear();
Chris@2093 1282
Chris@168 1283 QAction *action = new QAction(il.load("pane"), tr("Add &New Pane"), this);
Chris@155 1284 action->setShortcut(tr("N"));
Chris@66 1285 action->setStatusTip(tr("Add a new pane containing only a time ruler"));
Chris@66 1286 connect(action, SIGNAL(triggered()), this, SLOT(addPane()));
Chris@66 1287 connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool)));
Chris@2093 1288 m_paneActions.push_back({ action, LayerConfiguration(LayerFactory::TimeRuler) });
Chris@162 1289 m_keyReference->registerShortcut(action);
Chris@66 1290 menu->addAction(action);
Chris@66 1291
Chris@66 1292 menu->addSeparator();
Chris@66 1293
Chris@66 1294 menu = m_layerMenu;
Chris@66 1295
Chris@66 1296 LayerFactory::LayerTypeSet emptyLayerTypes =
Chris@1770 1297 LayerFactory::getInstance()->getValidEmptyLayerTypes();
Chris@66 1298
Chris@66 1299 for (LayerFactory::LayerTypeSet::iterator i = emptyLayerTypes.begin();
Chris@1770 1300 i != emptyLayerTypes.end(); ++i) {
Chris@1770 1301
Chris@1770 1302 QIcon icon;
Chris@1770 1303 QString mainText, tipText, channelText;
Chris@1770 1304 LayerFactory::LayerType type = *i;
Chris@1770 1305 QString name = LayerFactory::getInstance()->getLayerPresentationName(type);
Chris@1770 1306
Chris@1770 1307 icon = il.load(LayerFactory::getInstance()->getLayerIconName(type));
Chris@1770 1308
Chris@1770 1309 mainText = tr("Add New %1 Layer").arg(name);
Chris@1770 1310 tipText = tr("Add a new empty layer of type %1").arg(name);
Chris@1770 1311
Chris@1770 1312 action = new QAction(icon, mainText, this);
Chris@1770 1313 action->setStatusTip(tipText);
Chris@1770 1314
Chris@1770 1315 if (type == LayerFactory::Text) {
Chris@1770 1316 action->setShortcut(tr("T"));
Chris@162 1317 m_keyReference->registerShortcut(action);
Chris@1770 1318 }
Chris@1770 1319
Chris@1770 1320 connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
Chris@1770 1321 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@2093 1322 m_layerActions.push_back({ action, LayerConfiguration(type) });
Chris@1770 1323 menu->addAction(action);
Chris@66 1324 m_rightButtonLayerMenu->addAction(action);
Chris@66 1325 }
Chris@66 1326
Chris@66 1327 m_rightButtonLayerMenu->addSeparator();
Chris@66 1328 menu->addSeparator();
Chris@66 1329
Chris@66 1330 LayerFactory::LayerType backgroundTypes[] = {
Chris@1770 1331 LayerFactory::Waveform,
Chris@1770 1332 LayerFactory::Spectrogram,
Chris@1770 1333 LayerFactory::MelodicRangeSpectrogram,
Chris@1770 1334 LayerFactory::PeakFrequencySpectrogram,
Chris@66 1335 LayerFactory::Spectrum
Chris@66 1336 };
Chris@2022 1337 int backgroundTypeCount = int(sizeof(backgroundTypes) /
Chris@2022 1338 sizeof(backgroundTypes[0]));
Chris@66 1339
Chris@2300 1340 std::vector<ModelId> models;
Chris@224 1341 if (m_document) models = m_document->getTransformInputModels();
Chris@66 1342 bool plural = (models.size() > 1);
Chris@66 1343 if (models.empty()) {
Chris@2300 1344 models.push_back(getMainModelId()); // probably None at this point
Chris@66 1345 }
Chris@66 1346
Chris@2022 1347 for (int i = 0; i < backgroundTypeCount; ++i) {
Chris@66 1348
Chris@231 1349 const int paneMenuType = 0, layerMenuType = 1;
Chris@231 1350
Chris@1770 1351 for (int menuType = paneMenuType; menuType <= layerMenuType; ++menuType) {
Chris@1770 1352
Chris@1770 1353 if (menuType == paneMenuType) menu = m_paneMenu;
Chris@1770 1354 else menu = m_layerMenu;
Chris@1770 1355
Chris@2126 1356 QMenu *submenu = nullptr;
Chris@66 1357
Chris@66 1358 QIcon icon;
Chris@1794 1359 QString mainText, tipText, channelText;
Chris@66 1360 LayerFactory::LayerType type = backgroundTypes[i];
Chris@66 1361 bool mono = true;
Chris@730 1362
Chris@1794 1363 QString shortcutText = shortcutFor(type, menuType == paneMenuType);
Chris@1794 1364
Chris@730 1365 // Avoid warnings/errors with -Wextra because we aren't explicitly
Chris@730 1366 // handling all layer types (-Wall is OK with this because of the
Chris@730 1367 // default but the stricter level insists)
Chris@1264 1368 #ifdef __GNUC__
Chris@730 1369 #pragma GCC diagnostic ignored "-Wswitch-enum"
Chris@1264 1370 #endif
Chris@66 1371
Chris@66 1372 switch (type) {
Chris@66 1373
Chris@66 1374 case LayerFactory::Waveform:
Chris@168 1375 icon = il.load("waveform");
Chris@66 1376 mainText = tr("Add &Waveform");
Chris@231 1377 if (menuType == paneMenuType) {
Chris@66 1378 tipText = tr("Add a new pane showing a waveform view");
Chris@66 1379 } else {
Chris@66 1380 tipText = tr("Add a new layer showing a waveform view");
Chris@66 1381 }
Chris@66 1382 mono = false;
Chris@66 1383 break;
Chris@1770 1384
Chris@66 1385 case LayerFactory::Spectrogram:
Chris@168 1386 icon = il.load("spectrogram");
Chris@161 1387 mainText = tr("Add Spectro&gram");
Chris@231 1388 if (menuType == paneMenuType) {
Chris@90 1389 tipText = tr("Add a new pane showing a spectrogram");
Chris@66 1390 } else {
Chris@90 1391 tipText = tr("Add a new layer showing a spectrogram");
Chris@66 1392 }
Chris@66 1393 break;
Chris@1770 1394
Chris@66 1395 case LayerFactory::MelodicRangeSpectrogram:
Chris@168 1396 icon = il.load("spectrogram");
Chris@66 1397 mainText = tr("Add &Melodic Range Spectrogram");
Chris@231 1398 if (menuType == paneMenuType) {
Chris@90 1399 tipText = tr("Add a new pane showing a spectrogram set up for an overview of note pitches");
Chris@66 1400 } else {
Chris@90 1401 tipText = tr("Add a new layer showing a spectrogram set up for an overview of note pitches");
Chris@66 1402 }
Chris@66 1403 break;
Chris@1770 1404
Chris@66 1405 case LayerFactory::PeakFrequencySpectrogram:
Chris@168 1406 icon = il.load("spectrogram");
Chris@155 1407 mainText = tr("Add Pea&k Frequency Spectrogram");
Chris@231 1408 if (menuType == paneMenuType) {
Chris@66 1409 tipText = tr("Add a new pane showing a spectrogram set up for tracking frequencies");
Chris@66 1410 } else {
Chris@66 1411 tipText = tr("Add a new layer showing a spectrogram set up for tracking frequencies");
Chris@66 1412 }
Chris@66 1413 break;
Chris@66 1414
Chris@66 1415 case LayerFactory::Spectrum:
Chris@168 1416 icon = il.load("spectrum");
Chris@66 1417 mainText = tr("Add Spectr&um");
Chris@231 1418 if (menuType == paneMenuType) {
Chris@66 1419 tipText = tr("Add a new pane showing a frequency spectrum");
Chris@66 1420 } else {
Chris@66 1421 tipText = tr("Add a new layer showing a frequency spectrum");
Chris@66 1422 }
Chris@66 1423 break;
Chris@66 1424
Chris@66 1425 default: break;
Chris@66 1426 }
Chris@66 1427
Chris@2300 1428 std::vector<ModelId> candidateModels = models;
Chris@2300 1429 if (candidateModels.empty()) {
Chris@2300 1430 throw std::logic_error("candidateModels should not be empty");
Chris@2300 1431 }
Chris@66 1432
Chris@2300 1433 for (auto modelId: candidateModels) {
Chris@2300 1434
Chris@2300 1435 auto model = ModelById::get(modelId);
Chris@66 1436
Chris@66 1437 int channels = 0;
Chris@66 1438 if (model) {
Chris@2300 1439 if (auto dtvm = ModelById::getAs<DenseTimeValueModel>
Chris@2300 1440 (modelId)) {
Chris@2300 1441 channels = dtvm->getChannelCount();
Chris@2300 1442 }
Chris@66 1443 }
Chris@66 1444 if (channels < 1 && getMainModel()) {
Chris@66 1445 channels = getMainModel()->getChannelCount();
Chris@66 1446 }
Chris@66 1447 if (channels < 1) channels = 1;
Chris@66 1448
Chris@66 1449 for (int c = 0; c <= channels; ++c) {
Chris@66 1450
Chris@66 1451 if (c == 1 && channels == 1) continue;
Chris@66 1452 bool isDefault = (c == 0);
Chris@66 1453 bool isOnly = (isDefault && (channels == 1));
Chris@66 1454
Chris@346 1455 if (isOnly && !plural) {
Chris@346 1456
Chris@346 1457 action = new QAction(icon, mainText, this);
Chris@67 1458
Chris@66 1459 action->setShortcut(shortcutText);
Chris@66 1460 action->setStatusTip(tipText);
Chris@231 1461 if (menuType == paneMenuType) {
Chris@66 1462 connect(action, SIGNAL(triggered()),
Chris@66 1463 this, SLOT(addPane()));
Chris@66 1464 connect(this, SIGNAL(canAddPane(bool)),
Chris@66 1465 action, SLOT(setEnabled(bool)));
Chris@2093 1466 m_paneActions.push_back
Chris@2300 1467 ({ action, LayerConfiguration(type, modelId) });
Chris@66 1468 } else {
Chris@66 1469 connect(action, SIGNAL(triggered()),
Chris@66 1470 this, SLOT(addLayer()));
Chris@66 1471 connect(this, SIGNAL(canAddLayer(bool)),
Chris@66 1472 action, SLOT(setEnabled(bool)));
Chris@2093 1473 m_layerActions.push_back
Chris@2300 1474 ({ action, LayerConfiguration(type, modelId) });
Chris@66 1475 }
Chris@162 1476 if (shortcutText != "") {
Chris@162 1477 m_keyReference->registerShortcut(action);
Chris@162 1478 }
Chris@66 1479 menu->addAction(action);
Chris@66 1480
Chris@66 1481 } else {
Chris@66 1482
Chris@66 1483 if (!submenu) {
Chris@66 1484 submenu = menu->addMenu(mainText);
Chris@97 1485 submenu->setTearOffEnabled(true);
Chris@67 1486 } else if (isDefault) {
Chris@67 1487 submenu->addSeparator();
Chris@66 1488 }
Chris@66 1489
Chris@66 1490 QString actionText;
Chris@66 1491 if (c == 0) {
Chris@66 1492 if (mono) {
Chris@66 1493 actionText = tr("&All Channels Mixed");
Chris@66 1494 } else {
Chris@66 1495 actionText = tr("&All Channels");
Chris@66 1496 }
Chris@66 1497 } else {
Chris@66 1498 actionText = tr("Channel &%1").arg(c);
Chris@66 1499 }
Chris@66 1500
Chris@66 1501 if (model) {
Chris@66 1502 actionText = tr("%1: %2")
Chris@66 1503 .arg(model->objectName())
Chris@66 1504 .arg(actionText);
Chris@66 1505 }
Chris@67 1506
Chris@67 1507 if (isDefault) {
Chris@67 1508 action = new QAction(icon, actionText, this);
Chris@2300 1509 if (!model || modelId == getMainModelId()) {
Chris@2093 1510 // Default for the shortcut is to
Chris@2093 1511 // attach to an action that uses the
Chris@2093 1512 // main model as input. But this may
Chris@2093 1513 // change when the user selects a
Chris@2093 1514 // different pane - see
Chris@2093 1515 // updateLayerShortcutsFor() below.
Chris@162 1516 action->setShortcut(shortcutText);
Chris@67 1517 }
Chris@67 1518 } else {
Chris@67 1519 action = new QAction(actionText, this);
Chris@67 1520 }
Chris@67 1521
Chris@66 1522 action->setStatusTip(tipText);
Chris@66 1523
Chris@231 1524 if (menuType == paneMenuType) {
Chris@66 1525 connect(action, SIGNAL(triggered()),
Chris@66 1526 this, SLOT(addPane()));
Chris@66 1527 connect(this, SIGNAL(canAddPane(bool)),
Chris@66 1528 action, SLOT(setEnabled(bool)));
Chris@2093 1529 m_paneActions.push_back
Chris@2300 1530 ({ action, LayerConfiguration(type, modelId, c - 1) });
Chris@66 1531 } else {
Chris@66 1532 connect(action, SIGNAL(triggered()),
Chris@66 1533 this, SLOT(addLayer()));
Chris@66 1534 connect(this, SIGNAL(canAddLayer(bool)),
Chris@66 1535 action, SLOT(setEnabled(bool)));
Chris@2093 1536 m_layerActions.push_back
Chris@2300 1537 ({ action, LayerConfiguration(type, modelId, c - 1) });
Chris@66 1538 }
Chris@66 1539
Chris@66 1540 submenu->addAction(action);
Chris@66 1541 }
Chris@346 1542
Chris@415 1543 if (isDefault && menuType == layerMenuType &&
Chris@2300 1544 modelId == *candidateModels.begin()) {
Chris@415 1545 // only add for one model, one channel, one menu on
Chris@415 1546 // right button -- the action itself will discover
Chris@415 1547 // which model is the correct one (based on pane)
Chris@346 1548 action = new QAction(icon, mainText, this);
Chris@346 1549 action->setStatusTip(tipText);
Chris@346 1550 connect(action, SIGNAL(triggered()),
Chris@346 1551 this, SLOT(addLayer()));
Chris@346 1552 connect(this, SIGNAL(canAddLayer(bool)),
Chris@346 1553 action, SLOT(setEnabled(bool)));
Chris@2093 1554 m_layerActions.push_back
Chris@2300 1555 ({ action, LayerConfiguration(type, ModelId(), 0) });
Chris@346 1556 m_rightButtonLayerMenu->addAction(action);
Chris@346 1557 }
Chris@1770 1558 }
Chris@1770 1559 }
Chris@1770 1560 }
Chris@66 1561 }
Chris@66 1562
Chris@347 1563 m_rightButtonLayerMenu->addSeparator();
Chris@347 1564
Chris@66 1565 menu = m_paneMenu;
Chris@225 1566 menu->addSeparator();
Chris@225 1567
Chris@225 1568 action = new QAction(tr("Switch to Previous Pane"), this);
Chris@225 1569 action->setShortcut(tr("["));
Chris@225 1570 action->setStatusTip(tr("Make the next pane up in the pane stack current"));
Chris@225 1571 connect(action, SIGNAL(triggered()), this, SLOT(previousPane()));
Chris@225 1572 connect(this, SIGNAL(canSelectPreviousPane(bool)), action, SLOT(setEnabled(bool)));
Chris@225 1573 m_keyReference->registerShortcut(action);
Chris@225 1574 menu->addAction(action);
Chris@225 1575
Chris@225 1576 action = new QAction(tr("Switch to Next Pane"), this);
Chris@225 1577 action->setShortcut(tr("]"));
Chris@225 1578 action->setStatusTip(tr("Make the next pane down in the pane stack current"));
Chris@225 1579 connect(action, SIGNAL(triggered()), this, SLOT(nextPane()));
Chris@225 1580 connect(this, SIGNAL(canSelectNextPane(bool)), action, SLOT(setEnabled(bool)));
Chris@225 1581 m_keyReference->registerShortcut(action);
Chris@225 1582 menu->addAction(action);
Chris@66 1583
Chris@66 1584 menu->addSeparator();
Chris@66 1585
Chris@168 1586 action = new QAction(il.load("editdelete"), tr("&Delete Pane"), this);
Chris@155 1587 action->setShortcut(tr("Ctrl+Shift+D"));
Chris@90 1588 action->setStatusTip(tr("Delete the currently active pane"));
Chris@66 1589 connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentPane()));
Chris@66 1590 connect(this, SIGNAL(canDeleteCurrentPane(bool)), action, SLOT(setEnabled(bool)));
Chris@162 1591 m_keyReference->registerShortcut(action);
Chris@66 1592 menu->addAction(action);
Chris@66 1593
Chris@66 1594 menu = m_layerMenu;
Chris@66 1595
Chris@168 1596 action = new QAction(il.load("timeruler"), tr("Add &Time Ruler"), this);
Chris@66 1597 action->setStatusTip(tr("Add a new layer showing a time ruler"));
Chris@66 1598 connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
Chris@66 1599 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@2093 1600 m_layerActions.push_back({ action, LayerConfiguration(LayerFactory::TimeRuler) });
Chris@66 1601 menu->addAction(action);
Chris@66 1602
Chris@66 1603 menu->addSeparator();
Chris@66 1604
Chris@66 1605 m_existingLayersMenu = menu->addMenu(tr("Add &Existing Layer"));
Chris@97 1606 m_existingLayersMenu->setTearOffEnabled(true);
Chris@66 1607 m_rightButtonLayerMenu->addMenu(m_existingLayersMenu);
Chris@95 1608
Chris@95 1609 m_sliceMenu = menu->addMenu(tr("Add S&lice of Layer"));
Chris@97 1610 m_sliceMenu->setTearOffEnabled(true);
Chris@95 1611 m_rightButtonLayerMenu->addMenu(m_sliceMenu);
Chris@95 1612
Chris@95 1613 setupExistingLayersMenus();
Chris@66 1614
Chris@225 1615 menu->addSeparator();
Chris@225 1616
Chris@225 1617 action = new QAction(tr("Switch to Previous Layer"), this);
Chris@225 1618 action->setShortcut(tr("{"));
Chris@225 1619 action->setStatusTip(tr("Make the previous layer in the pane current"));
Chris@225 1620 connect(action, SIGNAL(triggered()), this, SLOT(previousLayer()));
Chris@225 1621 connect(this, SIGNAL(canSelectPreviousLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@225 1622 m_keyReference->registerShortcut(action);
Chris@225 1623 menu->addAction(action);
Chris@225 1624
Chris@225 1625 action = new QAction(tr("Switch to Next Layer"), this);
Chris@225 1626 action->setShortcut(tr("}"));
Chris@225 1627 action->setStatusTip(tr("Make the next layer in the pane current"));
Chris@225 1628 connect(action, SIGNAL(triggered()), this, SLOT(nextLayer()));
Chris@225 1629 connect(this, SIGNAL(canSelectNextLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@225 1630 m_keyReference->registerShortcut(action);
Chris@225 1631 menu->addAction(action);
Chris@785 1632
Chris@66 1633 m_rightButtonLayerMenu->addSeparator();
Chris@66 1634 menu->addSeparator();
Chris@66 1635
Chris@163 1636 QAction *raction = new QAction(tr("&Rename Layer..."), this);
Chris@163 1637 raction->setShortcut(tr("R"));
Chris@163 1638 raction->setStatusTip(tr("Rename the currently active layer"));
Chris@163 1639 connect(raction, SIGNAL(triggered()), this, SLOT(renameCurrentLayer()));
Chris@163 1640 connect(this, SIGNAL(canRenameLayer(bool)), raction, SLOT(setEnabled(bool)));
Chris@163 1641 menu->addAction(raction);
Chris@163 1642 m_rightButtonLayerMenu->addAction(raction);
Chris@66 1643
Chris@258 1644 QAction *eaction = new QAction(tr("Edit Layer Data"), this);
Chris@258 1645 eaction->setShortcut(tr("E"));
Chris@258 1646 eaction->setStatusTip(tr("Edit the currently active layer as a data grid"));
Chris@258 1647 connect(eaction, SIGNAL(triggered()), this, SLOT(editCurrentLayer()));
Chris@291 1648 connect(this, SIGNAL(canEditLayerTabular(bool)), eaction, SLOT(setEnabled(bool)));
Chris@258 1649 menu->addAction(eaction);
Chris@258 1650 m_rightButtonLayerMenu->addAction(eaction);
Chris@258 1651
Chris@168 1652 action = new QAction(il.load("editdelete"), tr("&Delete Layer"), this);
Chris@155 1653 action->setShortcut(tr("Ctrl+D"));
Chris@66 1654 action->setStatusTip(tr("Delete the currently active layer"));
Chris@66 1655 connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentLayer()));
Chris@66 1656 connect(this, SIGNAL(canDeleteCurrentLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@162 1657 m_keyReference->registerShortcut(action);
Chris@66 1658 menu->addAction(action);
Chris@66 1659 m_rightButtonLayerMenu->addAction(action);
Chris@163 1660
Chris@163 1661 m_keyReference->registerShortcut(raction); // rename after delete, so delete layer goes next to delete pane
Chris@258 1662 m_keyReference->registerShortcut(eaction); // edit also after delete
Chris@755 1663
Chris@755 1664 finaliseMenus();
Chris@66 1665 }
Chris@66 1666
Chris@66 1667 void
Chris@2300 1668 MainWindow::updateLayerShortcutsFor(ModelId modelId)
Chris@1794 1669 {
Chris@2093 1670 // Called when e.g. the current pane has changed, to ensure the
Chris@2093 1671 // various layer shortcuts select an action whose input model is
Chris@2093 1672 // the active one in this pane
Chris@2093 1673
Chris@1794 1674 set<LayerFactory::LayerType> seen;
Chris@1794 1675
Chris@1794 1676 for (auto &a : m_paneActions) {
Chris@2300 1677 if (a.second.sourceModel.isNone()) {
Chris@2300 1678 continue; // empty pane/layer shortcut
Chris@2300 1679 }
Chris@1794 1680 auto type = a.second.layer;
Chris@2300 1681 if (a.second.sourceModel == modelId && seen.find(type) == seen.end()) {
Chris@1794 1682 a.first->setShortcut(shortcutFor(type, true));
Chris@1794 1683 seen.insert(type);
Chris@1794 1684 } else {
Chris@1794 1685 a.first->setShortcut(QString());
Chris@1794 1686 }
Chris@1794 1687 }
Chris@1794 1688
Chris@1794 1689 seen.clear();
Chris@1794 1690
Chris@1794 1691 for (auto &a : m_layerActions) {
Chris@2300 1692 if (a.second.sourceModel.isNone()) {
Chris@2300 1693 continue; // empty pane/layer shortcut
Chris@2300 1694 }
Chris@1794 1695 auto type = a.second.layer;
Chris@2300 1696 if (a.second.sourceModel == modelId && seen.find(type) == seen.end()) {
Chris@1794 1697 a.first->setShortcut(shortcutFor(type, false));
Chris@1794 1698 seen.insert(type);
Chris@1794 1699 } else {
Chris@1794 1700 a.first->setShortcut(QString());
Chris@1794 1701 }
Chris@1794 1702 }
Chris@1794 1703 }
Chris@1794 1704
Chris@1794 1705 void
Chris@2550 1706 MainWindow::prepareTransformsMenu()
Chris@66 1707 {
Chris@2550 1708 SVDEBUG << "MainWindow::prepareTransformsMenu" << endl;
Chris@2550 1709
Chris@2550 1710 if (m_transformsMenu) {
Chris@2550 1711 return;
Chris@2550 1712 }
Chris@2550 1713
Chris@2550 1714 m_transformsMenu = menuBar()->addMenu(tr("&Transform"));
Chris@2550 1715 m_transformsMenu->setTearOffEnabled(true);
Chris@2550 1716 m_transformsMenu->setSeparatorsCollapsible(true);
Chris@2550 1717
Chris@2563 1718 auto pending = m_transformsMenu->addAction(tr("Scanning plugins..."));
Chris@2563 1719 pending->setEnabled(false);
Chris@2563 1720
Chris@2550 1721 SVDEBUG << "MainWindow::prepareTransformsMenu: Starting installed-transform population thread" << endl;
Chris@2550 1722 m_transformPopulater = new TransformPopulater(this);
Chris@2550 1723 m_transformPopulater->start();
Chris@2550 1724 }
Chris@2550 1725
Chris@2550 1726 void
Chris@2550 1727 MainWindow::TransformPopulater::run()
Chris@2550 1728 {
Chris@2550 1729 usleep(200000);
Chris@2550 1730
Chris@2550 1731 TransformFactory *tf = TransformFactory::getInstance();
Chris@2550 1732 if (!tf) return;
Chris@2550 1733
Chris@2550 1734 connect(tf, SIGNAL(transformsPopulated()),
Chris@2550 1735 m_mw, SLOT(populateTransformsMenu()));
Chris@2550 1736
Chris@2550 1737 SVDEBUG << "MainWindow::TransformPopulater::run: scanning" << endl;
Chris@2550 1738
Chris@2550 1739 PluginScan::getInstance()->scan();
Chris@2550 1740
Chris@2550 1741 SVDEBUG << "MainWindow::TransformPopulater::run: populating" << endl;
Chris@2550 1742
Chris@2550 1743 (void)tf->haveTransform({}); // populate!
Chris@2550 1744
Chris@2550 1745 SVDEBUG << "MainWindow::TransformPopulater::run: done" << endl;
Chris@2550 1746 }
Chris@2550 1747
Chris@2550 1748 void
Chris@2550 1749 MainWindow::populateTransformsMenu()
Chris@2550 1750 {
Chris@2550 1751 SVDEBUG << "MainWindow::populateTransformsMenu" << endl;
Chris@2550 1752
Chris@34 1753 if (m_transformsMenu) {
Chris@2538 1754 m_transformsMenu->clear();
Chris@2550 1755 m_rightButtonTransformsMenu->clear();
Chris@2538 1756 m_transformActionsReverse.clear();
Chris@34 1757 m_transformActions.clear();
Chris@2538 1758 for (auto a: m_transformActions) {
Chris@2538 1759 delete a.first;
Chris@2538 1760 }
Chris@34 1761 } else {
Chris@1770 1762 m_transformsMenu = menuBar()->addMenu(tr("&Transform"));
Chris@97 1763 m_transformsMenu->setTearOffEnabled(true);
Chris@286 1764 m_transformsMenu->setSeparatorsCollapsible(true);
Chris@272 1765 }
Chris@34 1766
Chris@288 1767 TransformFactory *factory = TransformFactory::getInstance();
Chris@288 1768
Chris@288 1769 TransformList transforms = factory->getAllTransformDescriptions();
Chris@2593 1770
Chris@2593 1771 // We have two possible sources of error here: plugin scan
Chris@2593 1772 // warnings, and transform factory startup errors.
Chris@2593 1773 //
Chris@2593 1774 // In the Piper world, a plugin scan warning is typically
Chris@2593 1775 // non-fatal (it means a plugin is being ignored) but a transform
Chris@2593 1776 // factory startup error is fatal (it means no plugins can be used
Chris@2593 1777 // at all, or at least no feature extraction plugins, and probably
Chris@2593 1778 // indicates an installation problem with SV itself rather than
Chris@2593 1779 // with a plugin).
Chris@2593 1780 //
Chris@2593 1781 // If we have both types of error text, we should either show both
Chris@2593 1782 // (which we don't do as we haven't designed a way to do that
Chris@2593 1783 // tidily) or else only show the transform factory one.
Chris@2593 1784 //
Chris@2593 1785 QString warning = factory->getStartupFailureReport();
Chris@2593 1786 if (warning != "") {
Chris@2593 1787 SVDEBUG << "MainWindow::populateTransformsMenu: Transform population yielded errors" << endl;
Chris@2593 1788 pluginPopulationWarning(warning);
Chris@2593 1789 } else {
Chris@2593 1790 warning = PluginScan::getInstance()->getStartupFailureReport();
Chris@2593 1791 if (warning != "") {
Chris@2593 1792 SVDEBUG << "MainWindow::populateTransformsMenu: Plugin scan yielded errors" << endl;
Chris@2593 1793 pluginPopulationWarning(warning);
Chris@2593 1794 }
Chris@1277 1795 }
Chris@1277 1796
Chris@288 1797 vector<TransformDescription::Type> types = factory->getAllTransformTypes();
Chris@288 1798
Chris@288 1799 map<TransformDescription::Type, map<QString, SubdividingMenu *> > categoryMenus;
Chris@288 1800 map<TransformDescription::Type, map<QString, SubdividingMenu *> > makerMenus;
Chris@288 1801
Chris@288 1802 map<TransformDescription::Type, SubdividingMenu *> byPluginNameMenus;
Chris@288 1803 map<TransformDescription::Type, map<QString, QMenu *> > pluginNameMenus;
Chris@33 1804
Chris@37 1805 set<SubdividingMenu *> pendingMenus;
Chris@37 1806
Chris@211 1807 m_recentTransformsMenu = m_transformsMenu->addMenu(tr("&Recent Transforms"));
Chris@211 1808 m_recentTransformsMenu->setTearOffEnabled(true);
Chris@211 1809 m_rightButtonTransformsMenu->addMenu(m_recentTransformsMenu);
Chris@211 1810 connect(&m_recentTransforms, SIGNAL(recentChanged()),
Chris@211 1811 this, SLOT(setupRecentTransformsMenu()));
Chris@34 1812
Chris@34 1813 m_transformsMenu->addSeparator();
Chris@211 1814 m_rightButtonTransformsMenu->addSeparator();
Chris@34 1815
Chris@288 1816 for (vector<TransformDescription::Type>::iterator i = types.begin();
Chris@288 1817 i != types.end(); ++i) {
Chris@33 1818
Chris@33 1819 if (i != types.begin()) {
Chris@34 1820 m_transformsMenu->addSeparator();
Chris@211 1821 m_rightButtonTransformsMenu->addSeparator();
Chris@33 1822 }
Chris@33 1823
Chris@288 1824 QString byCategoryLabel = tr("%1 by Category")
Chris@288 1825 .arg(factory->getTransformTypeName(*i));
Chris@37 1826 SubdividingMenu *byCategoryMenu = new SubdividingMenu(byCategoryLabel,
Chris@37 1827 20, 40);
Chris@97 1828 byCategoryMenu->setTearOffEnabled(true);
Chris@37 1829 m_transformsMenu->addMenu(byCategoryMenu);
Chris@211 1830 m_rightButtonTransformsMenu->addMenu(byCategoryMenu);
Chris@37 1831 pendingMenus.insert(byCategoryMenu);
Chris@33 1832
Chris@288 1833 vector<QString> categories = factory->getTransformCategories(*i);
Chris@33 1834
Chris@33 1835 for (vector<QString>::iterator j = categories.begin();
Chris@33 1836 j != categories.end(); ++j) {
Chris@33 1837
Chris@33 1838 QString category = *j;
Chris@33 1839 if (category == "") category = tr("Unclassified");
Chris@33 1840
Chris@33 1841 if (categories.size() < 2) {
Chris@33 1842 categoryMenus[*i][category] = byCategoryMenu;
Chris@33 1843 continue;
Chris@33 1844 }
Chris@33 1845
Chris@33 1846 QStringList components = category.split(" > ");
Chris@33 1847 QString key;
Chris@33 1848
Chris@33 1849 for (QStringList::iterator k = components.begin();
Chris@33 1850 k != components.end(); ++k) {
Chris@33 1851
Chris@33 1852 QString parentKey = key;
Chris@33 1853 if (key != "") key += " > ";
Chris@33 1854 key += *k;
Chris@33 1855
Chris@33 1856 if (categoryMenus[*i].find(key) == categoryMenus[*i].end()) {
Chris@37 1857 SubdividingMenu *m = new SubdividingMenu(*k, 20, 40);
Chris@97 1858 m->setTearOffEnabled(true);
Chris@37 1859 pendingMenus.insert(m);
Chris@37 1860 categoryMenus[*i][key] = m;
Chris@33 1861 if (parentKey == "") {
Chris@37 1862 byCategoryMenu->addMenu(m);
Chris@33 1863 } else {
Chris@37 1864 categoryMenus[*i][parentKey]->addMenu(m);
Chris@33 1865 }
Chris@33 1866 }
Chris@33 1867 }
Chris@33 1868 }
Chris@33 1869
Chris@288 1870 QString byPluginNameLabel = tr("%1 by Plugin Name")
Chris@288 1871 .arg(factory->getTransformTypeName(*i));
Chris@36 1872 byPluginNameMenus[*i] = new SubdividingMenu(byPluginNameLabel);
Chris@97 1873 byPluginNameMenus[*i]->setTearOffEnabled(true);
Chris@36 1874 m_transformsMenu->addMenu(byPluginNameMenus[*i]);
Chris@211 1875 m_rightButtonTransformsMenu->addMenu(byPluginNameMenus[*i]);
Chris@37 1876 pendingMenus.insert(byPluginNameMenus[*i]);
Chris@34 1877
Chris@288 1878 QString byMakerLabel = tr("%1 by Maker")
Chris@288 1879 .arg(factory->getTransformTypeName(*i));
Chris@37 1880 SubdividingMenu *byMakerMenu = new SubdividingMenu(byMakerLabel, 20, 40);
Chris@97 1881 byMakerMenu->setTearOffEnabled(true);
Chris@37 1882 m_transformsMenu->addMenu(byMakerMenu);
Chris@211 1883 m_rightButtonTransformsMenu->addMenu(byMakerMenu);
Chris@37 1884 pendingMenus.insert(byMakerMenu);
Chris@33 1885
Chris@288 1886 vector<QString> makers = factory->getTransformMakers(*i);
Chris@37 1887
Chris@33 1888 for (vector<QString>::iterator j = makers.begin();
Chris@33 1889 j != makers.end(); ++j) {
Chris@33 1890
Chris@33 1891 QString maker = *j;
Chris@33 1892 if (maker == "") maker = tr("Unknown");
Chris@55 1893 maker.replace(QRegExp(tr(" [\\(<].*$")), "");
Chris@55 1894
Chris@37 1895 makerMenus[*i][maker] = new SubdividingMenu(maker, 30, 40);
Chris@97 1896 makerMenus[*i][maker]->setTearOffEnabled(true);
Chris@37 1897 byMakerMenu->addMenu(makerMenus[*i][maker]);
Chris@37 1898 pendingMenus.insert(makerMenus[*i][maker]);
Chris@33 1899 }
Chris@0 1900 }
Chris@0 1901
Chris@230 1902 // Names should only be duplicated here if they have the same
Chris@230 1903 // plugin name, output name and maker but are in different library
Chris@230 1904 // .so names -- that won't happen often I hope
Chris@230 1905 std::map<QString, QString> idNameSonameMap;
Chris@230 1906 std::set<QString> seenNames, duplicateNames;
Chris@2022 1907 for (int i = 0; in_range_for(transforms, i); ++i) {
Chris@230 1908 QString name = transforms[i].name;
Chris@230 1909 if (seenNames.find(name) != seenNames.end()) {
Chris@230 1910 duplicateNames.insert(name);
Chris@230 1911 } else {
Chris@230 1912 seenNames.insert(name);
Chris@230 1913 }
Chris@230 1914 }
Chris@230 1915
Chris@2093 1916 m_transformActions.clear();
Chris@2093 1917 m_transformActionsReverse.clear();
Chris@2093 1918
Chris@2022 1919 for (int i = 0; in_range_for(transforms, i); ++i) {
Chris@1770 1920
Chris@1770 1921 QString name = transforms[i].name;
Chris@1770 1922 if (name == "") name = transforms[i].identifier;
Chris@107 1923
Chris@665 1924 // cerr << "Plugin Name: " << name << endl;
Chris@80 1925
Chris@288 1926 TransformDescription::Type type = transforms[i].type;
Chris@288 1927 QString typeStr = factory->getTransformTypeName(type);
Chris@33 1928
Chris@33 1929 QString category = transforms[i].category;
Chris@33 1930 if (category == "") category = tr("Unclassified");
Chris@33 1931
Chris@33 1932 QString maker = transforms[i].maker;
Chris@33 1933 if (maker == "") maker = tr("Unknown");
Chris@55 1934 maker.replace(QRegExp(tr(" [\\(<].*$")), "");
Chris@33 1935
Chris@107 1936 QString pluginName = name.section(": ", 0, 0);
Chris@107 1937 QString output = name.section(": ", 1);
Chris@107 1938
Chris@230 1939 if (duplicateNames.find(pluginName) != duplicateNames.end()) {
Chris@230 1940 pluginName = QString("%1 <%2>")
Chris@230 1941 .arg(pluginName)
Chris@230 1942 .arg(transforms[i].identifier.section(':', 1, 1));
Chris@230 1943 if (output == "") name = pluginName;
Chris@230 1944 else name = QString("%1: %2")
Chris@230 1945 .arg(pluginName)
Chris@230 1946 .arg(output);
Chris@230 1947 }
Chris@230 1948
Chris@1770 1949 QAction *action = new QAction(tr("%1...").arg(name), this);
Chris@1770 1950 connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
Chris@2093 1951 m_transformActions.push_back({ action, transforms[i].identifier });
Chris@107 1952 m_transformActionsReverse[transforms[i].identifier] = action;
Chris@1770 1953 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@33 1954
Chris@272 1955 action->setStatusTip(transforms[i].longDescription);
Chris@90 1956
Chris@33 1957 if (categoryMenus[type].find(category) == categoryMenus[type].end()) {
Chris@665 1958 cerr << "WARNING: MainWindow::setupMenus: Internal error: "
Chris@33 1959 << "No category menu for transform \""
Chris@432 1960 << name << "\" (category = \""
Chris@665 1961 << category << "\")" << endl;
Chris@33 1962 } else {
Chris@33 1963 categoryMenus[type][category]->addAction(action);
Chris@33 1964 }
Chris@33 1965
Chris@33 1966 if (makerMenus[type].find(maker) == makerMenus[type].end()) {
Chris@665 1967 cerr << "WARNING: MainWindow::setupMenus: Internal error: "
Chris@33 1968 << "No maker menu for transform \""
Chris@432 1969 << name << "\" (maker = \""
Chris@665 1970 << maker << "\")" << endl;
Chris@33 1971 } else {
Chris@80 1972 makerMenus[type][maker]->addAction(action);
Chris@33 1973 }
Chris@33 1974
Chris@33 1975 action = new QAction(tr("%1...").arg(output == "" ? pluginName : output), this);
Chris@33 1976 connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
Chris@2093 1977 m_transformActions.push_back({ action, transforms[i].identifier });
Chris@33 1978 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@272 1979 action->setStatusTip(transforms[i].longDescription);
Chris@33 1980
Chris@432 1981 // cerr << "Transform: \"" << name << "\": plugin name \"" << pluginName << "\"" << endl;
Chris@34 1982
Chris@33 1983 if (pluginNameMenus[type].find(pluginName) ==
Chris@33 1984 pluginNameMenus[type].end()) {
Chris@33 1985
Chris@36 1986 SubdividingMenu *parentMenu = byPluginNameMenus[type];
Chris@97 1987 parentMenu->setTearOffEnabled(true);
Chris@34 1988
Chris@33 1989 if (output == "") {
Chris@36 1990 parentMenu->addAction(pluginName, action);
Chris@33 1991 } else {
Chris@34 1992 pluginNameMenus[type][pluginName] =
Chris@34 1993 parentMenu->addMenu(pluginName);
Chris@33 1994 connect(this, SIGNAL(canAddLayer(bool)),
Chris@33 1995 pluginNameMenus[type][pluginName],
Chris@33 1996 SLOT(setEnabled(bool)));
Chris@33 1997 }
Chris@33 1998 }
Chris@33 1999
Chris@33 2000 if (pluginNameMenus[type].find(pluginName) !=
Chris@33 2001 pluginNameMenus[type].end()) {
Chris@33 2002 pluginNameMenus[type][pluginName]->addAction(action);
Chris@33 2003 }
Chris@0 2004 }
Chris@0 2005
Chris@37 2006 for (set<SubdividingMenu *>::iterator i = pendingMenus.begin();
Chris@37 2007 i != pendingMenus.end(); ++i) {
Chris@37 2008 (*i)->entriesAdded();
Chris@37 2009 }
Chris@37 2010
Chris@273 2011 m_transformsMenu->addSeparator();
Chris@273 2012 m_rightButtonTransformsMenu->addSeparator();
Chris@273 2013
Chris@273 2014 QAction *action = new QAction(tr("Find a Transform..."), this);
Chris@273 2015 action->setStatusTip(tr("Search for a transform from the installed plugins, by name or description"));
Chris@275 2016 action->setShortcut(tr("Ctrl+M"));
Chris@273 2017 connect(action, SIGNAL(triggered()), this, SLOT(findTransform()));
Chris@287 2018 // connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@275 2019 m_keyReference->registerShortcut(action);
Chris@273 2020 m_transformsMenu->addAction(action);
Chris@273 2021 m_rightButtonTransformsMenu->addAction(action);
Chris@273 2022
Chris@211 2023 setupRecentTransformsMenu();
Chris@2550 2024
Chris@2550 2025 updateMenuStates();
Chris@66 2026 }
Chris@66 2027
Chris@66 2028 void
Chris@66 2029 MainWindow::setupHelpMenu()
Chris@66 2030 {
Chris@2550 2031 SVDEBUG << "MainWindow::setupHelpMenu" << endl;
Chris@2550 2032
Chris@66 2033 QMenu *menu = menuBar()->addMenu(tr("&Help"));
Chris@97 2034 menu->setTearOffEnabled(true);
Chris@66 2035
Chris@162 2036 m_keyReference->setCategory(tr("Help"));
Chris@162 2037
Chris@168 2038 IconLoader il;
Chris@168 2039
Chris@518 2040 QString name = QApplication::applicationName();
Chris@518 2041
Chris@168 2042 QAction *action = new QAction(il.load("help"),
Chris@138 2043 tr("&Help Reference"), this);
Chris@162 2044 action->setShortcut(tr("F1"));
Chris@518 2045 action->setStatusTip(tr("Open the %1 reference manual").arg(name));
Chris@66 2046 connect(action, SIGNAL(triggered()), this, SLOT(help()));
Chris@162 2047 m_keyReference->registerShortcut(action);
Chris@0 2048 menu->addAction(action);
Chris@162 2049
Chris@163 2050 action = new QAction(tr("&Key and Mouse Reference"), this);
Chris@162 2051 action->setShortcut(tr("F2"));
Chris@518 2052 action->setStatusTip(tr("Open a window showing the keystrokes you can use in %1").arg(name));
Chris@162 2053 connect(action, SIGNAL(triggered()), this, SLOT(keyReference()));
Chris@162 2054 m_keyReference->registerShortcut(action);
Chris@162 2055 menu->addAction(action);
Chris@66 2056
Chris@2038 2057 action = new QAction(tr("What's &New In This Release?"), this);
Chris@2038 2058 action->setStatusTip(tr("List the changes in this release (and every previous release) of %1").arg(name));
Chris@1516 2059 connect(action, SIGNAL(triggered()), this, SLOT(whatsNew()));
Chris@1516 2060 menu->addAction(action);
Chris@1516 2061
Chris@518 2062 action = new QAction(tr("&About %1").arg(name), this);
Chris@518 2063 action->setStatusTip(tr("Show information about %1").arg(name));
Chris@66 2064 connect(action, SIGNAL(triggered()), this, SLOT(about()));
Chris@0 2065 menu->addAction(action);
Chris@0 2066 }
Chris@0 2067
Chris@0 2068 void
Chris@0 2069 MainWindow::setupRecentFilesMenu()
Chris@0 2070 {
Chris@2550 2071 SVDEBUG << "MainWindow::setupRecentFilesMenu" << endl;
Chris@2550 2072
Chris@0 2073 m_recentFilesMenu->clear();
Chris@34 2074 vector<QString> files = m_recentFiles.getRecent();
Chris@0 2075 for (size_t i = 0; i < files.size(); ++i) {
Chris@2149 2076 QString path = files[i];
Chris@2538 2077 QAction *action = m_recentFilesMenu->addAction(path);
Chris@2149 2078 action->setObjectName(path);
Chris@2149 2079 connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile()));
Chris@162 2080 if (i == 0) {
Chris@162 2081 action->setShortcut(tr("Ctrl+R"));
Chris@162 2082 m_keyReference->registerShortcut
Chris@163 2083 (tr("Re-open"),
Chris@528 2084 action->shortcut().toString(),
Chris@163 2085 tr("Re-open the current or most recently opened file"));
Chris@162 2086 }
Chris@0 2087 }
Chris@0 2088 }
Chris@0 2089
Chris@0 2090 void
Chris@423 2091 MainWindow::setupTemplatesMenu()
Chris@423 2092 {
Chris@2550 2093 SVDEBUG << "MainWindow::setupTemplatesMenu" << endl;
Chris@2550 2094
Chris@423 2095 m_templatesMenu->clear();
Chris@423 2096
Chris@2538 2097 QAction *defaultAction = m_templatesMenu->addAction(tr("Standard Waveform"));
Chris@435 2098 defaultAction->setObjectName("default");
Chris@435 2099 connect(defaultAction, SIGNAL(triggered()), this, SLOT(applyTemplate()));
Chris@435 2100
Chris@435 2101 m_templatesMenu->addSeparator();
Chris@435 2102
Chris@2126 2103 QAction *action = nullptr;
Chris@435 2104
Chris@435 2105 QStringList templates = ResourceFinder().getResourceFiles("templates", "svt");
Chris@435 2106
Chris@436 2107 bool havePersonal = false;
Chris@436 2108
Chris@435 2109 // (ordered by name)
Chris@435 2110 std::set<QString> byName;
Chris@435 2111 foreach (QString t, templates) {
Chris@436 2112 if (!t.startsWith(":")) havePersonal = true;
Chris@435 2113 byName.insert(QFileInfo(t).baseName());
Chris@435 2114 }
Chris@435 2115
Chris@435 2116 foreach (QString t, byName) {
Chris@435 2117 if (t.toLower() == "default") continue;
Chris@2538 2118 action = m_templatesMenu->addAction(t);
Chris@435 2119 connect(action, SIGNAL(triggered()), this, SLOT(applyTemplate()));
Chris@435 2120 }
Chris@435 2121
Chris@435 2122 if (!templates.empty()) m_templatesMenu->addSeparator();
Chris@435 2123
Chris@435 2124 if (!m_templateWatcher) {
Chris@435 2125 m_templateWatcher = new QFileSystemWatcher(this);
Chris@435 2126 m_templateWatcher->addPath(ResourceFinder().getResourceSaveDir("templates"));
Chris@435 2127 connect(m_templateWatcher, SIGNAL(directoryChanged(const QString &)),
Chris@435 2128 this, SLOT(setupTemplatesMenu()));
Chris@435 2129 }
Chris@436 2130
Chris@2538 2131 m_templatesMenu->addSeparator();
Chris@2538 2132
Chris@2538 2133 QAction *setDefaultAction = m_templatesMenu->addAction(tr("Choose Default Template..."));
Chris@436 2134 setDefaultAction->setObjectName("set_default_template");
Chris@436 2135 connect(setDefaultAction, SIGNAL(triggered()), this, SLOT(preferences()));
Chris@436 2136
Chris@436 2137 m_manageTemplatesAction->setEnabled(havePersonal);
Chris@435 2138 }
Chris@435 2139
Chris@423 2140
Chris@423 2141 void
Chris@211 2142 MainWindow::setupRecentTransformsMenu()
Chris@34 2143 {
Chris@2550 2144 SVDEBUG << "MainWindow::setupRecentTransformsMenu" << endl;
Chris@2550 2145
Chris@211 2146 m_recentTransformsMenu->clear();
Chris@211 2147 vector<QString> transforms = m_recentTransforms.getRecent();
Chris@34 2148 for (size_t i = 0; i < transforms.size(); ++i) {
Chris@211 2149 TransformActionReverseMap::iterator ti =
Chris@34 2150 m_transformActionsReverse.find(transforms[i]);
Chris@34 2151 if (ti == m_transformActionsReverse.end()) {
Chris@665 2152 cerr << "WARNING: MainWindow::setupRecentTransformsMenu: "
Chris@665 2153 << "Unknown transform \"" << transforms[i]
Chris@665 2154 << "\" in recent transforms list" << endl;
Chris@34 2155 continue;
Chris@34 2156 }
Chris@162 2157 if (i == 0) {
Chris@162 2158 ti->second->setShortcut(tr("Ctrl+T"));
Chris@162 2159 m_keyReference->registerShortcut
Chris@211 2160 (tr("Repeat Transform"),
Chris@528 2161 ti->second->shortcut().toString(),
Chris@163 2162 tr("Re-select the most recently run transform"));
Chris@216 2163 } else {
Chris@216 2164 ti->second->setShortcut(QString(""));
Chris@162 2165 }
Chris@1770 2166 m_recentTransformsMenu->addAction(ti->second);
Chris@34 2167 }
Chris@34 2168 }
Chris@34 2169
Chris@34 2170 void
Chris@95 2171 MainWindow::setupExistingLayersMenus()
Chris@0 2172 {
Chris@2550 2173 SVDEBUG << "MainWindow::setupExistingLayersMenus" << endl;
Chris@2550 2174
Chris@0 2175 if (!m_existingLayersMenu) return; // should have been created by setupMenus
Chris@0 2176
Chris@438 2177 // SVDEBUG << "MainWindow::setupExistingLayersMenu" << endl;
Chris@0 2178
Chris@2538 2179 Profiler profiler1("MainWindow::setupExistingLayersMenu");
Chris@2538 2180
Chris@0 2181 m_existingLayersMenu->clear();
Chris@2538 2182 for (auto a: m_existingLayerActions) {
Chris@2538 2183 delete a.first;
Chris@2538 2184 }
Chris@0 2185 m_existingLayerActions.clear();
Chris@0 2186
Chris@95 2187 m_sliceMenu->clear();
Chris@2538 2188 for (auto a: m_sliceActions) {
Chris@2538 2189 delete a.first;
Chris@2538 2190 }
Chris@95 2191 m_sliceActions.clear();
Chris@95 2192
Chris@168 2193 IconLoader il;
Chris@168 2194
Chris@33 2195 vector<Layer *> orderedLayers;
Chris@33 2196 set<Layer *> observedLayers;
Chris@95 2197 set<Layer *> sliceableLayers;
Chris@95 2198
Chris@95 2199 LayerFactory *factory = LayerFactory::getInstance();
Chris@0 2200
Chris@0 2201 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@0 2202
Chris@1770 2203 Pane *pane = m_paneStack->getPane(i);
Chris@1770 2204 if (!pane) continue;
Chris@1770 2205
Chris@1770 2206 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@1770 2207
Chris@1770 2208 Layer *layer = pane->getLayer(j);
Chris@1770 2209 if (!layer) continue;
Chris@1770 2210 if (observedLayers.find(layer) != observedLayers.end()) {
Chris@1770 2211 // cerr << "found duplicate layer " << layer << endl;
Chris@1770 2212 continue;
Chris@1770 2213 }
Chris@1770 2214
Chris@1770 2215 // cerr << "found new layer " << layer << " (name = "
Chris@1770 2216 // << layer->getLayerPresentationName() << ")" << endl;
Chris@1770 2217
Chris@1770 2218 orderedLayers.push_back(layer);
Chris@1770 2219 observedLayers.insert(layer);
Chris@95 2220
Chris@95 2221 if (factory->isLayerSliceable(layer)) {
Chris@95 2222 sliceableLayers.insert(layer);
Chris@95 2223 }
Chris@1770 2224 }
Chris@0 2225 }
Chris@0 2226
Chris@2538 2227 Profiler profiler3("MainWindow::setupExistingLayersMenu: after sorting");
Chris@2538 2228
Chris@33 2229 map<QString, int> observedNames;
Chris@0 2230
Chris@137 2231 for (size_t i = 0; i < orderedLayers.size(); ++i) {
Chris@1770 2232
Chris@95 2233 Layer *layer = orderedLayers[i];
Chris@95 2234
Chris@1770 2235 QString name = layer->getLayerPresentationName();
Chris@1770 2236 int n = ++observedNames[name];
Chris@1770 2237 if (n > 1) name = QString("%1 <%2>").arg(name).arg(n);
Chris@1770 2238
Chris@1770 2239 QIcon icon = il.load(factory->getLayerIconName
Chris@168 2240 (factory->getLayerType(layer)));
Chris@95 2241
Chris@1770 2242 QAction *action = new QAction(icon, name, this);
Chris@1770 2243 connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
Chris@1770 2244 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@2093 2245 m_existingLayerActions.push_back({ action, layer });
Chris@1770 2246
Chris@1770 2247 m_existingLayersMenu->addAction(action);
Chris@95 2248
Chris@95 2249 if (sliceableLayers.find(layer) != sliceableLayers.end()) {
Chris@95 2250 action = new QAction(icon, name, this);
Chris@95 2251 connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
Chris@95 2252 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@2093 2253 m_sliceActions.push_back({ action, layer });
Chris@95 2254 m_sliceMenu->addAction(action);
Chris@95 2255 }
Chris@0 2256 }
Chris@95 2257
Chris@95 2258 m_sliceMenu->setEnabled(!m_sliceActions.empty());
Chris@0 2259 }
Chris@0 2260
Chris@0 2261 void
Chris@0 2262 MainWindow::setupToolbars()
Chris@0 2263 {
Chris@2550 2264 SVDEBUG << "MainWindow::setupToolbars" << endl;
Chris@2550 2265
Chris@162 2266 m_keyReference->setCategory(tr("Playback and Transport Controls"));
Chris@162 2267
Chris@168 2268 IconLoader il;
Chris@168 2269
Chris@155 2270 QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back"));
Chris@155 2271 menu->setTearOffEnabled(true);
Chris@155 2272 m_rightButtonMenu->addSeparator();
Chris@155 2273 m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback"));
Chris@155 2274
Chris@155 2275 QToolBar *toolbar = addToolBar(tr("Playback Toolbar"));
Chris@155 2276
Chris@265 2277 m_rwdStartAction = toolbar->addAction(il.load("rewind-start"),
Chris@265 2278 tr("Rewind to Start"));
Chris@265 2279 m_rwdStartAction->setShortcut(tr("Home"));
Chris@265 2280 m_rwdStartAction->setStatusTip(tr("Rewind to the start"));
Chris@265 2281 connect(m_rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart()));
Chris@265 2282 connect(this, SIGNAL(canPlay(bool)), m_rwdStartAction, SLOT(setEnabled(bool)));
Chris@265 2283
Chris@265 2284 m_rwdAction = toolbar->addAction(il.load("rewind"), tr("Rewind"));
Chris@155 2285 m_rwdAction->setShortcut(tr("PgUp"));
Chris@163 2286 m_rwdAction->setStatusTip(tr("Rewind to the previous time instant or time ruler notch"));
Chris@155 2287 connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind()));
Chris@155 2288 connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool)));
Chris@155 2289
Chris@323 2290 m_rwdSimilarAction = new QAction(tr("Rewind to Similar Point"), this);
Chris@323 2291 m_rwdSimilarAction->setShortcut(tr("Shift+PgUp"));
Chris@323 2292 m_rwdSimilarAction->setStatusTip(tr("Rewind to the previous similarly valued time instant"));
Chris@323 2293 connect(m_rwdSimilarAction, SIGNAL(triggered()), this, SLOT(rewindSimilar()));
Chris@323 2294 connect(this, SIGNAL(canRewind(bool)), m_rwdSimilarAction, SLOT(setEnabled(bool)));
Chris@323 2295
Chris@265 2296 m_playAction = toolbar->addAction(il.load("playpause"),
Chris@265 2297 tr("Play / Pause"));
Chris@265 2298 m_playAction->setCheckable(true);
Chris@1790 2299
Chris@1790 2300 /*: This text is a shortcut label referring to the space-bar on
Chris@1790 2301 the keyboard. It probably should not be translated, and
Chris@1790 2302 certainly should not be translated as if referring to an empty
Chris@1790 2303 void or to the extra-terrestrial universe.
Chris@1790 2304 */
Chris@265 2305 m_playAction->setShortcut(tr("Space"));
Chris@1790 2306
Chris@265 2307 m_playAction->setStatusTip(tr("Start or stop playback from the current position"));
Chris@265 2308 connect(m_playAction, SIGNAL(triggered()), this, SLOT(play()));
Chris@0 2309 connect(m_playSource, SIGNAL(playStatusChanged(bool)),
Chris@1770 2310 m_playAction, SLOT(setChecked(bool)));
Chris@305 2311 connect(m_playSource, SIGNAL(playStatusChanged(bool)),
Chris@305 2312 this, SLOT(playStatusChanged(bool)));
Chris@265 2313 connect(this, SIGNAL(canPlay(bool)), m_playAction, SLOT(setEnabled(bool)));
Chris@155 2314
Chris@168 2315 m_ffwdAction = toolbar->addAction(il.load("ffwd"),
Chris@286 2316 tr("Fast Forward"));
Chris@155 2317 m_ffwdAction->setShortcut(tr("PgDown"));
Chris@163 2318 m_ffwdAction->setStatusTip(tr("Fast-forward to the next time instant or time ruler notch"));
Chris@155 2319 connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd()));
Chris@155 2320 connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool)));
Chris@155 2321
Chris@323 2322 m_ffwdSimilarAction = new QAction(tr("Fast Forward to Similar Point"), this);
Chris@323 2323 m_ffwdSimilarAction->setShortcut(tr("Shift+PgDown"));
Chris@323 2324 m_ffwdSimilarAction->setStatusTip(tr("Fast-forward to the next similarly valued time instant"));
Chris@323 2325 connect(m_ffwdSimilarAction, SIGNAL(triggered()), this, SLOT(ffwdSimilar()));
Chris@323 2326 connect(this, SIGNAL(canFfwd(bool)), m_ffwdSimilarAction, SLOT(setEnabled(bool)));
Chris@323 2327
Chris@265 2328 m_ffwdEndAction = toolbar->addAction(il.load("ffwd-end"),
Chris@265 2329 tr("Fast Forward to End"));
Chris@265 2330 m_ffwdEndAction->setShortcut(tr("End"));
Chris@265 2331 m_ffwdEndAction->setStatusTip(tr("Fast-forward to the end"));
Chris@265 2332 connect(m_ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd()));
Chris@265 2333 connect(this, SIGNAL(canPlay(bool)), m_ffwdEndAction, SLOT(setEnabled(bool)));
Chris@0 2334
Chris@1047 2335 m_recordAction = toolbar->addAction(il.load("record"),
Chris@1047 2336 tr("Record"));
Chris@1047 2337 m_recordAction->setCheckable(true);
Chris@1047 2338 m_recordAction->setShortcut(tr("Ctrl+Space"));
Chris@1047 2339 m_recordAction->setStatusTip(tr("Record a new audio file"));
Chris@1047 2340 connect(m_recordAction, SIGNAL(triggered()), this, SLOT(record()));
Chris@1047 2341 connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)),
Chris@1770 2342 m_recordAction, SLOT(setChecked(bool)));
Chris@1047 2343 connect(this, SIGNAL(canRecord(bool)),
Chris@1047 2344 m_recordAction, SLOT(setEnabled(bool)));
Chris@1047 2345
Chris@0 2346 toolbar = addToolBar(tr("Play Mode Toolbar"));
Chris@0 2347
Chris@265 2348 m_playSelectionAction = toolbar->addAction(il.load("playselection"),
Chris@265 2349 tr("Constrain Playback to Selection"));
Chris@265 2350 m_playSelectionAction->setCheckable(true);
Chris@265 2351 m_playSelectionAction->setChecked(m_viewManager->getPlaySelectionMode());
Chris@265 2352 m_playSelectionAction->setShortcut(tr("s"));
Chris@265 2353 m_playSelectionAction->setStatusTip(tr("Constrain playback to the selected regions"));
Chris@69 2354 connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)),
Chris@265 2355 m_playSelectionAction, SLOT(setChecked(bool)));
Chris@265 2356 connect(m_playSelectionAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled()));
Chris@265 2357 connect(this, SIGNAL(canPlaySelection(bool)), m_playSelectionAction, SLOT(setEnabled(bool)));
Chris@265 2358
Chris@265 2359 m_playLoopAction = toolbar->addAction(il.load("playloop"),
Chris@265 2360 tr("Loop Playback"));
Chris@265 2361 m_playLoopAction->setCheckable(true);
Chris@265 2362 m_playLoopAction->setChecked(m_viewManager->getPlayLoopMode());
Chris@265 2363 m_playLoopAction->setShortcut(tr("l"));
Chris@265 2364 m_playLoopAction->setStatusTip(tr("Loop playback"));
Chris@69 2365 connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)),
Chris@265 2366 m_playLoopAction, SLOT(setChecked(bool)));
Chris@265 2367 connect(m_playLoopAction, SIGNAL(triggered()), this, SLOT(playLoopToggled()));
Chris@265 2368 connect(this, SIGNAL(canPlay(bool)), m_playLoopAction, SLOT(setEnabled(bool)));
Chris@155 2369
Chris@207 2370 m_soloAction = toolbar->addAction(il.load("solo"),
Chris@323 2371 tr("Solo Current Pane"));
Chris@207 2372 m_soloAction->setCheckable(true);
Chris@207 2373 m_soloAction->setChecked(m_viewManager->getPlaySoloMode());
Chris@207 2374 m_prevSolo = m_viewManager->getPlaySoloMode();
Chris@207 2375 m_soloAction->setShortcut(tr("o"));
Chris@207 2376 m_soloAction->setStatusTip(tr("Solo the current pane during playback"));
Chris@180 2377 connect(m_viewManager, SIGNAL(playSoloModeChanged(bool)),
Chris@207 2378 m_soloAction, SLOT(setChecked(bool)));
Chris@207 2379 connect(m_soloAction, SIGNAL(triggered()), this, SLOT(playSoloToggled()));
Chris@207 2380 connect(this, SIGNAL(canChangeSolo(bool)), m_soloAction, SLOT(setEnabled(bool)));
Chris@180 2381
Chris@2550 2382 QAction *alAction = toolbar->addAction(il.load("align"),
Chris@2550 2383 tr("Align File Timelines"));
Chris@2550 2384 alAction->setCheckable(true);
Chris@2550 2385 alAction->setChecked(m_viewManager->getAlignMode());
Chris@2550 2386 alAction->setStatusTip(tr("Treat multiple audio files as versions of the same work, and align their timelines"));
Chris@2550 2387 alAction->setEnabled(false); // until canAlign emitted
Chris@2550 2388 connect(m_viewManager, SIGNAL(alignModeChanged(bool)),
Chris@2550 2389 alAction, SLOT(setChecked(bool)));
Chris@2550 2390 connect(alAction, SIGNAL(triggered()), this, SLOT(alignToggled()));
Chris@2550 2391 connect(this, SIGNAL(canAlign(bool)), alAction, SLOT(setEnabled(bool)));
Chris@206 2392
Chris@265 2393 m_keyReference->registerShortcut(m_playAction);
Chris@1056 2394 m_keyReference->registerShortcut(m_recordAction);
Chris@265 2395 m_keyReference->registerShortcut(m_playSelectionAction);
Chris@265 2396 m_keyReference->registerShortcut(m_playLoopAction);
Chris@207 2397 m_keyReference->registerShortcut(m_soloAction);
Chris@2550 2398 m_keyReference->registerShortcut(alAction);
Chris@162 2399 m_keyReference->registerShortcut(m_rwdAction);
Chris@162 2400 m_keyReference->registerShortcut(m_ffwdAction);
Chris@323 2401 m_keyReference->registerShortcut(m_rwdSimilarAction);
Chris@323 2402 m_keyReference->registerShortcut(m_ffwdSimilarAction);
Chris@265 2403 m_keyReference->registerShortcut(m_rwdStartAction);
Chris@265 2404 m_keyReference->registerShortcut(m_ffwdEndAction);
Chris@265 2405
Chris@265 2406 menu->addAction(m_playAction);
Chris@1056 2407 menu->addAction(m_recordAction);
Chris@265 2408 menu->addAction(m_playSelectionAction);
Chris@265 2409 menu->addAction(m_playLoopAction);
Chris@207 2410 menu->addAction(m_soloAction);
Chris@2550 2411 menu->addAction(alAction);
Chris@155 2412 menu->addSeparator();
Chris@155 2413 menu->addAction(m_rwdAction);
Chris@155 2414 menu->addAction(m_ffwdAction);
Chris@155 2415 menu->addSeparator();
Chris@323 2416 menu->addAction(m_rwdSimilarAction);
Chris@323 2417 menu->addAction(m_ffwdSimilarAction);
Chris@323 2418 menu->addSeparator();
Chris@265 2419 menu->addAction(m_rwdStartAction);
Chris@265 2420 menu->addAction(m_ffwdEndAction);
Chris@155 2421 menu->addSeparator();
Chris@1055 2422 menu->addAction(m_recordAction);
Chris@1055 2423 menu->addSeparator();
Chris@155 2424
Chris@265 2425 m_rightButtonPlaybackMenu->addAction(m_playAction);
Chris@265 2426 m_rightButtonPlaybackMenu->addAction(m_playSelectionAction);
Chris@265 2427 m_rightButtonPlaybackMenu->addAction(m_playLoopAction);
Chris@207 2428 m_rightButtonPlaybackMenu->addAction(m_soloAction);
Chris@208 2429 if (alAction) m_rightButtonPlaybackMenu->addAction(alAction);
Chris@155 2430 m_rightButtonPlaybackMenu->addSeparator();
Chris@155 2431 m_rightButtonPlaybackMenu->addAction(m_rwdAction);
Chris@155 2432 m_rightButtonPlaybackMenu->addAction(m_ffwdAction);
Chris@155 2433 m_rightButtonPlaybackMenu->addSeparator();
Chris@265 2434 m_rightButtonPlaybackMenu->addAction(m_rwdStartAction);
Chris@265 2435 m_rightButtonPlaybackMenu->addAction(m_ffwdEndAction);
Chris@155 2436 m_rightButtonPlaybackMenu->addSeparator();
Chris@1055 2437 m_rightButtonPlaybackMenu->addAction(m_recordAction);
Chris@1055 2438 m_rightButtonPlaybackMenu->addSeparator();
Chris@155 2439
Chris@155 2440 QAction *fastAction = menu->addAction(tr("Speed Up"));
Chris@155 2441 fastAction->setShortcut(tr("Ctrl+PgUp"));
Chris@163 2442 fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch"));
Chris@155 2443 connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback()));
Chris@155 2444 connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool)));
Chris@155 2445
Chris@155 2446 QAction *slowAction = menu->addAction(tr("Slow Down"));
Chris@155 2447 slowAction->setShortcut(tr("Ctrl+PgDown"));
Chris@163 2448 slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch"));
Chris@155 2449 connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback()));
Chris@155 2450 connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool)));
Chris@155 2451
Chris@155 2452 QAction *normalAction = menu->addAction(tr("Restore Normal Speed"));
Chris@155 2453 normalAction->setShortcut(tr("Ctrl+Home"));
Chris@163 2454 normalAction->setStatusTip(tr("Restore non-time-stretched playback"));
Chris@155 2455 connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback()));
Chris@155 2456 connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool)));
Chris@155 2457
Chris@162 2458 m_keyReference->registerShortcut(fastAction);
Chris@162 2459 m_keyReference->registerShortcut(slowAction);
Chris@162 2460 m_keyReference->registerShortcut(normalAction);
Chris@162 2461
Chris@155 2462 m_rightButtonPlaybackMenu->addAction(fastAction);
Chris@155 2463 m_rightButtonPlaybackMenu->addAction(slowAction);
Chris@155 2464 m_rightButtonPlaybackMenu->addAction(normalAction);
Chris@0 2465
Chris@0 2466 toolbar = addToolBar(tr("Edit Toolbar"));
Chris@0 2467 CommandHistory::getInstance()->registerToolbar(toolbar);
Chris@0 2468
Chris@0 2469 toolbar = addToolBar(tr("Tools Toolbar"));
Chris@0 2470 QActionGroup *group = new QActionGroup(this);
Chris@2093 2471 m_toolActions.clear();
Chris@2093 2472
Chris@705 2473 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2474 QAction *action = toolbar->addAction(il.load("navigate"), tr("Navigate"));
Chris@0 2475 action->setCheckable(true);
Chris@0 2476 action->setChecked(true);
Chris@0 2477 action->setShortcut(tr("1"));
Chris@90 2478 action->setStatusTip(tr("Navigate"));
Chris@0 2479 connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected()));
Chris@596 2480 connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger()));
Chris@0 2481 group->addAction(action);
Chris@162 2482 m_keyReference->registerShortcut(action);
Chris@2093 2483 m_toolActions.push_back({ ViewManager::NavigateMode, action });
Chris@705 2484
Chris@705 2485 m_keyReference->setCategory
Chris@705 2486 (tr("Navigate Tool Mouse Actions"));
Chris@705 2487 m_keyReference->registerShortcut
Chris@705 2488 (tr("Navigate"), tr("Left"),
Chris@705 2489 tr("Click left button and drag to move around"));
Chris@705 2490 m_keyReference->registerShortcut
Chris@705 2491 (tr("Zoom to Area"), tr("Shift+Left"),
Chris@705 2492 tr("Shift-click left button and drag to zoom to a rectangular area"));
Chris@705 2493 m_keyReference->registerShortcut
Chris@705 2494 (tr("Relocate"), tr("Double-Click Left"),
Chris@705 2495 tr("Double-click left button to jump to clicked location"));
Chris@705 2496 m_keyReference->registerShortcut
Chris@705 2497 (tr("Edit"), tr("Double-Click Left"),
Chris@705 2498 tr("Double-click left button on an item to edit it"));
Chris@705 2499
Chris@705 2500 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2501 action = toolbar->addAction(il.load("select"), tr("Select"));
Chris@0 2502 action->setCheckable(true);
Chris@0 2503 action->setShortcut(tr("2"));
Chris@90 2504 action->setStatusTip(tr("Select ranges"));
Chris@0 2505 connect(action, SIGNAL(triggered()), this, SLOT(toolSelectSelected()));
Chris@0 2506 group->addAction(action);
Chris@162 2507 m_keyReference->registerShortcut(action);
Chris@2093 2508 m_toolActions.push_back({ ViewManager::SelectMode, action });
Chris@705 2509
Chris@705 2510 m_keyReference->setCategory
Chris@705 2511 (tr("Select Tool Mouse Actions"));
Chris@705 2512 m_keyReference->registerShortcut
Chris@705 2513 (tr("Select"), tr("Left"),
Chris@705 2514 tr("Click left button and drag to select region; drag region edge to resize"));
Chris@705 2515 #ifdef Q_OS_MAC
Chris@705 2516 m_keyReference->registerShortcut
Chris@705 2517 (tr("Multi Select"), tr("Ctrl+Left"),
Chris@705 2518 tr("Cmd-click left button and drag to select an additional region"));
Chris@705 2519 #else
Chris@705 2520 m_keyReference->registerShortcut
Chris@705 2521 (tr("Multi Select"), tr("Ctrl+Left"),
Chris@705 2522 tr("Ctrl-click left button and drag to select an additional region"));
Chris@705 2523 #endif
Chris@705 2524 m_keyReference->registerShortcut
Chris@705 2525 (tr("Fine Select"), tr("Shift+Left"),
Chris@705 2526 tr("Shift-click left button and drag to select without snapping to items or grid"));
Chris@705 2527
Chris@705 2528 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2529 action = toolbar->addAction(il.load("move"), tr("Edit"));
Chris@0 2530 action->setCheckable(true);
Chris@0 2531 action->setShortcut(tr("3"));
Chris@90 2532 action->setStatusTip(tr("Edit items in layer"));
Chris@0 2533 connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected()));
Chris@0 2534 connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@0 2535 group->addAction(action);
Chris@162 2536 m_keyReference->registerShortcut(action);
Chris@2093 2537 m_toolActions.push_back({ ViewManager::EditMode, action });
Chris@705 2538
Chris@705 2539 m_keyReference->setCategory
Chris@705 2540 (tr("Edit Tool Mouse Actions"));
Chris@705 2541 m_keyReference->registerShortcut
Chris@705 2542 (tr("Move"), tr("Left"),
Chris@705 2543 tr("Click left button on an item or selected region and drag to move"));
Chris@705 2544 m_keyReference->registerShortcut
Chris@705 2545 (tr("Edit"), tr("Double-Click Left"),
Chris@705 2546 tr("Double-click left button on an item to edit it"));
Chris@705 2547
Chris@705 2548 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2549 action = toolbar->addAction(il.load("draw"), tr("Draw"));
Chris@0 2550 action->setCheckable(true);
Chris@0 2551 action->setShortcut(tr("4"));
Chris@90 2552 action->setStatusTip(tr("Draw new items in layer"));
Chris@0 2553 connect(action, SIGNAL(triggered()), this, SLOT(toolDrawSelected()));
Chris@0 2554 connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@0 2555 group->addAction(action);
Chris@162 2556 m_keyReference->registerShortcut(action);
Chris@2093 2557 m_toolActions.push_back({ ViewManager::DrawMode, action });
Chris@0 2558
Chris@705 2559 m_keyReference->setCategory
Chris@705 2560 (tr("Draw Tool Mouse Actions"));
Chris@705 2561 m_keyReference->registerShortcut
Chris@705 2562 (tr("Draw"), tr("Left"),
Chris@705 2563 tr("Click left button and drag to create new item"));
Chris@705 2564
Chris@705 2565 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2566 action = toolbar->addAction(il.load("erase"), tr("Erase"));
Chris@217 2567 action->setCheckable(true);
Chris@217 2568 action->setShortcut(tr("5"));
Chris@217 2569 action->setStatusTip(tr("Erase items from layer"));
Chris@217 2570 connect(action, SIGNAL(triggered()), this, SLOT(toolEraseSelected()));
Chris@217 2571 connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@217 2572 group->addAction(action);
Chris@217 2573 m_keyReference->registerShortcut(action);
Chris@2093 2574 m_toolActions.push_back({ ViewManager::EraseMode, action });
Chris@217 2575
Chris@705 2576 m_keyReference->setCategory
Chris@705 2577 (tr("Erase Tool Mouse Actions"));
Chris@705 2578 m_keyReference->registerShortcut
Chris@705 2579 (tr("Erase"), tr("Left"),
Chris@705 2580 tr("Click left button on an item to remove it from the layer"));
Chris@705 2581
Chris@705 2582 m_keyReference->setCategory(tr("Tool Selection"));
Chris@265 2583 action = toolbar->addAction(il.load("measure"), tr("Measure"));
Chris@151 2584 action->setCheckable(true);
Chris@217 2585 action->setShortcut(tr("6"));
Chris@151 2586 action->setStatusTip(tr("Make measurements in layer"));
Chris@151 2587 connect(action, SIGNAL(triggered()), this, SLOT(toolMeasureSelected()));
Chris@169 2588 connect(this, SIGNAL(canMeasureLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@151 2589 group->addAction(action);
Chris@162 2590 m_keyReference->registerShortcut(action);
Chris@2093 2591 m_toolActions.push_back({ ViewManager::MeasureMode, action });
Chris@151 2592
Chris@705 2593 m_keyReference->setCategory
Chris@705 2594 (tr("Measure Tool Mouse Actions"));
Chris@705 2595 m_keyReference->registerShortcut
Chris@705 2596 (tr("Measure Area"), tr("Left"),
Chris@705 2597 tr("Click left button and drag to measure a rectangular area"));
Chris@705 2598 m_keyReference->registerShortcut
Chris@705 2599 (tr("Measure Item"), tr("Double-Click Left"),
Chris@705 2600 tr("Click left button and drag to measure extents of an item or shape"));
Chris@705 2601 m_keyReference->registerShortcut
Chris@705 2602 (tr("Zoom to Area"), tr("Shift+Left"),
Chris@705 2603 tr("Shift-click left button and drag to zoom to a rectangular area"));
Chris@705 2604
Chris@0 2605 toolNavigateSelected();
Chris@163 2606
Chris@163 2607 Pane::registerShortcuts(*m_keyReference);
Chris@0 2608 }
Chris@0 2609
Chris@0 2610 void
Chris@265 2611 MainWindow::connectLayerEditDialog(ModelDataTableDialog *dialog)
Chris@265 2612 {
Chris@265 2613 MainWindowBase::connectLayerEditDialog(dialog);
Chris@265 2614 QToolBar *toolbar = dialog->getPlayToolbar();
Chris@265 2615 if (toolbar) {
Chris@265 2616 toolbar->addAction(m_rwdStartAction);
Chris@265 2617 toolbar->addAction(m_rwdAction);
Chris@265 2618 toolbar->addAction(m_playAction);
Chris@265 2619 toolbar->addAction(m_ffwdAction);
Chris@265 2620 toolbar->addAction(m_ffwdEndAction);
Chris@265 2621 }
Chris@265 2622 }
Chris@265 2623
Chris@265 2624 void
Chris@0 2625 MainWindow::updateMenuStates()
Chris@0 2626 {
Chris@2550 2627 SVDEBUG << "MainWindow::updateMenuStates" << endl;
Chris@2550 2628
Chris@200 2629 MainWindowBase::updateMenuStates();
Chris@200 2630
Chris@2126 2631 Pane *currentPane = nullptr;
Chris@2126 2632 Layer *currentLayer = nullptr;
Chris@117 2633
Chris@117 2634 if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
Chris@117 2635 if (currentPane) currentLayer = currentPane->getSelectedLayer();
Chris@117 2636
Chris@0 2637 bool haveCurrentPane =
Chris@2126 2638 (currentPane != nullptr);
Chris@0 2639 bool haveCurrentLayer =
Chris@117 2640 (haveCurrentPane &&
Chris@2126 2641 (currentLayer != nullptr));
Chris@206 2642 bool havePlayTarget =
Chris@2126 2643 (m_playTarget != nullptr || m_audioIO != nullptr);
Chris@0 2644 bool haveSelection =
Chris@1770 2645 (m_viewManager &&
Chris@1770 2646 !m_viewManager->getSelections().empty());
Chris@0 2647 bool haveCurrentEditableLayer =
Chris@1770 2648 (haveCurrentLayer &&
Chris@1770 2649 currentLayer->isLayerEditable());
Chris@0 2650 bool haveCurrentTimeInstantsLayer =
Chris@1770 2651 (haveCurrentLayer &&
Chris@1770 2652 dynamic_cast<TimeInstantLayer *>(currentLayer));
Chris@0 2653 bool haveCurrentTimeValueLayer =
Chris@1770 2654 (haveCurrentLayer &&
Chris@1770 2655 dynamic_cast<TimeValueLayer *>(currentLayer));
Chris@207 2656
Chris@314 2657 bool alignMode = m_viewManager && m_viewManager->getAlignMode();
Chris@314 2658 emit canChangeSolo(havePlayTarget && !alignMode);
Chris@2550 2659
Chris@2550 2660 if (TransformFactory::getInstance()->havePopulated()) {
Chris@2550 2661 emit canAlign(havePlayTarget && m_document && m_document->canAlign());
Chris@2550 2662 }
Chris@206 2663
Chris@200 2664 emit canChangePlaybackSpeed(true);
Chris@200 2665 int v = m_playSpeed->value();
Chris@200 2666 emit canSpeedUpPlayback(v < m_playSpeed->maximum());
Chris@200 2667 emit canSlowDownPlayback(v > m_playSpeed->minimum());
Chris@155 2668
Chris@164 2669 if (m_viewManager &&
Chris@164 2670 (m_viewManager->getToolMode() == ViewManager::MeasureMode)) {
Chris@164 2671 emit canDeleteSelection(haveCurrentLayer);
Chris@164 2672 m_deleteSelectedAction->setText(tr("&Delete Current Measurement"));
Chris@164 2673 m_deleteSelectedAction->setStatusTip(tr("Delete the measurement currently under the mouse pointer"));
Chris@164 2674 } else {
Chris@164 2675 emit canDeleteSelection(haveSelection && haveCurrentEditableLayer);
Chris@164 2676 m_deleteSelectedAction->setText(tr("&Delete Selected Items"));
Chris@164 2677 m_deleteSelectedAction->setStatusTip(tr("Delete items in current selection from the current layer"));
Chris@164 2678 }
Chris@164 2679
Chris@155 2680 if (m_ffwdAction && m_rwdAction) {
Chris@155 2681 if (haveCurrentTimeInstantsLayer) {
Chris@155 2682 m_ffwdAction->setText(tr("Fast Forward to Next Instant"));
Chris@155 2683 m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer"));
Chris@155 2684 m_rwdAction->setText(tr("Rewind to Previous Instant"));
Chris@155 2685 m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer"));
Chris@155 2686 } else if (haveCurrentTimeValueLayer) {
Chris@155 2687 m_ffwdAction->setText(tr("Fast Forward to Next Point"));
Chris@155 2688 m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer"));
Chris@155 2689 m_rwdAction->setText(tr("Rewind to Previous Point"));
Chris@155 2690 m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer"));
Chris@155 2691 } else {
Chris@155 2692 m_ffwdAction->setText(tr("Fast Forward"));
Chris@155 2693 m_ffwdAction->setStatusTip(tr("Fast forward"));
Chris@155 2694 m_rwdAction->setText(tr("Rewind"));
Chris@155 2695 m_rwdAction->setStatusTip(tr("Rewind"));
Chris@155 2696 }
Chris@155 2697 }
Chris@0 2698 }
Chris@0 2699
Chris@0 2700 void
Chris@0 2701 MainWindow::updateDescriptionLabel()
Chris@0 2702 {
Chris@0 2703 if (!getMainModel()) {
Chris@1770 2704 m_descriptionLabel->setText(tr("No audio file loaded."));
Chris@1770 2705 return;
Chris@0 2706 }
Chris@0 2707
Chris@0 2708 QString description;
Chris@0 2709
Chris@1404 2710 //!!!???
Chris@1404 2711
Chris@922 2712 sv_samplerate_t ssr = getMainModel()->getSampleRate();
Chris@922 2713 sv_samplerate_t tsr = ssr;
Chris@1405 2714 if (m_playSource) tsr = m_playSource->getDeviceSampleRate();
Chris@0 2715
Chris@0 2716 if (ssr != tsr) {
Chris@1770 2717 description = tr("%1Hz (resampling to %2Hz)").arg(ssr).arg(tsr);
Chris@0 2718 } else {
Chris@1770 2719 description = QString("%1Hz").arg(ssr);
Chris@0 2720 }
Chris@0 2721
Chris@0 2722 description = QString("%1 - %2")
Chris@1770 2723 .arg(RealTime::frame2RealTime(getMainModel()->getEndFrame(), ssr)
Chris@1770 2724 .toText(false).c_str())
Chris@1770 2725 .arg(description);
Chris@0 2726
Chris@0 2727 m_descriptionLabel->setText(description);
Chris@0 2728 }
Chris@0 2729
Chris@0 2730 void
Chris@0 2731 MainWindow::documentModified()
Chris@0 2732 {
Chris@200 2733 //!!!
Chris@200 2734 MainWindowBase::documentModified();
Chris@0 2735 }
Chris@0 2736
Chris@0 2737 void
Chris@0 2738 MainWindow::documentRestored()
Chris@0 2739 {
Chris@200 2740 //!!!
Chris@200 2741 MainWindowBase::documentRestored();
Chris@0 2742 }
Chris@0 2743
Chris@0 2744 void
Chris@0 2745 MainWindow::toolNavigateSelected()
Chris@0 2746 {
Chris@0 2747 m_viewManager->setToolMode(ViewManager::NavigateMode);
Chris@0 2748 }
Chris@0 2749
Chris@0 2750 void
Chris@0 2751 MainWindow::toolSelectSelected()
Chris@0 2752 {
Chris@0 2753 m_viewManager->setToolMode(ViewManager::SelectMode);
Chris@0 2754 }
Chris@0 2755
Chris@0 2756 void
Chris@0 2757 MainWindow::toolEditSelected()
Chris@0 2758 {
Chris@0 2759 m_viewManager->setToolMode(ViewManager::EditMode);
Chris@0 2760 }
Chris@0 2761
Chris@0 2762 void
Chris@0 2763 MainWindow::toolDrawSelected()
Chris@0 2764 {
Chris@0 2765 m_viewManager->setToolMode(ViewManager::DrawMode);
Chris@0 2766 }
Chris@0 2767
Chris@151 2768 void
Chris@217 2769 MainWindow::toolEraseSelected()
Chris@217 2770 {
Chris@217 2771 m_viewManager->setToolMode(ViewManager::EraseMode);
Chris@217 2772 }
Chris@217 2773
Chris@217 2774 void
Chris@151 2775 MainWindow::toolMeasureSelected()
Chris@151 2776 {
Chris@151 2777 m_viewManager->setToolMode(ViewManager::MeasureMode);
Chris@151 2778 }
Chris@151 2779
Chris@0 2780 void
Chris@0 2781 MainWindow::importAudio()
Chris@0 2782 {
Chris@88 2783 QString path = getOpenFileName(FileFinder::AudioFile);
Chris@0 2784
Chris@0 2785 if (path != "") {
Chris@1770 2786 if (openAudio(path, ReplaceSession) == FileOpenFailed) {
Chris@247 2787 emit hideSplash();
Chris@1770 2788 QMessageBox::critical(this, tr("Failed to open file"),
Chris@1770 2789 tr("<b>File open failed</b><p>Audio file \"%1\" could not be opened").arg(path));
Chris@1770 2790 }
Chris@0 2791 }
Chris@0 2792 }
Chris@0 2793
Chris@0 2794 void
Chris@0 2795 MainWindow::importMoreAudio()
Chris@0 2796 {
Chris@88 2797 QString path = getOpenFileName(FileFinder::AudioFile);
Chris@0 2798
Chris@0 2799 if (path != "") {
Chris@1770 2800 if (openAudio(path, CreateAdditionalModel) == FileOpenFailed) {
Chris@247 2801 emit hideSplash();
Chris@1770 2802 QMessageBox::critical(this, tr("Failed to open file"),
Chris@1770 2803 tr("<b>File open failed</b><p>Audio file \"%1\" could not be opened").arg(path));
Chris@1770 2804 }
Chris@0 2805 }
Chris@0 2806 }
Chris@0 2807
Chris@0 2808 void
Chris@508 2809 MainWindow::replaceMainAudio()
Chris@508 2810 {
Chris@508 2811 QString path = getOpenFileName(FileFinder::AudioFile);
Chris@508 2812
Chris@508 2813 if (path != "") {
Chris@1770 2814 if (openAudio(path, ReplaceMainModel) == FileOpenFailed) {
Chris@508 2815 emit hideSplash();
Chris@1770 2816 QMessageBox::critical(this, tr("Failed to open file"),
Chris@1770 2817 tr("<b>File open failed</b><p>Audio file \"%1\" could not be opened").arg(path));
Chris@1770 2818 }
Chris@508 2819 }
Chris@508 2820 }
Chris@508 2821
Chris@508 2822 void
Chris@0 2823 MainWindow::exportAudio()
Chris@0 2824 {
Chris@631 2825 exportAudio(false);
Chris@631 2826 }
Chris@631 2827
Chris@631 2828 void
Chris@631 2829 MainWindow::exportAudioData()
Chris@631 2830 {
Chris@631 2831 exportAudio(true);
Chris@631 2832 }
Chris@631 2833
Chris@631 2834 void
Chris@631 2835 MainWindow::exportAudio(bool asData)
Chris@631 2836 {
Chris@2300 2837 auto modelId = getMainModelId();
Chris@2300 2838 if (modelId.isNone()) return;
Chris@2300 2839
Chris@2300 2840 std::set<ModelId> otherModelIds;
Chris@2300 2841 ModelId current = modelId;
Chris@2300 2842
Chris@320 2843 if (m_paneStack) {
Chris@320 2844 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@320 2845 Pane *pane = m_paneStack->getPane(i);
Chris@320 2846 if (!pane) continue;
Chris@320 2847 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@320 2848 Layer *layer = pane->getLayer(j);
Chris@320 2849 if (!layer) continue;
Chris@432 2850 cerr << "layer = " << layer->objectName() << endl;
Chris@2300 2851 ModelId m = layer->getModel();
Chris@2300 2852 if (ModelById::isa<RangeSummarisableTimeValueModel>(m)) {
Chris@2300 2853 otherModelIds.insert(m);
Chris@320 2854 if (pane == m_paneStack->getCurrentPane()) {
Chris@2300 2855 current = m;
Chris@320 2856 }
Chris@320 2857 }
Chris@320 2858 }
Chris@320 2859 }
Chris@320 2860 }
Chris@2300 2861 if (!otherModelIds.empty()) {
Chris@2300 2862 std::map<QString, ModelId> m;
Chris@2300 2863 QString unnamed = tr("<unnamed>");
Chris@2300 2864 QString oname = unnamed;
Chris@2300 2865 if (auto mp = ModelById::get(modelId)) {
Chris@2300 2866 oname = mp->objectName();
Chris@2300 2867 }
Chris@2300 2868 m[tr("1. %2").arg(oname)] = modelId;
Chris@320 2869 int n = 2;
Chris@320 2870 int c = 0;
Chris@2300 2871 for (auto otherModelId: otherModelIds) {
Chris@2300 2872 if (otherModelId == modelId) continue;
Chris@2300 2873 oname = unnamed;
Chris@2300 2874 if (auto mp = ModelById::get(otherModelId)) {
Chris@2300 2875 oname = mp->objectName();
Chris@2300 2876 }
Chris@2300 2877 m[tr("%1. %2").arg(n).arg(oname)] = otherModelId;
Chris@320 2878 ++n;
Chris@2300 2879 if (otherModelId == current) c = n-1;
Chris@320 2880 }
Chris@320 2881 QStringList items;
Chris@2300 2882 for (auto i: m) {
Chris@2300 2883 items << i.first;
Chris@320 2884 }
Chris@325 2885 if (items.size() > 1) {
Chris@325 2886 bool ok = false;
Chris@325 2887 QString item = QInputDialog::getItem
Chris@325 2888 (this, tr("Select audio file to export"),
Chris@325 2889 tr("Which audio file do you want to export from?"),
Chris@325 2890 items, c, false, &ok);
Chris@325 2891 if (!ok || item.isEmpty()) return;
Chris@325 2892 if (m.find(item) == m.end()) {
Chris@2300 2893 SVCERR << "WARNING: Model " << item
Chris@2300 2894 << " not found in list!" << endl;
Chris@325 2895 } else {
Chris@2300 2896 modelId = m[item];
Chris@325 2897 }
Chris@320 2898 }
Chris@320 2899 }
Chris@320 2900
Chris@2300 2901 auto model = ModelById::getAs<DenseTimeValueModel>(modelId);
Chris@2300 2902 if (!model) {
Chris@2300 2903 SVCERR << "ERROR: Chosen model is not a DenseTimeValueModel!" << endl;
Chris@2300 2904 return;
Chris@2300 2905 }
Chris@2300 2906
Chris@631 2907 QString path;
Chris@631 2908 if (asData) {
Chris@631 2909 path = getSaveFileName(FileFinder::CSVFile);
Chris@631 2910 } else {
Chris@631 2911 path = getSaveFileName(FileFinder::AudioFile);
Chris@631 2912 }
Chris@0 2913 if (path == "") return;
Chris@0 2914
Chris@0 2915 bool ok = false;
Chris@0 2916 QString error;
Chris@0 2917
Chris@0 2918 MultiSelection ms = m_viewManager->getSelection();
Chris@0 2919 MultiSelection::SelectionList selections = m_viewManager->getSelections();
Chris@0 2920
Chris@0 2921 bool multiple = false;
Chris@0 2922
Chris@2126 2923 MultiSelection *selectionToWrite = nullptr;
Chris@38 2924
Chris@38 2925 if (selections.size() == 1) {
Chris@0 2926
Chris@1770 2927 QStringList items;
Chris@1770 2928 items << tr("Export the selected region only")
Chris@1770 2929 << tr("Export the whole audio file");
Chris@1770 2930
Chris@1770 2931 bool ok = false;
Chris@1770 2932 QString item = ListInputDialog::getItem
Chris@1770 2933 (this, tr("Select region to export"),
Chris@1770 2934 tr("Which region from the original audio file do you want to export?"),
Chris@1770 2935 items, 0, &ok);
Chris@1770 2936
Chris@1770 2937 if (!ok || item.isEmpty()) return;
Chris@1770 2938
Chris@1770 2939 if (item == items[0]) selectionToWrite = &ms;
Chris@38 2940
Chris@38 2941 } else if (selections.size() > 1) {
Chris@0 2942
Chris@631 2943 if (!asData) { // Multi-file export not supported for data
Chris@631 2944
Chris@631 2945 QStringList items;
Chris@631 2946 items << tr("Export the selected regions into a single file")
Chris@631 2947 << tr("Export the selected regions into separate files")
Chris@631 2948 << tr("Export the whole file");
Chris@631 2949
Chris@631 2950 QString item = ListInputDialog::getItem
Chris@631 2951 (this, tr("Select region to export"),
Chris@631 2952 tr("Multiple regions of the original audio file are selected.\nWhat do you want to export?"),
Chris@631 2953 items, 0, &ok);
Chris@1770 2954
Chris@631 2955 if (!ok || item.isEmpty()) return;
Chris@631 2956
Chris@631 2957 if (item == items[0]) {
Chris@631 2958 selectionToWrite = &ms;
Chris@631 2959 } else if (item == items[1]) {
Chris@631 2960 multiple = true;
Chris@631 2961 }
Chris@631 2962
Chris@631 2963 } else { // asData
Chris@38 2964 selectionToWrite = &ms;
Chris@631 2965 }
Chris@631 2966
Chris@631 2967 if (multiple) { // Can only happen when asData false
Chris@0 2968
Chris@1770 2969 int n = 1;
Chris@1770 2970 QString base = path;
Chris@1770 2971 base.replace(".wav", "");
Chris@1770 2972
Chris@1770 2973 for (MultiSelection::SelectionList::iterator i = selections.begin();
Chris@1770 2974 i != selections.end(); ++i) {
Chris@1770 2975
Chris@1770 2976 MultiSelection subms;
Chris@1770 2977 subms.setSelection(*i);
Chris@1770 2978
Chris@1770 2979 QString subpath = QString("%1.%2.wav").arg(base).arg(n);
Chris@1770 2980 ++n;
Chris@1770 2981
Chris@1770 2982 if (QFileInfo(subpath).exists()) {
Chris@1770 2983 error = tr("Fragment file %1 already exists, aborting").arg(subpath);
Chris@1770 2984 break;
Chris@1770 2985 }
Chris@1770 2986
Chris@1770 2987 WavFileWriter subwriter(subpath,
Chris@320 2988 model->getSampleRate(),
Chris@428 2989 model->getChannelCount(),
Chris@428 2990 WavFileWriter::WriteToTemporary);
Chris@2300 2991 subwriter.writeModel(model.get(), &subms);
Chris@1770 2992 ok = subwriter.isOK();
Chris@1770 2993
Chris@1770 2994 if (!ok) {
Chris@1770 2995 error = subwriter.getError();
Chris@1770 2996 break;
Chris@1770 2997 }
Chris@1770 2998 }
Chris@1770 2999 }
Chris@0 3000 }
Chris@0 3001
Chris@38 3002 if (!multiple) {
Chris@631 3003 if (asData) {
Chris@1779 3004 stop();
Chris@1779 3005 ProgressDialog dialog {
Chris@1779 3006 QObject::tr("Exporting audio data..."),
Chris@1779 3007 true,
Chris@1779 3008 0,
Chris@1779 3009 this,
Chris@1779 3010 Qt::ApplicationModal
Chris@1779 3011 };
Chris@2300 3012 CSVFileWriter writer(path, model.get(), &dialog,
Chris@631 3013 ((QFileInfo(path).suffix() == "csv") ?
Chris@631 3014 "," : "\t"));
Chris@631 3015 if (selectionToWrite) {
Chris@1779 3016 writer.writeSelection(*selectionToWrite);
Chris@631 3017 } else {
Chris@631 3018 writer.write();
Chris@631 3019 }
Chris@631 3020 ok = writer.isOK();
Chris@631 3021 error = writer.getError();
Chris@631 3022 } else {
Chris@631 3023 WavFileWriter writer(path,
Chris@631 3024 model->getSampleRate(),
Chris@631 3025 model->getChannelCount(),
Chris@631 3026 WavFileWriter::WriteToTemporary);
Chris@2300 3027 writer.writeModel(model.get(), selectionToWrite);
Chris@631 3028 ok = writer.isOK();
Chris@631 3029 error = writer.getError();
Chris@631 3030 }
Chris@0 3031 }
Chris@0 3032
Chris@0 3033 if (ok) {
Chris@310 3034 if (multiple) {
Chris@310 3035 emit activity(tr("Export multiple audio files"));
Chris@310 3036 } else {
Chris@310 3037 emit activity(tr("Export audio to \"%1\"").arg(path));
Chris@34 3038 m_recentFiles.addFile(path);
Chris@0 3039 }
Chris@0 3040 } else {
Chris@1770 3041 QMessageBox::critical(this, tr("Failed to write file"), error);
Chris@0 3042 }
Chris@0 3043 }
Chris@0 3044
Chris@0 3045 void
Chris@1995 3046 MainWindow::convertAudio()
Chris@1902 3047 {
Chris@1902 3048 QString path = getOpenFileName(FileFinder::CSVFile);
Chris@1902 3049 if (path == "") return;
Chris@1902 3050
Chris@1995 3051 sv_samplerate_t defaultRate = 44100;
Chris@1902 3052
Chris@1902 3053 CSVFormat format(path);
Chris@1902 3054 format.setModelType(CSVFormat::WaveFileModel);
Chris@1902 3055 format.setTimingType(CSVFormat::ImplicitTiming);
Chris@1902 3056 format.setTimeUnits(CSVFormat::TimeAudioFrames);
Chris@1995 3057 format.setSampleRate(defaultRate); // as a default for the dialog
Chris@1995 3058
Chris@1987 3059 {
Chris@1989 3060 CSVAudioFormatDialog *dialog = new CSVAudioFormatDialog(this, format);
Chris@1987 3061 if (dialog->exec() != QDialog::Accepted) {
Chris@1987 3062 delete dialog;
Chris@1987 3063 return;
Chris@1987 3064 }
Chris@1987 3065 format = dialog->getFormat();
Chris@1987 3066 delete dialog;
Chris@1987 3067 }
Chris@1987 3068
Chris@1902 3069 FileOpenStatus status = FileOpenSucceeded;
Chris@1911 3070
Chris@1987 3071 ProgressDialog *progress = new ProgressDialog
Chris@2001 3072 (tr("Converting audio data..."), true, 0, this, Qt::ApplicationModal);
Chris@1902 3073
Chris@1902 3074 WaveFileModel *model = qobject_cast<WaveFileModel *>
Chris@1902 3075 (DataFileReaderFactory::loadCSV
Chris@1902 3076 (path, format,
Chris@1995 3077 getMainModel() ? getMainModel()->getSampleRate() : defaultRate,
Chris@1987 3078 progress));
Chris@1987 3079
Chris@1987 3080 if (progress->wasCancelled()) {
Chris@1911 3081
Chris@1911 3082 delete model;
Chris@1911 3083 status = FileOpenCancelled;
Chris@1911 3084
Chris@1911 3085 } else if (!model || !model->isOK()) {
Chris@1902 3086
Chris@1902 3087 delete model;
Chris@1902 3088 status = FileOpenFailed;
Chris@1902 3089
Chris@1902 3090 } else {
Chris@1902 3091
Chris@2300 3092 auto modelId = ModelById::add(std::shared_ptr<Model>(model));
Chris@2300 3093
Chris@1902 3094 status = addOpenedAudioModel(path,
Chris@2300 3095 modelId,
Chris@1902 3096 CreateAdditionalModel,
Chris@1902 3097 getDefaultSessionTemplate(),
Chris@1902 3098 false);
Chris@1902 3099 }
Chris@1902 3100
Chris@1987 3101 delete progress;
Chris@1911 3102
Chris@1902 3103 if (status == FileOpenFailed) {
Chris@1902 3104 emit hideSplash();
Chris@1902 3105 QMessageBox::critical(this, tr("Failed to open file"),
Chris@1902 3106 tr("<b>File open failed</b><p>Audio data file %1 could not be opened.").arg(path));
Chris@1902 3107 }
Chris@1902 3108 }
Chris@1902 3109
Chris@1902 3110 void
Chris@0 3111 MainWindow::importLayer()
Chris@0 3112 {
Chris@0 3113 Pane *pane = m_paneStack->getCurrentPane();
Chris@0 3114
Chris@0 3115 if (!pane) {
Chris@1770 3116 // shouldn't happen, as the menu action should have been disabled
Chris@1770 3117 cerr << "WARNING: MainWindow::importLayer: no current pane" << endl;
Chris@1770 3118 return;
Chris@0 3119 }
Chris@0 3120
Chris@0 3121 if (!getMainModel()) {
Chris@1770 3122 // shouldn't happen, as the menu action should have been disabled
Chris@1770 3123 cerr << "WARNING: MainWindow::importLayer: No main model -- hence no default sample rate available" << endl;
Chris@1770 3124 return;
Chris@0 3125 }
Chris@0 3126
Chris@88 3127 QString path = getOpenFileName(FileFinder::LayerFile);
Chris@0 3128
Chris@0 3129 if (path != "") {
Chris@0 3130
Chris@197 3131 FileOpenStatus status = openLayer(path);
Chris@193 3132
Chris@193 3133 if (status == FileOpenFailed) {
Chris@247 3134 emit hideSplash();
Chris@0 3135 QMessageBox::critical(this, tr("Failed to open file"),
Chris@193 3136 tr("<b>File open failed</b><p>Layer file %1 could not be opened.").arg(path));
Chris@0 3137 return;
Chris@193 3138 } else if (status == FileOpenWrongMode) {
Chris@247 3139 emit hideSplash();
Chris@193 3140 QMessageBox::critical(this, tr("Failed to open file"),
Chris@294 3141 tr("<b>Audio required</b><p>Unable to load layer data from \"%1\" without an audio file.<br>Please load at least one audio file before importing annotations.").arg(path));
Chris@0 3142 }
Chris@0 3143 }
Chris@0 3144 }
Chris@0 3145
Chris@0 3146 void
Chris@0 3147 MainWindow::exportLayer()
Chris@0 3148 {
Chris@0 3149 Pane *pane = m_paneStack->getCurrentPane();
Chris@0 3150 if (!pane) return;
Chris@0 3151
Chris@0 3152 Layer *layer = pane->getSelectedLayer();
Chris@0 3153 if (!layer) return;
Chris@0 3154
Chris@2300 3155 ModelId modelId = layer->getModel();
Chris@2300 3156 if (modelId.isNone()) return;
Chris@0 3157
Chris@185 3158 FileFinder::FileType type = FileFinder::LayerFileNoMidi;
Chris@2300 3159 if (ModelById::isa<NoteModel>(modelId)) type = FileFinder::LayerFile;
Chris@185 3160 QString path = getSaveFileName(type);
Chris@0 3161
Chris@0 3162 if (path == "") return;
Chris@0 3163
Chris@2446 3164 QString suffix = QFileInfo(path).suffix().toLower();
Chris@2454 3165 if (suffix == "") suffix = "csv";
Chris@2446 3166
Chris@2449 3167 bool canWriteSelection =
Chris@2449 3168 ! (suffix == "xml" || suffix == "svl" ||
Chris@2449 3169 suffix == "n3" || suffix == "ttl");
Chris@2449 3170
Chris@2449 3171 bool useCSVDialog =
Chris@2449 3172 ! (suffix == "xml" || suffix == "svl" ||
Chris@2449 3173 suffix == "mid" || suffix == "midi" ||
Chris@2449 3174 suffix == "n3" || suffix == "ttl");
Chris@2455 3175
Chris@2455 3176 if (!ModelById::isa<NoteModel>(modelId) &&
Chris@2455 3177 (suffix == "mid" || suffix == "midi")) {
Chris@2455 3178 QMessageBox::critical
Chris@2455 3179 (this, tr("Failed to export layer"),
Chris@2455 3180 tr("Only note layers may be exported to MIDI files."));
Chris@2455 3181 return;
Chris@2455 3182 }
Chris@2455 3183
Chris@2455 3184 if (ModelById::isa<DenseTimeValueModel>(modelId) &&
Chris@2455 3185 !useCSVDialog) {
Chris@2455 3186 // This is the case for spectrograms
Chris@2455 3187 QMessageBox::critical
Chris@2455 3188 (this, tr("Failed to export layer"),
Chris@2455 3189 tr("Cannot export this layer to this file type. Only delimited column formats such as CSV are supported."));
Chris@2455 3190 return;
Chris@2455 3191 }
Chris@2446 3192
Chris@2442 3193 MultiSelection ms = m_viewManager->getSelection();
Chris@2449 3194 bool haveSelection = !ms.getSelections().empty();
Chris@2449 3195
Chris@2442 3196 MultiSelection *selectionToWrite = nullptr;
Chris@2449 3197 LayerGeometryProvider *provider = pane;
Chris@2449 3198
Chris@2449 3199 DataExportOptions options = DataExportDefaults;
Chris@2449 3200 QString delimiter = ",";
Chris@2449 3201
Chris@2449 3202 if (useCSVDialog) {
Chris@2449 3203
Chris@2449 3204 CSVExportDialog::Configuration config;
Chris@2449 3205 config.layerName = layer->getLayerPresentationName();
Chris@2449 3206 config.fileExtension = suffix;
Chris@2449 3207 config.isDense = false;
Chris@2449 3208 if (auto m = ModelById::get(modelId)) {
Chris@2449 3209 config.isDense = !m->isSparse();
Chris@2449 3210 }
Chris@2449 3211 config.haveView = true;
Chris@2449 3212 config.haveSelection = canWriteSelection && haveSelection;
Chris@2449 3213
Chris@2449 3214 CSVExportDialog dialog(config, this);
Chris@2449 3215 if (dialog.exec() != QDialog::Accepted) {
Chris@2449 3216 return;
Chris@2449 3217 }
Chris@2449 3218
Chris@2449 3219 if (dialog.shouldConstrainToSelection()) {
Chris@2449 3220 selectionToWrite = &ms;
Chris@2449 3221 }
Chris@2449 3222
Chris@2449 3223 if (!dialog.shouldConstrainToViewHeight()) {
Chris@2449 3224 provider = nullptr;
Chris@2449 3225 }
Chris@2449 3226
Chris@2449 3227 delimiter = dialog.getDelimiter();
Chris@2449 3228
Chris@2449 3229 if (dialog.shouldIncludeHeader()) {
Chris@2449 3230 options |= DataExportIncludeHeader;
Chris@2449 3231 }
Chris@2449 3232
Chris@2449 3233 if (dialog.shouldIncludeTimestamps()) {
Chris@2449 3234 options |= DataExportAlwaysIncludeTimestamp;
Chris@2449 3235 }
Chris@2449 3236
Chris@2449 3237 if (dialog.shouldWriteTimeInFrames()) {
Chris@2449 3238 options |= DataExportWriteTimeInFrames;
Chris@2449 3239 }
Chris@2449 3240
Chris@2449 3241 } else if (canWriteSelection && haveSelection) {
Chris@2442 3242
Chris@2442 3243 QStringList items;
Chris@2442 3244 items << tr("Export the content of the selected area")
Chris@2442 3245 << tr("Export the whole layer");
Chris@2442 3246
Chris@2442 3247 bool ok = false;
Chris@2442 3248 QString item = ListInputDialog::getItem
Chris@2442 3249 (this, tr("Select region to export"),
Chris@2442 3250 tr("Which region of the layer do you want to export?"),
Chris@2442 3251 items, 0, &ok);
Chris@2442 3252
Chris@2449 3253 if (!ok || item.isEmpty()) {
Chris@2449 3254 return;
Chris@2449 3255 }
Chris@2442 3256
Chris@2449 3257 if (item == items[0]) {
Chris@2449 3258 selectionToWrite = &ms;
Chris@2449 3259 }
Chris@2442 3260 }
Chris@2442 3261
Chris@0 3262 QString error;
Chris@0 3263
Chris@2449 3264 bool result = false;
Chris@2449 3265
Chris@2449 3266 if (suffix == "xml" || suffix == "svl") {
Chris@2449 3267 result = exportLayerToSVL(layer, path, error);
Chris@2449 3268 } else if (suffix == "mid" || suffix == "midi") {
Chris@2449 3269 result = exportLayerToMIDI(layer, selectionToWrite, path, error);
Chris@2449 3270 } else if (suffix == "ttl" || suffix == "n3") {
Chris@2449 3271 result = exportLayerToRDF(layer, path, error);
Chris@2449 3272 } else {
Chris@2449 3273 result = exportLayerToCSV(layer, provider, selectionToWrite,
Chris@2449 3274 delimiter, options, path, error);
Chris@2449 3275 }
Chris@2449 3276
Chris@2449 3277 if (!result) {
Chris@0 3278 QMessageBox::critical(this, tr("Failed to write file"), error);
Chris@0 3279 } else {
Chris@34 3280 m_recentFiles.addFile(path);
Chris@310 3281 emit activity(tr("Export layer to \"%1\"").arg(path));
Chris@0 3282 }
Chris@0 3283 }
Chris@0 3284
Chris@121 3285 void
Chris@121 3286 MainWindow::exportImage()
Chris@121 3287 {
Chris@121 3288 Pane *pane = m_paneStack->getCurrentPane();
Chris@121 3289 if (!pane) return;
Chris@121 3290
Chris@121 3291 QString path = getSaveFileName(FileFinder::ImageFile);
Chris@121 3292 if (path == "") return;
Chris@1460 3293 if (QFileInfo(path).suffix() == "") path += ".png";
Chris@1451 3294
Chris@123 3295 bool haveSelection = m_viewManager && !m_viewManager->getSelections().empty();
Chris@123 3296
Chris@123 3297 QSize total, visible, selected;
Chris@1451 3298 total = pane->getRenderedImageSize();
Chris@1451 3299 visible = pane->getRenderedPartImageSize(pane->getFirstVisibleFrame(),
Chris@1451 3300 pane->getLastVisibleFrame());
Chris@123 3301
Chris@922 3302 sv_frame_t sf0 = 0, sf1 = 0;
Chris@123 3303
Chris@123 3304 if (haveSelection) {
Chris@123 3305 MultiSelection::SelectionList selections = m_viewManager->getSelections();
Chris@123 3306 sf0 = selections.begin()->getStartFrame();
Chris@123 3307 MultiSelection::SelectionList::iterator e = selections.end();
Chris@123 3308 --e;
Chris@123 3309 sf1 = e->getEndFrame();
Chris@1451 3310 selected = pane->getRenderedPartImageSize(sf0, sf1);
Chris@123 3311 }
Chris@123 3312
Chris@123 3313 QStringList items;
Chris@125 3314 items << tr("Export the whole pane (%1x%2 pixels)")
Chris@123 3315 .arg(total.width()).arg(total.height());
Chris@123 3316 items << tr("Export the visible area only (%1x%2 pixels)")
Chris@123 3317 .arg(visible.width()).arg(visible.height());
Chris@123 3318 if (haveSelection) {
Chris@123 3319 items << tr("Export the selection extent (%1x%2 pixels)")
Chris@123 3320 .arg(selected.width()).arg(selected.height());
Chris@124 3321 } else {
Chris@124 3322 items << tr("Export the selection extent");
Chris@123 3323 }
Chris@123 3324
Chris@123 3325 QSettings settings;
Chris@123 3326 settings.beginGroup("MainWindow");
Chris@123 3327 int deflt = settings.value("lastimageexportregion", 0).toInt();
Chris@123 3328 if (deflt == 2 && !haveSelection) deflt = 1;
Chris@1460 3329 if (deflt == 0 && total.width() > 32767) deflt = 1;
Chris@124 3330
Chris@124 3331 ListInputDialog *lid = new ListInputDialog
Chris@123 3332 (this, tr("Select region to export"),
Chris@123 3333 tr("Which region of the current pane do you want to export as an image?"),
Chris@124 3334 items, deflt);
Chris@124 3335
Chris@124 3336 if (!haveSelection) {
Chris@124 3337 lid->setItemAvailability(2, false);
Chris@124 3338 }
Chris@1460 3339 if (total.width() > 32767) { // appears to be limit of a QImage
Chris@124 3340 lid->setItemAvailability(0, false);
Chris@124 3341 lid->setFootnote(tr("Note: the whole pane is too wide to be exported as a single image."));
Chris@124 3342 }
Chris@124 3343
Chris@124 3344 bool ok = lid->exec();
Chris@124 3345 QString item = lid->getCurrentString();
Chris@124 3346 delete lid;
Chris@1770 3347
Chris@123 3348 if (!ok || item.isEmpty()) return;
Chris@123 3349
Chris@123 3350 settings.setValue("lastimageexportregion", deflt);
Chris@123 3351
Chris@2126 3352 QImage *image = nullptr;
Chris@1451 3353
Chris@1460 3354 if (item == items[0]) {
Chris@1460 3355 image = pane->renderToNewImage();
Chris@1460 3356 } else if (item == items[1]) {
Chris@1460 3357 image = pane->renderPartToNewImage(pane->getFirstVisibleFrame(),
Chris@1460 3358 pane->getLastVisibleFrame());
Chris@1460 3359 } else if (haveSelection) {
Chris@1460 3360 image = pane->renderPartToNewImage(sf0, sf1);
Chris@1451 3361 }
Chris@1460 3362
Chris@1460 3363 if (!image) return;
Chris@1460 3364
Chris@1460 3365 if (!image->save(path, "PNG")) {
Chris@1460 3366 QMessageBox::critical(this, tr("Failed to save image file"),
Chris@1460 3367 tr("Failed to save image file %1").arg(path));
Chris@1460 3368 }
Chris@1460 3369
Chris@1460 3370 delete image;
Chris@1451 3371 }
Chris@1451 3372
Chris@1451 3373 void
Chris@1451 3374 MainWindow::exportSVG()
Chris@1451 3375 {
Chris@1451 3376 Pane *pane = m_paneStack->getCurrentPane();
Chris@1451 3377 if (!pane) return;
Chris@1451 3378
Chris@1451 3379 QString path = getSaveFileName(FileFinder::SVGFile);
Chris@1451 3380 if (path == "") return;
Chris@1451 3381 if (QFileInfo(path).suffix() == "") path += ".svg";
Chris@1451 3382
Chris@1451 3383 bool haveSelection = m_viewManager && !m_viewManager->getSelections().empty();
Chris@1451 3384
Chris@1451 3385 sv_frame_t sf0 = 0, sf1 = 0;
Chris@1451 3386
Chris@1451 3387 if (haveSelection) {
Chris@1451 3388 MultiSelection::SelectionList selections = m_viewManager->getSelections();
Chris@1451 3389 sf0 = selections.begin()->getStartFrame();
Chris@1451 3390 MultiSelection::SelectionList::iterator e = selections.end();
Chris@1451 3391 --e;
Chris@1451 3392 sf1 = e->getEndFrame();
Chris@1451 3393 }
Chris@1451 3394
Chris@1451 3395 QStringList items;
Chris@1451 3396 items << tr("Export the whole pane");
Chris@1451 3397 items << tr("Export the visible area only");
Chris@1451 3398 items << tr("Export the selection extent");
Chris@1451 3399
Chris@1451 3400 QSettings settings;
Chris@1451 3401 settings.beginGroup("MainWindow");
Chris@1451 3402 int deflt = settings.value("lastsvgexportregion", 0).toInt();
Chris@1451 3403 if (deflt == 2 && !haveSelection) deflt = 1;
Chris@1451 3404
Chris@1451 3405 ListInputDialog *lid = new ListInputDialog
Chris@1451 3406 (this, tr("Select region to export"),
Chris@1451 3407 tr("Which region of the current pane do you want to export as a scalable SVG image?"),
Chris@1451 3408 items, deflt);
Chris@1451 3409
Chris@1451 3410 if (!haveSelection) {
Chris@1451 3411 lid->setItemAvailability(2, false);
Chris@1451 3412 }
Chris@1451 3413
Chris@1451 3414 bool ok = lid->exec();
Chris@1451 3415 QString item = lid->getCurrentString();
Chris@1451 3416 delete lid;
Chris@1770 3417
Chris@1451 3418 if (!ok || item.isEmpty()) return;
Chris@1451 3419
Chris@1451 3420 settings.setValue("lastsvgexportregion", deflt);
Chris@1451 3421
Chris@1451 3422 bool result = false;
Chris@1451 3423
Chris@123 3424 if (item == items[0]) {
Chris@2517 3425 result = pane->renderToSvgFile(path);
Chris@123 3426 } else if (item == items[1]) {
Chris@1451 3427 result = pane->renderPartToSvgFile(path,
Chris@1451 3428 pane->getFirstVisibleFrame(),
Chris@1451 3429 pane->getLastVisibleFrame());
Chris@123 3430 } else if (haveSelection) {
Chris@1451 3431 result = pane->renderPartToSvgFile(path, sf0, sf1);
Chris@121 3432 }
Chris@121 3433
Chris@1451 3434 if (!result) {
Chris@1451 3435 QMessageBox::critical(this, tr("Failed to save SVG file"),
Chris@1451 3436 tr("Failed to save SVG file %1").arg(path));
Chris@1451 3437 }
Chris@121 3438 }
Chris@121 3439
Chris@0 3440 void
Chris@1056 3441 MainWindow::browseRecordedAudio()
Chris@1056 3442 {
Chris@1995 3443 QString path = RecordDirectory::getRecordContainerDirectory();
Chris@1995 3444 if (path == "") path = RecordDirectory::getRecordDirectory();
Chris@1056 3445 if (path == "") return;
Chris@1056 3446
Chris@1056 3447 openLocalFolder(path);
Chris@1056 3448 }
Chris@1056 3449
Chris@1056 3450 void
Chris@0 3451 MainWindow::newSession()
Chris@0 3452 {
Chris@0 3453 if (!checkSaveModified()) return;
Chris@0 3454
Chris@0 3455 closeSession();
Chris@2062 3456 stop();
Chris@0 3457 createDocument();
Chris@0 3458
Chris@0 3459 Pane *pane = m_paneStack->addPane();
Chris@0 3460
Chris@90 3461 connect(pane, SIGNAL(contextHelpChanged(const QString &)),
Chris@116 3462 this, SLOT(contextHelpChanged(const QString &)));
Chris@90 3463
Chris@0 3464 if (!m_timeRulerLayer) {
Chris@1770 3465 m_timeRulerLayer = m_document->createMainModelLayer
Chris@1770 3466 (LayerFactory::TimeRuler);
Chris@0 3467 }
Chris@0 3468
Chris@0 3469 m_document->addLayerToView(pane, m_timeRulerLayer);
Chris@0 3470
Chris@0 3471 Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform);
Chris@0 3472 m_document->addLayerToView(pane, waveform);
Chris@0 3473
Chris@65 3474 m_overview->registerView(pane);
Chris@0 3475
Chris@0 3476 CommandHistory::getInstance()->clear();
Chris@0 3477 CommandHistory::getInstance()->documentSaved();
Chris@0 3478 documentRestored();
Chris@0 3479 updateMenuStates();
Chris@0 3480 }
Chris@0 3481
Chris@0 3482 void
Chris@303 3483 MainWindow::documentReplaced()
Chris@303 3484 {
Chris@303 3485 if (m_document) {
Chris@303 3486 connect(m_document, SIGNAL(activity(QString)),
Chris@303 3487 m_activityLog, SLOT(activityHappened(QString)));
Chris@303 3488 }
Chris@303 3489 }
Chris@303 3490
Chris@303 3491 void
Chris@0 3492 MainWindow::closeSession()
Chris@0 3493 {
Chris@0 3494 if (!checkSaveModified()) return;
Chris@0 3495
Chris@0 3496 while (m_paneStack->getPaneCount() > 0) {
Chris@0 3497
Chris@1770 3498 Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1);
Chris@1770 3499
Chris@1770 3500 while (pane->getLayerCount() > 0) {
Chris@1770 3501 m_document->removeLayerFromView
Chris@1770 3502 (pane, pane->getLayer(pane->getLayerCount() - 1));
Chris@1770 3503 }
Chris@1770 3504
Chris@1770 3505 m_overview->unregisterView(pane);
Chris@1770 3506 m_paneStack->deletePane(pane);
Chris@0 3507 }
Chris@0 3508
Chris@0 3509 while (m_paneStack->getHiddenPaneCount() > 0) {
Chris@0 3510
Chris@1770 3511 Pane *pane = m_paneStack->getHiddenPane
Chris@1770 3512 (m_paneStack->getHiddenPaneCount() - 1);
Chris@1770 3513
Chris@1770 3514 while (pane->getLayerCount() > 0) {
Chris@1770 3515 m_document->removeLayerFromView
Chris@1770 3516 (pane, pane->getLayer(pane->getLayerCount() - 1));
Chris@1770 3517 }
Chris@1770 3518
Chris@1770 3519 m_overview->unregisterView(pane);
Chris@1770 3520 m_paneStack->deletePane(pane);
Chris@0 3521 }
Chris@0 3522
Chris@504 3523 delete m_layerTreeDialog.data();
Chris@504 3524 delete m_preferencesDialog.data();
Chris@504 3525
Chris@504 3526 m_activityLog->hide();
Chris@891 3527 m_unitConverter->hide();
Chris@504 3528 m_keyReference->hide();
Chris@504 3529
Chris@0 3530 delete m_document;
Chris@2126 3531 m_document = nullptr;
Chris@0 3532 m_viewManager->clearSelections();
Chris@2126 3533 m_timeRulerLayer = nullptr; // document owned this
Chris@0 3534
Chris@0 3535 m_sessionFile = "";
Chris@2350 3536 m_originalLocation = "";
Chris@518 3537 setWindowTitle(QApplication::applicationName());
Chris@0 3538
Chris@0 3539 CommandHistory::getInstance()->clear();
Chris@0 3540 CommandHistory::getInstance()->documentSaved();
Chris@0 3541 documentRestored();
Chris@0 3542 }
Chris@0 3543
Chris@0 3544 void
Chris@0 3545 MainWindow::openSomething()
Chris@0 3546 {
Chris@0 3547 QString orig = m_audioFile;
Chris@0 3548 if (orig == "") orig = ".";
Chris@0 3549 else orig = QFileInfo(orig).absoluteDir().canonicalPath();
Chris@0 3550
Chris@88 3551 QString path = getOpenFileName(FileFinder::AnyFile);
Chris@0 3552
Chris@0 3553 if (path.isEmpty()) return;
Chris@0 3554
Chris@844 3555 FileOpenStatus status = openPath(path, ReplaceSession);
Chris@193 3556
Chris@193 3557 if (status == FileOpenFailed) {
Chris@247 3558 emit hideSplash();
Chris@193 3559 QMessageBox::critical(this, tr("Failed to open file"),
Chris@193 3560 tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path));
Chris@193 3561 } else if (status == FileOpenWrongMode) {
Chris@247 3562 emit hideSplash();
Chris@193 3563 QMessageBox::critical(this, tr("Failed to open file"),
Chris@294 3564 tr("<b>Audio required</b><p>Unable to load layer data from \"%1\" without an audio file.<br>Please load at least one audio file before importing annotations.").arg(path));
Chris@0 3565 }
Chris@0 3566 }
Chris@0 3567
Chris@0 3568 void
Chris@86 3569 MainWindow::openLocation()
Chris@86 3570 {
Chris@103 3571 QSettings settings;
Chris@103 3572 settings.beginGroup("MainWindow");
Chris@103 3573 QString lastLocation = settings.value("lastremote", "").toString();
Chris@103 3574
Chris@86 3575 bool ok = false;
Chris@86 3576 QString text = QInputDialog::getText
Chris@86 3577 (this, tr("Open Location"),
Chris@86 3578 tr("Please enter the URL of the location to open:"),
Chris@103 3579 QLineEdit::Normal, lastLocation, &ok);
Chris@103 3580
Chris@103 3581 if (!ok) return;
Chris@103 3582
Chris@103 3583 settings.setValue("lastremote", text);
Chris@103 3584
Chris@103 3585 if (text.isEmpty()) return;
Chris@86 3586
Chris@844 3587 FileOpenStatus status = openPath(text, AskUser);
Chris@193 3588
Chris@193 3589 if (status == FileOpenFailed) {
Chris@247 3590 emit hideSplash();
Chris@86 3591 QMessageBox::critical(this, tr("Failed to open location"),
Chris@193 3592 tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text));
Chris@193 3593 } else if (status == FileOpenWrongMode) {
Chris@247 3594 emit hideSplash();
Chris@193 3595 QMessageBox::critical(this, tr("Failed to open location"),
Chris@294 3596 tr("<b>Audio required</b><p>Unable to load layer data from \"%1\" without an audio file.<br>Please load at least one audio file before importing annotations.").arg(text));
Chris@86 3597 }
Chris@86 3598 }
Chris@86 3599
Chris@86 3600 void
Chris@2149 3601 MainWindow::openRecentFile()
Chris@0 3602 {
Chris@0 3603 QObject *obj = sender();
Chris@0 3604 QAction *action = dynamic_cast<QAction *>(obj);
Chris@0 3605
Chris@0 3606 if (!action) {
Chris@1770 3607 cerr << "WARNING: MainWindow::openRecentFile: sender is not an action"
Chris@2149 3608 << endl;
Chris@1770 3609 return;
Chris@0 3610 }
Chris@0 3611
Chris@2149 3612 QString path = action->objectName();
Chris@2149 3613
Chris@2149 3614 if (path == "") {
Chris@2149 3615 cerr << "WARNING: MainWindow::openRecentFile: action incorrectly named"
Chris@2149 3616 << endl;
Chris@2149 3617 return;
Chris@2149 3618 }
Chris@0 3619
Chris@844 3620 FileOpenStatus status = openPath(path, ReplaceSession);
Chris@193 3621
Chris@193 3622 if (status == FileOpenFailed) {
Chris@247 3623 emit hideSplash();
Chris@193 3624 QMessageBox::critical(this, tr("Failed to open location"),
Chris@193 3625 tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path));
Chris@193 3626 } else if (status == FileOpenWrongMode) {
Chris@247 3627 emit hideSplash();
Chris@193 3628 QMessageBox::critical(this, tr("Failed to open location"),
Chris@294 3629 tr("<b>Audio required</b><p>Unable to load layer data from \"%1\" without an audio file.<br>Please load at least one audio file before importing annotations.").arg(path));
Chris@0 3630 }
Chris@0 3631 }
Chris@0 3632
Chris@0 3633 void
Chris@435 3634 MainWindow::applyTemplate()
Chris@435 3635 {
Chris@435 3636 QObject *s = sender();
Chris@435 3637 QAction *action = qobject_cast<QAction *>(s);
Chris@435 3638
Chris@435 3639 if (!action) {
Chris@1770 3640 cerr << "WARNING: MainWindow::applyTemplate: sender is not an action"
Chris@1770 3641 << endl;
Chris@1770 3642 return;
Chris@435 3643 }
Chris@435 3644
Chris@435 3645 QString n = action->objectName();
Chris@435 3646 if (n == "") n = action->text();
Chris@435 3647
Chris@435 3648 if (n == "") {
Chris@665 3649 cerr << "WARNING: MainWindow::applyTemplate: sender has no name"
Chris@665 3650 << endl;
Chris@435 3651 return;
Chris@435 3652 }
Chris@435 3653
Chris@435 3654 QString mainModelLocation;
Chris@2300 3655 auto mm = getMainModel();
Chris@435 3656 if (mm) mainModelLocation = mm->getLocation();
Chris@435 3657 if (mainModelLocation != "") {
Chris@435 3658 openAudio(mainModelLocation, ReplaceSession, n);
Chris@435 3659 } else {
Chris@435 3660 openSessionTemplate(n);
Chris@435 3661 }
Chris@435 3662 }
Chris@435 3663
Chris@435 3664 void
Chris@425 3665 MainWindow::saveSessionAsTemplate()
Chris@425 3666 {
Chris@762 3667 QDialog *d = new QDialog(this);
Chris@483 3668 d->setWindowTitle(tr("Enter template name"));
Chris@483 3669
Chris@483 3670 QGridLayout *layout = new QGridLayout;
Chris@483 3671 d->setLayout(layout);
Chris@483 3672
Chris@483 3673 layout->addWidget(new QLabel(tr("Please enter a name for the saved template:")),
Chris@483 3674 0, 0);
Chris@483 3675 QLineEdit *lineEdit = new QLineEdit;
Chris@483 3676 layout->addWidget(lineEdit, 1, 0);
Chris@483 3677 QCheckBox *makeDefault = new QCheckBox(tr("Set as default template for future audio files"));
Chris@483 3678 layout->addWidget(makeDefault, 2, 0);
Chris@425 3679
Chris@483 3680 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok |
Chris@483 3681 QDialogButtonBox::Cancel);
Chris@483 3682 layout->addWidget(bb, 3, 0);
Chris@483 3683 connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
Chris@483 3684 connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
Chris@483 3685 connect(bb, SIGNAL(rejected()), d, SLOT(reject()));
Chris@483 3686
Chris@483 3687 if (d->exec() == QDialog::Accepted) {
Chris@483 3688
Chris@483 3689 QString name = lineEdit->text();
Chris@483 3690 name.replace(QRegExp("[^\\w\\s\\.\"'-]"), "_");
Chris@483 3691
Chris@483 3692 ResourceFinder rf;
Chris@483 3693 QString dir = rf.getResourceSaveDir("templates");
Chris@483 3694 QString filename = QString("%1/%2.svt").arg(dir).arg(name);
Chris@483 3695 if (QFile(filename).exists()) {
Chris@483 3696 if (QMessageBox::warning(this,
Chris@483 3697 tr("Template file exists"),
Chris@483 3698 tr("<b>Template file exists</b><p>The template \"%1\" already exists.<br>Overwrite it?").arg(name),
Chris@483 3699 QMessageBox::Ok | QMessageBox::Cancel,
Chris@483 3700 QMessageBox::Cancel) != QMessageBox::Ok) {
Chris@1516 3701 delete d;
Chris@483 3702 return;
Chris@483 3703 }
Chris@483 3704 }
Chris@483 3705
Chris@483 3706 if (saveSessionTemplate(filename)) {
Chris@483 3707 if (makeDefault->isChecked()) {
Chris@483 3708 setDefaultSessionTemplate(name);
Chris@483 3709 }
Chris@431 3710 }
Chris@431 3711 }
Chris@1516 3712
Chris@1516 3713 delete d;
Chris@425 3714 }
Chris@425 3715
Chris@425 3716 void
Chris@425 3717 MainWindow::manageSavedTemplates()
Chris@425 3718 {
Chris@425 3719 ResourceFinder rf;
Chris@1930 3720 openLocalFolder(rf.getResourceSaveDir("templates"));
Chris@423 3721 }
Chris@423 3722
Chris@423 3723 void
Chris@200 3724 MainWindow::paneAdded(Pane *pane)
Chris@200 3725 {
Chris@200 3726 if (m_overview) m_overview->registerView(pane);
Chris@2303 3727 if (pane) {
Chris@2303 3728 connect(pane, SIGNAL(cancelButtonPressed(Layer *)),
Chris@2303 3729 this, SLOT(paneCancelButtonPressed(Layer *)));
Chris@2303 3730 }
Chris@200 3731 }
Chris@200 3732
Chris@200 3733 void
Chris@200 3734 MainWindow::paneHidden(Pane *pane)
Chris@200 3735 {
Chris@200 3736 if (m_overview) m_overview->unregisterView(pane);
Chris@200 3737 }
Chris@200 3738
Chris@200 3739 void
Chris@200 3740 MainWindow::paneAboutToBeDeleted(Pane *pane)
Chris@200 3741 {
Chris@200 3742 if (m_overview) m_overview->unregisterView(pane);
Chris@200 3743 }
Chris@200 3744
Chris@200 3745 void
Chris@2303 3746 MainWindow::paneCancelButtonPressed(Layer *layer)
Chris@2303 3747 {
Chris@2303 3748 Pane *pane = qobject_cast<Pane *>(sender());
Chris@2303 3749 bool found = false;
Chris@2303 3750 if (pane && layer) {
Chris@2303 3751 for (int i = 0; i < pane->getLayerCount(); ++i) {
Chris@2303 3752 if (pane->getLayer(i) == layer) {
Chris@2303 3753 found = true;
Chris@2303 3754 break;
Chris@2303 3755 }
Chris@2303 3756 }
Chris@2303 3757 }
Chris@2303 3758 if (!found) {
Chris@2303 3759 SVDEBUG << "MainWindow::paneCancelButtonPressed: Unknown layer in pane"
Chris@2303 3760 << endl;
Chris@2303 3761 return;
Chris@2303 3762 }
Chris@2303 3763
Chris@2303 3764 SVDEBUG << "MainWindow::paneCancelButtonPressed: Layer " << layer << endl;
Chris@2303 3765
Chris@2304 3766 // We need to ensure that the transform that is populating this
Chris@2304 3767 // layer's model is stopped - that is the main reason to use
Chris@2304 3768 // Cancel after all. It would also be a good idea to remove the
Chris@2304 3769 // incomplete layer from both the view and the undo/redo stack.
Chris@2304 3770
Chris@2304 3771 // Deleting the target model will ensure that the transform gets
Chris@2304 3772 // stopped, but removing the layer from the view is not enough to
Chris@2304 3773 // delete the model, because a reference to the layer remains on
Chris@2304 3774 // the undo/redo stack. If we also replace the model id with None
Chris@2304 3775 // in the layer, that does the trick.
Chris@2304 3776
Chris@2304 3777 m_document->setModel(layer, {});
Chris@2303 3778 m_document->removeLayerFromView(pane, layer);
Chris@2304 3779
Chris@2304 3780 // We still have a layer with no model on the undo/redo stack,
Chris@2304 3781 // which is a pity. I'm not sure we can easily remove it, since
Chris@2304 3782 // other commands may have been pushed on the stack since, so
Chris@2304 3783 // let's just leave that for now.
Chris@2304 3784
Chris@2303 3785 updateMenuStates();
Chris@2303 3786 }
Chris@2303 3787
Chris@2303 3788 void
Chris@193 3789 MainWindow::paneDropAccepted(Pane *pane, QStringList uriList)
Chris@193 3790 {
Chris@193 3791 if (pane) m_paneStack->setCurrentPane(pane);
Chris@193 3792
Chris@193 3793 for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) {
Chris@193 3794
Chris@295 3795 FileOpenStatus status;
Chris@295 3796
Chris@295 3797 if (i == uriList.begin()) {
Chris@844 3798 status = openPath(*i, ReplaceCurrentPane);
Chris@295 3799 } else {
Chris@844 3800 status = openPath(*i, CreateAdditionalModel);
Chris@295 3801 }
Chris@193 3802
Chris@193 3803 if (status == FileOpenFailed) {
Chris@247 3804 emit hideSplash();
Chris@193 3805 QMessageBox::critical(this, tr("Failed to open dropped URL"),
Chris@193 3806 tr("<b>Open failed</b><p>Dropped URL \"%1\" could not be opened").arg(*i));
Chris@295 3807 break;
Chris@193 3808 } else if (status == FileOpenWrongMode) {
Chris@247 3809 emit hideSplash();
Chris@193 3810 QMessageBox::critical(this, tr("Failed to open dropped URL"),
Chris@294 3811 tr("<b>Audio required</b><p>Unable to load layer data from \"%1\" without an audio file.<br>Please load at least one audio file before importing annotations.").arg(*i));
Chris@295 3812 break;
Chris@295 3813 } else if (status == FileOpenCancelled) {
Chris@295 3814 break;
Chris@193 3815 }
Chris@193 3816 }
Chris@193 3817 }
Chris@193 3818
Chris@193 3819 void
Chris@193 3820 MainWindow::paneDropAccepted(Pane *pane, QString text)
Chris@193 3821 {
Chris@193 3822 if (pane) m_paneStack->setCurrentPane(pane);
Chris@193 3823
Chris@193 3824 QUrl testUrl(text);
Chris@193 3825 if (testUrl.scheme() == "file" ||
Chris@193 3826 testUrl.scheme() == "http" ||
Chris@193 3827 testUrl.scheme() == "ftp") {
Chris@193 3828 QStringList list;
Chris@193 3829 list.push_back(text);
Chris@193 3830 paneDropAccepted(pane, list);
Chris@193 3831 return;
Chris@193 3832 }
Chris@193 3833
Chris@193 3834 //!!! open as text -- but by importing as if a CSV, or just adding
Chris@193 3835 //to a text layer?
Chris@193 3836 }
Chris@193 3837
Chris@193 3838 void
Chris@0 3839 MainWindow::closeEvent(QCloseEvent *e)
Chris@0 3840 {
Chris@2027 3841 SVDEBUG << "MainWindow::closeEvent" << endl;
Chris@118 3842
Chris@136 3843 if (m_openingAudioFile) {
Chris@2027 3844 SVCERR << "Busy - ignoring close event" << endl;
Chris@1770 3845 e->ignore();
Chris@1770 3846 return;
Chris@136 3847 }
Chris@136 3848
Chris@2440 3849 if (!checkSaveModified()) {
Chris@2027 3850 SVCERR << "Close refused by user - ignoring close event" << endl;
Chris@1770 3851 e->ignore();
Chris@1770 3852 return;
Chris@0 3853 }
Chris@0 3854
Chris@5 3855 QSettings settings;
Chris@5 3856 settings.beginGroup("MainWindow");
Chris@624 3857 settings.setValue("maximised", isMaximized());
Chris@624 3858 if (!isMaximized()) {
Chris@624 3859 settings.setValue("size", size());
Chris@624 3860 settings.setValue("position", pos());
Chris@624 3861 }
Chris@5 3862 settings.endGroup();
Chris@5 3863
Chris@163 3864 if (m_preferencesDialog &&
Chris@163 3865 m_preferencesDialog->isVisible()) {
Chris@1434 3866 m_preferencesDialog->applicationClosing(true);
Chris@163 3867 }
Chris@163 3868
Chris@2172 3869 stop();
Chris@200 3870 closeSession();
Chris@200 3871
Chris@0 3872 e->accept();
Chris@489 3873
Chris@0 3874 return;
Chris@0 3875 }
Chris@0 3876
Chris@0 3877 bool
Chris@11 3878 MainWindow::commitData(bool mayAskUser)
Chris@11 3879 {
Chris@11 3880 if (mayAskUser) {
Chris@163 3881 bool rv = checkSaveModified();
Chris@163 3882 if (rv) {
Chris@163 3883 if (m_preferencesDialog &&
Chris@163 3884 m_preferencesDialog->isVisible()) {
Chris@163 3885 m_preferencesDialog->applicationClosing(false);
Chris@163 3886 }
Chris@163 3887 }
Chris@163 3888 return rv;
Chris@11 3889 } else {
Chris@163 3890 if (m_preferencesDialog &&
Chris@163 3891 m_preferencesDialog->isVisible()) {
Chris@163 3892 m_preferencesDialog->applicationClosing(true);
Chris@163 3893 }
Chris@11 3894 if (!m_documentModified) return true;
Chris@11 3895
Chris@11 3896 // If we can't check with the user first, then we can't save
Chris@11 3897 // to the original session file (even if we have it) -- have
Chris@11 3898 // to use a temporary file
Chris@11 3899
Chris@11 3900 QString svDirBase = ".sv1";
Chris@11 3901 QString svDir = QDir::home().filePath(svDirBase);
Chris@11 3902
Chris@11 3903 if (!QFileInfo(svDir).exists()) {
Chris@11 3904 if (!QDir::home().mkdir(svDirBase)) return false;
Chris@11 3905 } else {
Chris@11 3906 if (!QFileInfo(svDir).isDir()) return false;
Chris@11 3907 }
Chris@11 3908
Chris@11 3909 // This name doesn't have to be unguessable
Chris@93 3910 #ifndef _WIN32
Chris@11 3911 QString fname = QString("tmp-%1-%2.sv")
Chris@11 3912 .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz"))
Chris@11 3913 .arg(QProcess().pid());
Chris@93 3914 #else
Chris@93 3915 QString fname = QString("tmp-%1.sv")
Chris@93 3916 .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz"));
Chris@93 3917 #endif
Chris@11 3918 QString fpath = QDir(svDir).filePath(fname);
Chris@11 3919 if (saveSessionFile(fpath)) {
Chris@34 3920 m_recentFiles.addFile(fpath);
Chris@310 3921 emit activity(tr("Export image to \"%1\"").arg(fpath));
Chris@11 3922 return true;
Chris@11 3923 } else {
Chris@11 3924 return false;
Chris@11 3925 }
Chris@11 3926 }
Chris@11 3927 }
Chris@11 3928
Chris@11 3929 bool
Chris@0 3930 MainWindow::checkSaveModified()
Chris@0 3931 {
Chris@0 3932 // Called before some destructive operation (e.g. new session,
Chris@0 3933 // exit program). Return true if we can safely proceed, false to
Chris@0 3934 // cancel.
Chris@0 3935
Chris@0 3936 if (!m_documentModified) return true;
Chris@0 3937
Chris@247 3938 emit hideSplash();
Chris@247 3939
Chris@0 3940 int button =
Chris@1770 3941 QMessageBox::warning(this,
Chris@1770 3942 tr("Session modified"),
Chris@1770 3943 tr("<b>Session modified</b><p>The current session has been modified.<br>Do you want to save it?"),
Chris@1770 3944 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@165 3945 QMessageBox::Yes);
Chris@0 3946
Chris@0 3947 if (button == QMessageBox::Yes) {
Chris@1770 3948 saveSession();
Chris@1770 3949 if (m_documentModified) { // save failed -- don't proceed!
Chris@1770 3950 return false;
Chris@1770 3951 } else {
Chris@0 3952 return true; // saved, so it's safe to continue now
Chris@0 3953 }
Chris@0 3954 } else if (button == QMessageBox::No) {
Chris@1770 3955 m_documentModified = false; // so we know to abandon it
Chris@1770 3956 return true;
Chris@0 3957 }
Chris@0 3958
Chris@0 3959 // else cancel
Chris@0 3960 return false;
Chris@0 3961 }
Chris@0 3962
Chris@290 3963 bool
Chris@294 3964 MainWindow::shouldCreateNewSessionForRDFAudio(bool *cancel)
Chris@290 3965 {
Chris@294 3966 //!!! This is very similar to part of MainWindowBase::openAudio --
Chris@294 3967 //!!! make them a bit more uniform
Chris@294 3968
Chris@294 3969 QSettings settings;
Chris@294 3970 settings.beginGroup("MainWindow");
Chris@294 3971 bool prevNewSession = settings.value("newsessionforrdfaudio", true).toBool();
Chris@294 3972 settings.endGroup();
Chris@294 3973 bool newSession = true;
Chris@294 3974
Chris@294 3975 QStringList items;
Chris@294 3976 items << tr("Close the current session and create a new one")
Chris@294 3977 << tr("Add this data to the current session");
Chris@294 3978
Chris@294 3979 bool ok = false;
Chris@294 3980 QString item = ListInputDialog::getItem
Chris@294 3981 (this, tr("Select target for import"),
Chris@294 3982 tr("<b>Select a target for import</b><p>This RDF document refers to one or more audio files.<br>You already have an audio waveform loaded.<br>What would you like to do with the new data?"),
Chris@294 3983 items, prevNewSession ? 0 : 1, &ok);
Chris@294 3984
Chris@294 3985 if (!ok || item.isEmpty()) {
Chris@294 3986 *cancel = true;
Chris@290 3987 return false;
Chris@290 3988 }
Chris@294 3989
Chris@294 3990 newSession = (item == items[0]);
Chris@294 3991 settings.beginGroup("MainWindow");
Chris@294 3992 settings.setValue("newsessionforrdfaudio", newSession);
Chris@294 3993 settings.endGroup();
Chris@294 3994
Chris@294 3995 if (newSession) return true;
Chris@294 3996 else return false;
Chris@290 3997 }
Chris@290 3998
Chris@0 3999 void
Chris@0 4000 MainWindow::saveSession()
Chris@0 4001 {
Chris@0 4002 if (m_sessionFile != "") {
Chris@1770 4003 if (!saveSessionFile(m_sessionFile)) {
Chris@1770 4004 QMessageBox::critical(this, tr("Failed to save file"),
Chris@1770 4005 tr("<b>Save failed</b><p>Session file \"%1\" could not be saved.").arg(m_sessionFile));
Chris@1770 4006 } else {
Chris@1770 4007 CommandHistory::getInstance()->documentSaved();
Chris@1770 4008 documentRestored();
Chris@1770 4009 }
Chris@0 4010 } else {
Chris@1770 4011 saveSessionAs();
Chris@0 4012 }
Chris@0 4013 }
Chris@0 4014
Chris@0 4015 void
Chris@0 4016 MainWindow::saveSessionAs()
Chris@0 4017 {
Chris@0 4018 QString orig = m_audioFile;
Chris@0 4019 if (orig == "") orig = ".";
Chris@0 4020 else orig = QFileInfo(orig).absoluteDir().canonicalPath();
Chris@0 4021
Chris@88 4022 QString path = getSaveFileName(FileFinder::SessionFile);
Chris@81 4023
Chris@81 4024 if (path == "") return;
Chris@0 4025
Chris@0 4026 if (!saveSessionFile(path)) {
Chris@1770 4027 QMessageBox::critical(this, tr("Failed to save file"),
Chris@1770 4028 tr("<b>Save failed</b><p>Session file \"%1\" could not be saved.").arg(path));
Chris@0 4029 } else {
Chris@1770 4030 setWindowTitle(tr("%1: %2")
Chris@518 4031 .arg(QApplication::applicationName())
Chris@1770 4032 .arg(QFileInfo(path).fileName()));
Chris@1770 4033 m_sessionFile = path;
Chris@1770 4034 CommandHistory::getInstance()->documentSaved();
Chris@1770 4035 documentRestored();
Chris@34 4036 m_recentFiles.addFile(path);
Chris@310 4037 emit activity(tr("Save session as \"%1\"").arg(path));
Chris@0 4038 }
Chris@0 4039 }
Chris@0 4040
Chris@90 4041 void
Chris@72 4042 MainWindow::preferenceChanged(PropertyContainer::PropertyName name)
Chris@72 4043 {
Chris@200 4044 MainWindowBase::preferenceChanged(name);
Chris@200 4045
Chris@1448 4046 if (name == "Background Mode") {
Chris@1448 4047 coloursChanged();
Chris@200 4048 }
Chris@0 4049 }
Chris@0 4050
Chris@0 4051 void
Chris@1448 4052 MainWindow::coloursChanged()
Chris@1448 4053 {
Chris@1448 4054 QSettings settings;
Chris@1448 4055 settings.beginGroup("Preferences");
Chris@2466 4056
Chris@2466 4057 bool haveDarkBackground = (m_viewManager &&
Chris@2466 4058 m_viewManager->getGlobalDarkBackground());
Chris@2466 4059 QColor highlight = QApplication::palette().color(QPalette::Highlight);
Chris@1448 4060 ColourDatabase *cdb = ColourDatabase::getInstance();
Chris@2466 4061 int nearestIndex = cdb->getNearbyColourIndex
Chris@2466 4062 (highlight,
Chris@2466 4063 haveDarkBackground ?
Chris@2466 4064 ColourDatabase::WithDarkBackground :
Chris@2466 4065 ColourDatabase::WithLightBackground);
Chris@2466 4066 QString defaultColourName = cdb->getColourName(nearestIndex);
Chris@2466 4067
cannam@1463 4068 QColor colour = QColor
cannam@1463 4069 (settings.value("overview-colour",
cannam@1463 4070 cdb->getColour(defaultColourName).name()).toString());
Chris@1448 4071 settings.endGroup();
Chris@1448 4072
Chris@1448 4073 int index = cdb->getColourIndex(colour);
Chris@2466 4074
Chris@2593 4075 SVDEBUG << "MainWindow::coloursChanged: haveDarkBackground = " << haveDarkBackground << ", highlight = " << highlight.name() << ", nearestIndex = " << nearestIndex << ", defaultColourName = " << defaultColourName << ", colour = " << colour.name() << ", index = " << index << endl;
Chris@2466 4076
Chris@1448 4077 if (index >= 0) {
Chris@1448 4078 m_panLayer->setBaseColour(index);
Chris@1448 4079 }
Chris@1448 4080 }
Chris@1448 4081
Chris@1448 4082 void
Chris@239 4083 MainWindow::propertyStacksResized(int width)
Chris@239 4084 {
Chris@438 4085 // SVDEBUG << "MainWindow::propertyStacksResized(" << width << ")" << endl;
Chris@239 4086
Chris@239 4087 if (!m_playControlsSpacer) return;
Chris@239 4088
Chris@239 4089 int spacerWidth = width - m_playControlsWidth - 4;
Chris@239 4090
Chris@438 4091 // SVDEBUG << "resizing spacer from " << m_playControlsSpacer->width() << " to " << spacerWidth << endl;
Chris@239 4092
Chris@239 4093 m_playControlsSpacer->setFixedSize(QSize(spacerWidth, 2));
Chris@239 4094 }
Chris@239 4095
Chris@239 4096 void
Chris@0 4097 MainWindow::addPane()
Chris@0 4098 {
Chris@0 4099 QObject *s = sender();
Chris@0 4100 QAction *action = dynamic_cast<QAction *>(s);
Chris@753 4101
Chris@753 4102 cerr << "addPane: sender is " << s << ", action is " << action << ", name " << action->text() << endl;
Chris@0 4103
Chris@0 4104 if (!action) {
Chris@1770 4105 cerr << "WARNING: MainWindow::addPane: sender is not an action"
Chris@1770 4106 << endl;
Chris@1770 4107 return;
Chris@0 4108 }
Chris@0 4109
Chris@2093 4110 PaneActions::iterator i = m_paneActions.begin();
Chris@2093 4111 while (i != m_paneActions.end()) {
Chris@2093 4112 if (i->first == action) break;
Chris@2093 4113 ++i;
Chris@2093 4114 }
Chris@0 4115
Chris@0 4116 if (i == m_paneActions.end()) {
Chris@1770 4117 cerr << "WARNING: MainWindow::addPane: unknown action "
Chris@2093 4118 << action->objectName() << endl;
Chris@753 4119 cerr << "known actions are:" << endl;
Chris@2093 4120 for (PaneActions::const_iterator i = m_paneActions.begin();
Chris@753 4121 i != m_paneActions.end(); ++i) {
Chris@753 4122 cerr << i->first << ", name " << i->first->text() << endl;
Chris@753 4123 }
Chris@1770 4124 return;
Chris@0 4125 }
Chris@0 4126
Chris@69 4127 addPane(i->second, action->text());
Chris@69 4128 }
Chris@69 4129
Chris@69 4130 void
Chris@232 4131 MainWindow::addPane(const LayerConfiguration &configuration, QString text)
Chris@69 4132 {
Chris@69 4133 CommandHistory::getInstance()->startCompoundOperation(text, true);
Chris@0 4134
Chris@0 4135 AddPaneCommand *command = new AddPaneCommand(this);
Chris@0 4136 CommandHistory::getInstance()->addCommand(command);
Chris@0 4137
Chris@0 4138 Pane *pane = command->getPane();
Chris@0 4139
Chris@69 4140 if (configuration.layer == LayerFactory::Spectrum) {
Chris@109 4141 pane->setPlaybackFollow(PlaybackScrollContinuous);
Chris@110 4142 pane->setFollowGlobalZoom(false);
Chris@2011 4143 pane->setZoomLevel(ZoomLevel(ZoomLevel::FramesPerPixel, 512));
Chris@7 4144 }
Chris@7 4145
Chris@69 4146 if (configuration.layer != LayerFactory::TimeRuler &&
Chris@69 4147 configuration.layer != LayerFactory::Spectrum) {
Chris@8 4148
Chris@1770 4149 if (!m_timeRulerLayer) {
Chris@1770 4150 // cerr << "no time ruler layer, creating one" << endl;
Chris@1770 4151 m_timeRulerLayer = m_document->createMainModelLayer
Chris@1770 4152 (LayerFactory::TimeRuler);
Chris@1770 4153 }
Chris@1770 4154
Chris@1770 4155 // SVDEBUG << "adding time ruler layer " << m_timeRulerLayer << endl;
Chris@1770 4156
Chris@1770 4157 m_document->addLayerToView(pane, m_timeRulerLayer);
Chris@0 4158 }
Chris@0 4159
Chris@69 4160 Layer *newLayer = m_document->createLayer(configuration.layer);
Chris@69 4161
Chris@2300 4162 ModelId suggestedModelId = configuration.sourceModel;
Chris@2300 4163 ModelId modelId;
Chris@2300 4164
Chris@2300 4165 if (!suggestedModelId.isNone()) {
Chris@66 4166
Chris@66 4167 // check its validity
Chris@2300 4168 std::vector<ModelId> inputModels = m_document->getTransformInputModels();
Chris@2300 4169 for (auto im: inputModels) {
Chris@2300 4170 if (im == suggestedModelId) {
Chris@2300 4171 modelId = suggestedModelId;
Chris@66 4172 }
Chris@66 4173 }
Chris@66 4174
Chris@2300 4175 if (modelId.isNone()) {
Chris@2300 4176 cerr << "WARNING: Model " << modelId
Chris@2300 4177 << " appears in pane action map, but is not reported "
Chris@2300 4178 << "by document as a valid transform source" << endl;
Chris@66 4179 }
Chris@66 4180 }
Chris@66 4181
Chris@2300 4182 if (modelId.isNone()) {
Chris@2300 4183 modelId = m_document->getMainModel();
Chris@346 4184 }
Chris@66 4185
Chris@2300 4186 m_document->setModel(newLayer, modelId);
Chris@66 4187
Chris@69 4188 m_document->setChannel(newLayer, configuration.channel);
Chris@0 4189 m_document->addLayerToView(pane, newLayer);
Chris@0 4190
Chris@0 4191 m_paneStack->setCurrentPane(pane);
Chris@70 4192 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@0 4193
Chris@438 4194 // SVDEBUG << "MainWindow::addPane: global centre frame is "
Chris@433 4195 // << m_viewManager->getGlobalCentreFrame() << endl;
Chris@130 4196 // pane->setCentreFrame(m_viewManager->getGlobalCentreFrame());
Chris@73 4197
Chris@0 4198 CommandHistory::getInstance()->endCompoundOperation();
Chris@0 4199
Chris@0 4200 updateMenuStates();
Chris@0 4201 }
Chris@0 4202
Chris@0 4203 void
Chris@0 4204 MainWindow::addLayer()
Chris@0 4205 {
Chris@0 4206 QObject *s = sender();
Chris@0 4207 QAction *action = dynamic_cast<QAction *>(s);
Chris@0 4208
Chris@0 4209 if (!action) {
Chris@1770 4210 cerr << "WARNING: MainWindow::addLayer: sender is not an action"
Chris@1770 4211 << endl;
Chris@1770 4212 return;
Chris@0 4213 }
Chris@0 4214
Chris@0 4215 Pane *pane = m_paneStack->getCurrentPane();
Chris@0 4216
Chris@0 4217 if (!pane) {
Chris@1770 4218 cerr << "WARNING: MainWindow::addLayer: no current pane" << endl;
Chris@1770 4219 return;
Chris@0 4220 }
Chris@0 4221
Chris@2093 4222 ExistingLayerActions::iterator ei = m_existingLayerActions.begin();
Chris@2093 4223 while (ei != m_existingLayerActions.end()) {
Chris@2093 4224 if (ei->first == action) break;
Chris@2093 4225 ++ei;
Chris@2093 4226 }
Chris@0 4227
Chris@0 4228 if (ei != m_existingLayerActions.end()) {
Chris@1770 4229 Layer *newLayer = ei->second;
Chris@1770 4230 m_document->addLayerToView(pane, newLayer);
Chris@1770 4231 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@1770 4232 return;
Chris@0 4233 }
Chris@0 4234
Chris@2093 4235 ei = m_sliceActions.begin();
Chris@2093 4236 while (ei != m_sliceActions.end()) {
Chris@2093 4237 if (ei->first == action) break;
Chris@2093 4238 ++ei;
Chris@2093 4239 }
Chris@95 4240
Chris@95 4241 if (ei != m_sliceActions.end()) {
Chris@95 4242 Layer *newLayer = m_document->createLayer(LayerFactory::Slice);
Chris@95 4243 // document->setModel(newLayer, ei->second->getModel());
Chris@95 4244 SliceableLayer *source = dynamic_cast<SliceableLayer *>(ei->second);
Chris@95 4245 SliceLayer *dest = dynamic_cast<SliceLayer *>(newLayer);
Chris@95 4246 if (source && dest) {
Chris@205 4247 //!!!???
Chris@95 4248 dest->setSliceableModel(source->getSliceableModel());
Chris@95 4249 connect(source, SIGNAL(sliceableModelReplaced(const Model *, const Model *)),
Chris@95 4250 dest, SLOT(sliceableModelReplaced(const Model *, const Model *)));
Chris@95 4251 connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)),
Chris@95 4252 dest, SLOT(modelAboutToBeDeleted(Model *)));
Chris@95 4253 }
Chris@1770 4254 m_document->addLayerToView(pane, newLayer);
Chris@1770 4255 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@1770 4256 return;
Chris@95 4257 }
Chris@95 4258
Chris@2093 4259 TransformActions::iterator i = m_transformActions.begin();
Chris@2093 4260 while (i != m_transformActions.end()) {
Chris@2093 4261 if (i->first == action) break;
Chris@2093 4262 ++i;
Chris@2093 4263 }
Chris@34 4264
Chris@34 4265 if (i == m_transformActions.end()) {
Chris@0 4266
Chris@2093 4267 LayerActions::iterator i = m_layerActions.begin();
Chris@2093 4268 while (i != m_layerActions.end()) {
Chris@2093 4269 if (i->first == action) break;
Chris@2093 4270 ++i;
Chris@2093 4271 }
Chris@1770 4272
Chris@1770 4273 if (i == m_layerActions.end()) {
Chris@1770 4274 cerr << "WARNING: MainWindow::addLayer: unknown action "
Chris@1770 4275 << action->objectName() << endl;
Chris@1770 4276 return;
Chris@1770 4277 }
Chris@1770 4278
Chris@1770 4279 LayerFactory::LayerType type = i->second.layer;
Chris@1770 4280
Chris@1770 4281 LayerFactory::LayerTypeSet emptyTypes =
Chris@1770 4282 LayerFactory::getInstance()->getValidEmptyLayerTypes();
Chris@1770 4283
Chris@2126 4284 Layer *newLayer = nullptr;
Chris@1770 4285
Chris@2332 4286 bool isNewEmptyLayer = false;
Chris@2332 4287
Chris@1770 4288 if (emptyTypes.find(type) != emptyTypes.end()) {
Chris@1770 4289
Chris@1770 4290 newLayer = m_document->createEmptyLayer(type);
Chris@217 4291 if (newLayer) {
Chris@2332 4292 isNewEmptyLayer = true;
Chris@217 4293 }
Chris@0 4294
Chris@1770 4295 } else {
Chris@0 4296
Chris@2300 4297 ModelId modelId = i->second.sourceModel;
Chris@2300 4298
Chris@2300 4299 if (modelId.isNone()) {
Chris@346 4300 if (type == LayerFactory::TimeRuler) {
Chris@346 4301 newLayer = m_document->createMainModelLayer(type);
Chris@346 4302 } else {
Chris@346 4303 // if model is unspecified and this is not a
Chris@415 4304 // time-ruler layer, use any plausible model from
Chris@415 4305 // the current pane -- this is the case for
Chris@415 4306 // right-button menu layer additions
Chris@415 4307 Pane::ModelSet ms = pane->getModels();
Chris@2300 4308 for (ModelId m: ms) {
Chris@2300 4309 if (ModelById::isa<RangeSummarisableTimeValueModel>(m)) {
Chris@2300 4310 modelId = m;
Chris@2300 4311 }
Chris@346 4312 }
Chris@2300 4313 if (modelId.isNone()) {
Chris@2300 4314 modelId = getMainModelId();
Chris@2300 4315 }
Chris@346 4316 }
Chris@346 4317 }
Chris@346 4318
Chris@2300 4319 if (!modelId.isNone()) {
Chris@238 4320 newLayer = m_document->createLayer(type);
Chris@2300 4321 if (m_document->isKnownModel(modelId)) {
Chris@238 4322 m_document->setChannel(newLayer, i->second.channel);
Chris@2300 4323 m_document->setModel(newLayer, modelId);
Chris@238 4324 } else {
Chris@2300 4325 SVCERR << "WARNING: MainWindow::addLayer: unknown model "
Chris@2300 4326 << modelId << " in layer action map" << endl;
Chris@238 4327 }
Chris@232 4328 }
Chris@238 4329 }
Chris@0 4330
Chris@2332 4331 if (isNewEmptyLayer) {
Chris@2332 4332
Chris@2370 4333 double min, max;
Chris@2370 4334 bool log;
Chris@2332 4335 QString unit;
Chris@2370 4336 if (pane->getVisibleExtentsForAnyUnit(min, max, log, unit)) {
Chris@2370 4337 newLayer->adoptExtents(min, max, unit);
Chris@2332 4338 }
Chris@2332 4339
Chris@2332 4340 for (auto &a : m_toolActions) {
Chris@2332 4341 if (a.first == ViewManager::DrawMode) {
Chris@2332 4342 a.second->trigger();
Chris@2332 4343 break;
Chris@2332 4344 }
Chris@2332 4345 }
Chris@2332 4346 }
Chris@2332 4347
Chris@217 4348 if (newLayer) {
Chris@217 4349 m_document->addLayerToView(pane, newLayer);
Chris@217 4350 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@217 4351 }
Chris@0 4352
Chris@1770 4353 return;
Chris@0 4354 }
Chris@0 4355
Chris@224 4356 //!!! want to do something like this, but it's not supported in
Chris@224 4357 //ModelTransformerFactory yet
Chris@224 4358 /*
Chris@0 4359 int channel = -1;
Chris@0 4360 // pick up the default channel from any existing layers on the same pane
Chris@0 4361 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@1770 4362 int c = LayerFactory::getInstance()->getChannel(pane->getLayer(j));
Chris@1770 4363 if (c != -1) {
Chris@1770 4364 channel = c;
Chris@1770 4365 break;
Chris@1770 4366 }
Chris@0 4367 }
Chris@224 4368 */
Chris@0 4369
Chris@33 4370 // We always ask for configuration, even if the plugin isn't
Chris@33 4371 // supposed to be configurable, because we need to let the user
Chris@33 4372 // change the execution context (block size etc).
Chris@0 4373
Chris@224 4374 QString transformId = i->second;
Chris@274 4375
Chris@274 4376 addLayer(transformId);
Chris@274 4377 }
Chris@274 4378
Chris@274 4379 void
Chris@274 4380 MainWindow::addLayer(QString transformId)
Chris@274 4381 {
Chris@274 4382 Pane *pane = m_paneStack->getCurrentPane();
Chris@274 4383 if (!pane) {
Chris@1770 4384 cerr << "WARNING: MainWindow::addLayer: no current pane" << endl;
Chris@1770 4385 return;
Chris@274 4386 }
Chris@274 4387
Chris@1558 4388 Transform transform;
Chris@1558 4389 try {
Chris@1558 4390 transform = TransformFactory::getInstance()->
Chris@1558 4391 getDefaultTransformFor(transformId);
Chris@1558 4392 } catch (std::exception &e) { // e.g. Piper server failure
Chris@1558 4393 QMessageBox::critical
Chris@1558 4394 (this, tr("Failed to query transform attributes"),
Chris@1558 4395 tr("<b>Failed to query transform attributes</b><p>Plugin or server error: %1</p>")
Chris@1558 4396 .arg(e.what()));
Chris@1558 4397 return;
Chris@1558 4398 }
Chris@27 4399
Chris@2300 4400 std::vector<ModelId> candidateInputModels =
Chris@224 4401 m_document->getTransformInputModels();
Chris@53 4402
Chris@2300 4403 ModelId defaultInputModelId;
Chris@895 4404
Chris@219 4405 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@895 4406
Chris@219 4407 Layer *layer = pane->getLayer(j);
Chris@219 4408 if (!layer) continue;
Chris@895 4409
Chris@243 4410 if (LayerFactory::getInstance()->getLayerType(layer) !=
Chris@243 4411 LayerFactory::Waveform &&
Chris@2300 4412 !layer->isLayerOpaque()) {
Chris@2300 4413 continue;
Chris@2300 4414 }
Chris@2300 4415
Chris@2300 4416 ModelId modelId = layer->getModel();
Chris@2300 4417 if (modelId.isNone()) continue;
Chris@2300 4418
Chris@2300 4419 for (ModelId candidateId: candidateInputModels) {
Chris@2300 4420 if (candidateId == modelId) {
Chris@2300 4421 defaultInputModelId = modelId;
Chris@219 4422 break;
Chris@219 4423 }
Chris@219 4424 }
Chris@895 4425
Chris@2300 4426 if (!defaultInputModelId.isNone()) break;
Chris@219 4427 }
Chris@895 4428
Chris@2300 4429 ModelId aggregate;
Chris@1620 4430
Chris@895 4431 if (candidateInputModels.size() > 1) {
Chris@895 4432 // Add an aggregate model as another option
Chris@895 4433 AggregateWaveModel::ChannelSpecList sl;
Chris@2300 4434 for (ModelId mid: candidateInputModels) {
Chris@2300 4435 if (ModelById::isa<RangeSummarisableTimeValueModel>(mid)) {
Chris@2300 4436 sl.push_back(AggregateWaveModel::ModelChannelSpec(mid, -1));
Chris@895 4437 }
Chris@895 4438 }
Chris@895 4439 if (!sl.empty()) {
Chris@2300 4440 auto aggregate = std::make_shared<AggregateWaveModel>(sl);
Chris@895 4441 aggregate->setObjectName(tr("Multiplex all of the above"));
Chris@2300 4442 candidateInputModels.push_back(ModelById::add(aggregate));
Chris@895 4443 }
Chris@895 4444 }
Chris@219 4445
Chris@914 4446 sv_frame_t startFrame = 0, duration = 0;
Chris@914 4447 sv_frame_t endFrame = 0;
Chris@184 4448 m_viewManager->getSelection().getExtents(startFrame, endFrame);
Chris@184 4449 if (endFrame > startFrame) duration = endFrame - startFrame;
Chris@184 4450 else startFrame = 0;
Chris@184 4451
Chris@357 4452 TransformUserConfigurator configurator;
Chris@357 4453
Chris@224 4454 ModelTransformer::Input input = ModelTransformerFactory::getInstance()->
Chris@224 4455 getConfigurationForTransform
Chris@211 4456 (transform,
Chris@211 4457 candidateInputModels,
Chris@2300 4458 defaultInputModelId,
Chris@211 4459 m_playSource,
Chris@211 4460 startFrame,
Chris@357 4461 duration,
Chris@357 4462 &configurator);
Chris@211 4463
Chris@2300 4464 if (!aggregate.isNone()) {
Chris@1623 4465 if (input.getModel() == aggregate) {
Chris@2300 4466 if (auto aggregateModel = ModelById::get(aggregate)) {
Chris@2300 4467 aggregateModel->setObjectName(tr("Multiplexed audio"));
Chris@2300 4468 }
Chris@2303 4469 m_document->addNonDerivedModel(aggregate);
Chris@1623 4470 } else {
Chris@2300 4471 ModelById::release(aggregate);
Chris@1623 4472 }
Chris@1620 4473 }
Chris@1620 4474
Chris@2300 4475 if (input.getModel().isNone()) return;
Chris@224 4476
Chris@438 4477 // SVDEBUG << "MainWindow::addLayer: Input model is " << input.getModel() << " \"" << input.getModel()->objectName() << "\"" << endl << "transform:" << endl << transform.toXmlString() << endl;
Chris@224 4478
Chris@1558 4479 try {
Chris@1558 4480 Layer *newLayer = m_document->createDerivedLayer(transform, input);
Chris@1558 4481 if (newLayer) {
Chris@1558 4482 m_document->addLayerToView(pane, newLayer);
Chris@1558 4483 m_document->setChannel(newLayer, input.getChannel());
Chris@1558 4484 m_recentTransforms.add(transformId);
Chris@1558 4485 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@1558 4486 }
Chris@1558 4487 } catch (std::exception &e) { // e.g. Piper server failure
Chris@1558 4488 QMessageBox::critical
Chris@1558 4489 (this, tr("Transform failed"),
Chris@1558 4490 tr("<b>Failed to run transform</b><p>Plugin or server error: %1</p>")
Chris@1558 4491 .arg(e.what()));
Chris@1558 4492 return;
Chris@0 4493 }
Chris@1558 4494
Chris@0 4495 updateMenuStates();
Chris@0 4496 }
Chris@0 4497
Chris@0 4498 void
Chris@0 4499 MainWindow::renameCurrentLayer()
Chris@0 4500 {
Chris@0 4501 Pane *pane = m_paneStack->getCurrentPane();
Chris@2480 4502 if (!pane) return;
Chris@2480 4503
Chris@2480 4504 Layer *layer = pane->getSelectedLayer();
Chris@2480 4505 if (!layer) return;
Chris@2480 4506
Chris@2480 4507 bool ok = false;
Chris@2480 4508 QString newName = QInputDialog::getText
Chris@2480 4509 (this, tr("Rename Layer"),
Chris@2480 4510 tr("New name for this layer:"),
Chris@2480 4511 QLineEdit::Normal, layer->objectName(), &ok);
Chris@2480 4512 if (!ok) return;
Chris@2480 4513
Chris@2480 4514 bool existingNameSet = layer->isPresentationNameSet();
Chris@2480 4515 QString existingName = layer->getLayerPresentationName();
Chris@2480 4516
Chris@2480 4517 CommandHistory::getInstance()->addCommand
Chris@2480 4518 (new GenericCommand
Chris@2480 4519 (tr("Rename Layer"),
Chris@2480 4520 [=]() {
Chris@2480 4521 layer->setPresentationName(newName);
Chris@2480 4522 setupExistingLayersMenus();
Chris@2480 4523 },
Chris@2480 4524 [=]() {
Chris@2480 4525 layer->setPresentationName(existingNameSet ? existingName : "");
Chris@2480 4526 setupExistingLayersMenus();
Chris@2480 4527 }));
Chris@0 4528 }
Chris@0 4529
Chris@0 4530 void
Chris@272 4531 MainWindow::findTransform()
Chris@272 4532 {
Chris@274 4533 TransformFinder *finder = new TransformFinder(this);
Chris@274 4534 if (!finder->exec()) {
Chris@274 4535 delete finder;
Chris@274 4536 return;
Chris@273 4537 }
Chris@274 4538 TransformId transform = finder->getTransform();
Chris@274 4539 delete finder;
Chris@287 4540
Chris@2126 4541 if (getMainModel() != nullptr && m_paneStack->getCurrentPane() != nullptr) {
Chris@287 4542 addLayer(transform);
Chris@287 4543 }
Chris@272 4544 }
Chris@272 4545
Chris@272 4546 void
Chris@207 4547 MainWindow::playSoloToggled()
Chris@207 4548 {
Chris@207 4549 MainWindowBase::playSoloToggled();
Chris@207 4550 m_soloModified = true;
Chris@207 4551 }
Chris@207 4552
Chris@207 4553 void
Chris@206 4554 MainWindow::alignToggled()
Chris@206 4555 {
Chris@206 4556 QAction *action = dynamic_cast<QAction *>(sender());
Chris@206 4557
Chris@207 4558 if (!m_viewManager) return;
Chris@207 4559
Chris@206 4560 if (action) {
Chris@1770 4561 m_viewManager->setAlignMode(action->isChecked());
Chris@206 4562 } else {
Chris@1770 4563 m_viewManager->setAlignMode(!m_viewManager->getAlignMode());
Chris@206 4564 }
Chris@206 4565
Chris@206 4566 if (m_viewManager->getAlignMode()) {
Chris@207 4567 m_prevSolo = m_soloAction->isChecked();
Chris@208 4568 if (!m_soloAction->isChecked()) {
Chris@208 4569 m_soloAction->setChecked(true);
Chris@208 4570 MainWindowBase::playSoloToggled();
Chris@208 4571 }
Chris@207 4572 m_soloModified = false;
Chris@207 4573 emit canChangeSolo(false);
Chris@206 4574 m_document->alignModels();
Chris@206 4575 m_document->setAutoAlignment(true);
Chris@206 4576 } else {
Chris@207 4577 if (!m_soloModified) {
Chris@208 4578 if (m_soloAction->isChecked() != m_prevSolo) {
Chris@208 4579 m_soloAction->setChecked(m_prevSolo);
Chris@208 4580 MainWindowBase::playSoloToggled();
Chris@208 4581 }
Chris@207 4582 }
Chris@207 4583 emit canChangeSolo(true);
Chris@206 4584 m_document->setAutoAlignment(false);
Chris@206 4585 }
Chris@206 4586
Chris@206 4587 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@206 4588
Chris@1770 4589 Pane *pane = m_paneStack->getPane(i);
Chris@1770 4590 if (!pane) continue;
Chris@206 4591
Chris@206 4592 pane->update();
Chris@206 4593 }
Chris@206 4594 }
Chris@206 4595
Chris@206 4596 void
Chris@59 4597 MainWindow::playSpeedChanged(int position)
Chris@0 4598 {
Chris@1031 4599 PlaySpeedRangeMapper mapper;
Chris@60 4600
Chris@922 4601 double percent = m_playSpeed->mappedValue();
Chris@922 4602 double factor = mapper.getFactorForValue(percent);
Chris@60 4603
Chris@1031 4604 // cerr << "play speed position = " << position << " (range 0-120) percent = " << percent << " factor = " << factor << endl;
Chris@1031 4605
Chris@1031 4606 int centre = m_playSpeed->defaultValue();
Chris@1031 4607
Chris@1031 4608 // Percentage is shown to 0dp if >100, to 1dp if <100; factor is
Chris@1031 4609 // shown to 3sf
Chris@1031 4610
Chris@1031 4611 char pcbuf[30];
Chris@1031 4612 char facbuf[30];
Chris@1031 4613
Chris@1031 4614 if (position == centre) {
Chris@155 4615 contextHelpChanged(tr("Playback speed: Normal"));
Chris@1031 4616 } else if (position < centre) {
Chris@1031 4617 sprintf(pcbuf, "%.1f", percent);
Chris@1031 4618 sprintf(facbuf, "%.3g", 1.0 / factor);
Chris@1031 4619 contextHelpChanged(tr("Playback speed: %1% (%2x slower)")
Chris@1031 4620 .arg(pcbuf)
Chris@1031 4621 .arg(facbuf));
Chris@155 4622 } else {
Chris@1031 4623 sprintf(pcbuf, "%.0f", percent);
Chris@1031 4624 sprintf(facbuf, "%.3g", factor);
Chris@1031 4625 contextHelpChanged(tr("Playback speed: %1% (%2x faster)")
Chris@1031 4626 .arg(pcbuf)
Chris@1031 4627 .arg(facbuf));
Chris@155 4628 }
Chris@155 4629
Chris@1031 4630 m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup
Chris@155 4631
Chris@155 4632 updateMenuStates();
Chris@16 4633 }
Chris@16 4634
Chris@26 4635 void
Chris@155 4636 MainWindow::speedUpPlayback()
Chris@155 4637 {
Chris@155 4638 int value = m_playSpeed->value();
Chris@155 4639 value = value + m_playSpeed->pageStep();
Chris@155 4640 if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum();
Chris@155 4641 m_playSpeed->setValue(value);
Chris@155 4642 }
Chris@155 4643
Chris@155 4644 void
Chris@155 4645 MainWindow::slowDownPlayback()
Chris@155 4646 {
Chris@155 4647 int value = m_playSpeed->value();
Chris@155 4648 value = value - m_playSpeed->pageStep();
Chris@155 4649 if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum();
Chris@155 4650 m_playSpeed->setValue(value);
Chris@155 4651 }
Chris@155 4652
Chris@155 4653 void
Chris@155 4654 MainWindow::restoreNormalPlayback()
Chris@155 4655 {
Chris@155 4656 m_playSpeed->setValue(m_playSpeed->defaultValue());
Chris@155 4657 }
Chris@155 4658
Chris@155 4659 void
Chris@227 4660 MainWindow::currentPaneChanged(Pane *pane)
Chris@227 4661 {
Chris@228 4662 MainWindowBase::currentPaneChanged(pane);
Chris@228 4663
Chris@227 4664 if (!pane || !m_panLayer) return;
Chris@761 4665
Chris@761 4666 // If this pane contains the main model, it usually makes sense to
Chris@761 4667 // show the main model in the pan layer even if it isn't the top
Chris@761 4668 // layer in the pane (e.g. if the top layer is one derived from
Chris@761 4669 // the main model).
Chris@761 4670 bool containsMainModel = false;
Chris@761 4671 for (int i = pane->getLayerCount(); i > 0; ) {
Chris@761 4672 --i;
Chris@761 4673 Layer *layer = pane->getLayer(i);
Chris@761 4674 if (layer &&
Chris@761 4675 LayerFactory::getInstance()->getLayerType(layer) ==
Chris@761 4676 LayerFactory::Waveform &&
Chris@2300 4677 layer->getModel() == getMainModelId()) {
Chris@761 4678 containsMainModel = true;
Chris@761 4679 break;
Chris@761 4680 }
Chris@761 4681 }
Chris@761 4682
Chris@1794 4683 bool panLayerSet = false;
Chris@1794 4684
Chris@227 4685 for (int i = pane->getLayerCount(); i > 0; ) {
Chris@227 4686 --i;
Chris@227 4687 Layer *layer = pane->getLayer(i);
Chris@2300 4688 ModelId modelId = layer->getModel();
Chris@2300 4689 if (ModelById::isa<RangeSummarisableTimeValueModel>(modelId)) {
Chris@1794 4690 auto type = LayerFactory::getInstance()->getLayerType(layer);
Chris@1794 4691 if (type != LayerFactory::TimeRuler) {
Chris@2300 4692 updateLayerShortcutsFor(modelId);
Chris@1794 4693 }
Chris@1794 4694 if (type == LayerFactory::Waveform) {
Chris@2300 4695 m_panLayer->setModel(modelId);
Chris@1794 4696 panLayerSet = true;
Chris@1794 4697 break;
Chris@227 4698 }
Chris@227 4699 }
Chris@227 4700 }
Chris@1794 4701
Chris@1794 4702 if (containsMainModel && !panLayerSet) {
Chris@2300 4703 m_panLayer->setModel(getMainModelId());
Chris@1794 4704 }
Chris@227 4705 }
Chris@227 4706
Chris@227 4707 void
Chris@116 4708 MainWindow::updateVisibleRangeDisplay(Pane *p) const
Chris@116 4709 {
Chris@2300 4710 sv_samplerate_t sampleRate = 0;
Chris@2300 4711 if (auto mm = getMainModel()) {
Chris@2300 4712 sampleRate = mm->getSampleRate();
Chris@2300 4713 } else {
Chris@2300 4714 return;
Chris@2300 4715 }
Chris@2300 4716 if (!p) {
Chris@116 4717 return;
Chris@116 4718 }
Chris@116 4719
Chris@117 4720 bool haveSelection = false;
Chris@922 4721 sv_frame_t startFrame = 0, endFrame = 0;
Chris@117 4722
Chris@117 4723 if (m_viewManager && m_viewManager->haveInProgressSelection()) {
Chris@117 4724
Chris@117 4725 bool exclusive = false;
Chris@117 4726 Selection s = m_viewManager->getInProgressSelection(exclusive);
Chris@117 4727
Chris@117 4728 if (!s.isEmpty()) {
Chris@117 4729 haveSelection = true;
Chris@117 4730 startFrame = s.getStartFrame();
Chris@117 4731 endFrame = s.getEndFrame();
Chris@117 4732 }
Chris@117 4733 }
Chris@117 4734
Chris@117 4735 if (!haveSelection) {
Chris@117 4736 startFrame = p->getFirstVisibleFrame();
Chris@117 4737 endFrame = p->getLastVisibleFrame();
Chris@117 4738 }
Chris@117 4739
Chris@2300 4740 RealTime start = RealTime::frame2RealTime(startFrame, sampleRate);
Chris@2300 4741 RealTime end = RealTime::frame2RealTime(endFrame, sampleRate);
Chris@116 4742 RealTime duration = end - start;
Chris@116 4743
Chris@116 4744 QString startStr, endStr, durationStr;
Chris@116 4745 startStr = start.toText(true).c_str();
Chris@116 4746 endStr = end.toText(true).c_str();
Chris@116 4747 durationStr = duration.toText(true).c_str();
Chris@116 4748
Chris@117 4749 if (haveSelection) {
Chris@117 4750 m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)")
Chris@117 4751 .arg(startStr).arg(endStr).arg(durationStr);
Chris@117 4752 } else {
Chris@117 4753 m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)")
Chris@117 4754 .arg(startStr).arg(endStr).arg(durationStr);
Chris@117 4755 }
Chris@116 4756
Chris@739 4757 if (getStatusLabel()->text() != m_myStatusMessage) {
Chris@739 4758 getStatusLabel()->setText(m_myStatusMessage);
Chris@737 4759 }
Chris@340 4760
Chris@340 4761 updatePositionStatusDisplays();
Chris@340 4762 }
Chris@340 4763
Chris@340 4764 void
Chris@340 4765 MainWindow::updatePositionStatusDisplays() const
Chris@340 4766 {
Chris@340 4767 if (!statusBar()->isVisible()) return;
Chris@340 4768
Chris@2126 4769 Pane *pane = nullptr;
Chris@922 4770 sv_frame_t frame = m_viewManager->getPlaybackFrame();
Chris@340 4771
Chris@340 4772 if (m_paneStack) pane = m_paneStack->getCurrentPane();
Chris@340 4773 if (!pane) return;
Chris@340 4774
Chris@340 4775 int layers = pane->getLayerCount();
Chris@340 4776 if (layers == 0) m_currentLabel->setText("");
Chris@340 4777
Chris@340 4778 for (int i = layers-1; i >= 0; --i) {
Chris@340 4779 Layer *layer = pane->getLayer(i);
Chris@340 4780 if (!layer) continue;
Chris@340 4781 if (!layer->isLayerEditable()) continue;
Chris@340 4782 QString label = layer->getLabelPreceding
Chris@340 4783 (pane->alignFromReference(frame));
Chris@340 4784 m_currentLabel->setText(label);
Chris@340 4785 break;
Chris@340 4786 }
Chris@116 4787 }
Chris@116 4788
Chris@116 4789 void
Chris@1476 4790 MainWindow::monitoringLevelsChanged(float left, float right)
Chris@0 4791 {
Chris@1386 4792 m_mainLevelPan->setMonitoringLevels(left, right);
Chris@0 4793 }
Chris@0 4794
Chris@0 4795 void
Chris@921 4796 MainWindow::sampleRateMismatch(sv_samplerate_t requested,
Chris@921 4797 sv_samplerate_t actual,
Chris@0 4798 bool willResample)
Chris@0 4799 {
Chris@0 4800 if (!willResample) {
Chris@247 4801 emit hideSplash();
Chris@0 4802 QMessageBox::information
Chris@0 4803 (this, tr("Sample rate mismatch"),
Chris@193 4804 tr("<b>Wrong sample rate</b><p>The sample rate of this audio file (%1 Hz) does not match\nthe current playback rate (%2 Hz).<p>The file will play at the wrong speed and pitch.<p>Change the <i>Resample mismatching files on import</i> option under <i>File</i> -> <i>Preferences</i> if you want to alter this behaviour.")
Chris@0 4805 .arg(requested).arg(actual));
Chris@0 4806 }
Chris@0 4807
Chris@0 4808 updateDescriptionLabel();
Chris@0 4809 }
Chris@0 4810
Chris@0 4811 void
Chris@42 4812 MainWindow::audioOverloadPluginDisabled()
Chris@42 4813 {
Chris@42 4814 QMessageBox::information
Chris@42 4815 (this, tr("Audio processing overload"),
Chris@193 4816 tr("<b>Overloaded</b><p>Audio effects plugin auditioning has been disabled due to a processing overload."));
Chris@42 4817 }
Chris@42 4818
Chris@42 4819 void
Chris@1521 4820 MainWindow::betaReleaseWarning()
Chris@1521 4821 {
Chris@1521 4822 QMessageBox::information
Chris@1521 4823 (this, tr("Beta release"),
Chris@2352 4824 tr("<b>This is a beta release of %1</b><p>Please see the \"What's New\" option in the Help menu for a list of changes since the last proper release.</p>").arg(QApplication::applicationName()));
Chris@1521 4825 }
Chris@1521 4826
Chris@1521 4827 void
Chris@2593 4828 MainWindow::pluginPopulationWarning(QString warning)
Chris@1087 4829 {
Chris@2593 4830 emit hideSplash();
Chris@2593 4831 QMessageBox box;
Chris@2593 4832 box.setWindowTitle(tr("Problems loading plugins"));
Chris@2593 4833 box.setText(tr("<b>Failed to load plugins</b>"));
Chris@2593 4834 box.setInformativeText(warning);
Chris@2593 4835 box.setIcon(QMessageBox::Warning);
Chris@2593 4836 box.setStandardButtons(QMessageBox::Ok);
Chris@2593 4837 box.exec();
Chris@1087 4838 }
Chris@1087 4839
Chris@1087 4840 void
Chris@304 4841 MainWindow::midiEventsAvailable()
Chris@304 4842 {
Chris@2126 4843 Pane *currentPane = nullptr;
Chris@2126 4844 NoteLayer *currentNoteLayer = nullptr;
Chris@2126 4845 TimeValueLayer *currentTimeValueLayer = nullptr;
Chris@307 4846
Chris@793 4847 if (m_paneStack) {
Chris@793 4848 currentPane = m_paneStack->getCurrentPane();
Chris@793 4849 }
Chris@793 4850
Chris@307 4851 if (currentPane) {
Chris@307 4852 currentNoteLayer = dynamic_cast<NoteLayer *>
Chris@307 4853 (currentPane->getSelectedLayer());
Chris@309 4854 currentTimeValueLayer = dynamic_cast<TimeValueLayer *>
Chris@309 4855 (currentPane->getSelectedLayer());
Chris@838 4856 } else {
Chris@793 4857 // discard these events
Chris@793 4858 while (m_midiInput->getEventsAvailable() > 0) {
Chris@793 4859 (void)m_midiInput->readEvent();
Chris@793 4860 }
Chris@793 4861 return;
Chris@793 4862 }
Chris@793 4863
Chris@305 4864 // This is called through a serialised signal/slot invocation
Chris@305 4865 // (across threads). It could happen quite some time after the
Chris@305 4866 // event was actually received, which is why event timestamping
Chris@305 4867 // happens in the MIDI input class and not here.
Chris@307 4868
Chris@305 4869 while (m_midiInput->getEventsAvailable() > 0) {
Chris@308 4870
Chris@305 4871 MIDIEvent ev(m_midiInput->readEvent());
Chris@307 4872
Chris@922 4873 sv_frame_t frame = currentPane->alignFromReference(ev.getTime());
Chris@309 4874
Chris@308 4875 bool noteOn = (ev.getMessageType() == MIDIConstants::MIDI_NOTE_ON &&
Chris@308 4876 ev.getVelocity() > 0);
Chris@308 4877
Chris@308 4878 bool noteOff = (ev.getMessageType() == MIDIConstants::MIDI_NOTE_OFF ||
Chris@308 4879 (ev.getMessageType() == MIDIConstants::MIDI_NOTE_ON &&
Chris@308 4880 ev.getVelocity() == 0));
Chris@308 4881
Chris@307 4882 if (currentNoteLayer) {
Chris@307 4883
Chris@310 4884 if (!m_playSource || !m_playSource->isPlaying()) continue;
Chris@310 4885
Chris@308 4886 if (noteOn) {
Chris@307 4887
Chris@309 4888 currentNoteLayer->addNoteOn(frame,
Chris@307 4889 ev.getPitch(),
Chris@307 4890 ev.getVelocity());
Chris@307 4891
Chris@308 4892 } else if (noteOff) {
Chris@307 4893
Chris@309 4894 currentNoteLayer->addNoteOff(frame,
Chris@307 4895 ev.getPitch());
Chris@307 4896
Chris@307 4897 }
Chris@307 4898
Chris@309 4899 continue;
Chris@309 4900 }
Chris@309 4901
Chris@309 4902 if (currentTimeValueLayer) {
Chris@308 4903
Chris@308 4904 if (!noteOn) continue;
Chris@310 4905
Chris@310 4906 if (!m_playSource || !m_playSource->isPlaying()) continue;
Chris@310 4907
Chris@2300 4908 ModelId modelId = currentTimeValueLayer->getModel();
Chris@2300 4909 if (ModelById::isa<SparseTimeValueModel>(modelId)) {
Chris@2226 4910 Event point(frame, float(ev.getPitch() % 12), "");
Chris@2226 4911 AddEventCommand *command = new AddEventCommand
Chris@2300 4912 (modelId.untyped, point, tr("Add Point"));
Chris@309 4913 CommandHistory::getInstance()->addCommand(command);
Chris@309 4914 }
Chris@838 4915
Chris@309 4916 continue;
Chris@305 4917 }
Chris@309 4918
Chris@838 4919 // This is reached only if !currentNoteLayer and
Chris@838 4920 // !currentTimeValueLayer, i.e. there is some other sort of
Chris@838 4921 // layer that may be insertable-into
Chris@838 4922
Chris@309 4923 if (!noteOn) continue;
Chris@309 4924 insertInstantAt(ev.getTime());
Chris@304 4925 }
Chris@304 4926 }
Chris@304 4927
Chris@304 4928 void
Chris@730 4929 MainWindow::playStatusChanged(bool )
Chris@305 4930 {
Chris@2126 4931 Pane *currentPane = nullptr;
Chris@2126 4932 NoteLayer *currentNoteLayer = nullptr;
Chris@307 4933
Chris@307 4934 if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
Chris@307 4935 if (currentPane) {
Chris@307 4936 currentNoteLayer = dynamic_cast<NoteLayer *>(currentPane->getSelectedLayer());
Chris@307 4937 }
Chris@307 4938
Chris@307 4939 if (currentNoteLayer) {
Chris@307 4940 currentNoteLayer->abandonNoteOns();
Chris@307 4941 }
Chris@305 4942 }
Chris@305 4943
Chris@305 4944 void
Chris@200 4945 MainWindow::layerRemoved(Layer *layer)
Chris@0 4946 {
Chris@2538 4947 Profiler profiler("MainWindow::layerRemoved");
Chris@95 4948 setupExistingLayersMenus();
Chris@200 4949 MainWindowBase::layerRemoved(layer);
Chris@0 4950 }
Chris@0 4951
Chris@0 4952 void
Chris@0 4953 MainWindow::layerInAView(Layer *layer, bool inAView)
Chris@0 4954 {
Chris@95 4955 setupExistingLayersMenus();
Chris@200 4956 MainWindowBase::layerInAView(layer, inAView);
Chris@0 4957 }
Chris@0 4958
Chris@0 4959 void
Chris@2300 4960 MainWindow::modelAdded(ModelId modelId)
Chris@0 4961 {
Chris@2300 4962 MainWindowBase::modelAdded(modelId);
Chris@2300 4963 if (ModelById::isa<DenseTimeValueModel>(modelId)) {
Chris@66 4964 setupPaneAndLayerMenus();
Chris@66 4965 }
Chris@0 4966 }
Chris@0 4967
Chris@0 4968 void
Chris@2300 4969 MainWindow::mainModelChanged(ModelId modelId)
Chris@0 4970 {
Chris@2300 4971 m_panLayer->setModel(modelId);
Chris@2300 4972
Chris@2300 4973 MainWindowBase::mainModelChanged(modelId);
Chris@200 4974
Chris@1055 4975 if (m_playTarget || m_audioIO) {
Chris@1386 4976 connect(m_mainLevelPan, SIGNAL(levelChanged(float)),
Chris@1035 4977 this, SLOT(mainModelGainChanged(float)));
Chris@1386 4978 connect(m_mainLevelPan, SIGNAL(panChanged(float)),
Chris@1386 4979 this, SLOT(mainModelPanChanged(float)));
Chris@1035 4980 }
Chris@1035 4981 }
Chris@1035 4982
Chris@1035 4983 void
Chris@1035 4984 MainWindow::mainModelGainChanged(float gain)
Chris@1035 4985 {
Chris@1035 4986 if (m_playTarget) {
Chris@1035 4987 m_playTarget->setOutputGain(gain);
Chris@1055 4988 } else if (m_audioIO) {
Chris@1055 4989 m_audioIO->setOutputGain(gain);
Chris@200 4990 }
Chris@0 4991 }
Chris@0 4992
Chris@0 4993 void
Chris@1386 4994 MainWindow::mainModelPanChanged(float balance)
Chris@1386 4995 {
Chris@1386 4996 // this is indeed stereo balance rather than pan
Chris@1386 4997 if (m_playTarget) {
Chris@1386 4998 m_playTarget->setOutputBalance(balance);
Chris@1386 4999 } else if (m_audioIO) {
Chris@1386 5000 m_audioIO->setOutputBalance(balance);
Chris@1386 5001 }
Chris@1386 5002 }
Chris@1386 5003
Chris@1386 5004 void
Chris@200 5005 MainWindow::setInstantsNumbering()
Chris@0 5006 {
Chris@200 5007 QAction *a = dynamic_cast<QAction *>(sender());
Chris@200 5008 if (!a) return;
Chris@200 5009
Chris@2093 5010 int type = 0;
Chris@2093 5011 for (auto &ai : m_numberingActions) {
Chris@2093 5012 if (ai.first == a) type = ai.second;
Chris@2093 5013 }
Chris@200 5014
Chris@200 5015 if (m_labeller) m_labeller->setType(Labeller::ValueType(type));
Chris@200 5016
Chris@200 5017 QSettings settings;
Chris@200 5018 settings.beginGroup("MainWindow");
Chris@200 5019 settings.setValue("labellertype", type);
Chris@200 5020 settings.endGroup();
Chris@200 5021 }
Chris@200 5022
Chris@200 5023 void
Chris@200 5024 MainWindow::setInstantsCounterCycle()
Chris@200 5025 {
Chris@200 5026 QAction *a = dynamic_cast<QAction *>(sender());
Chris@200 5027 if (!a) return;
Chris@200 5028
Chris@200 5029 int cycle = a->text().toInt();
Chris@200 5030 if (cycle == 0) return;
Chris@200 5031
Chris@200 5032 if (m_labeller) m_labeller->setCounterCycleSize(cycle);
Chris@200 5033
Chris@200 5034 QSettings settings;
Chris@200 5035 settings.beginGroup("MainWindow");
Chris@200 5036 settings.setValue("labellercycle", cycle);
Chris@200 5037 settings.endGroup();
Chris@200 5038 }
Chris@200 5039
Chris@200 5040 void
Chris@597 5041 MainWindow::setInstantsCounters()
Chris@200 5042 {
Chris@200 5043 LabelCounterInputDialog dialog(m_labeller, this);
Chris@241 5044 dialog.setWindowTitle(tr("Reset Counters"));
Chris@200 5045 dialog.exec();
Chris@0 5046 }
Chris@0 5047
Chris@0 5048 void
Chris@597 5049 MainWindow::resetInstantsCounters()
Chris@597 5050 {
Chris@597 5051 if (m_labeller) m_labeller->resetCounters();
Chris@597 5052 }
Chris@597 5053
Chris@597 5054 void
Chris@1355 5055 MainWindow::subdivideInstants()
Chris@1355 5056 {
Chris@1355 5057 QSettings settings;
Chris@1355 5058 settings.beginGroup("MainWindow");
Chris@1355 5059 int n = settings.value("subdivisions", 4).toInt();
Chris@1355 5060
Chris@1355 5061 bool ok;
Chris@1355 5062
Chris@1355 5063 n = QInputDialog::getInt(this,
Chris@1355 5064 tr("Subdivide instants"),
Chris@1355 5065 tr("Number of subdivisions:"),
Chris@1355 5066 n, 2, 96, 1, &ok);
Chris@1355 5067
Chris@1355 5068 if (ok) {
Chris@1355 5069 settings.setValue("subdivisions", n);
Chris@1355 5070 subdivideInstantsBy(n);
Chris@1355 5071 }
Chris@1355 5072
Chris@1355 5073 settings.endGroup();
Chris@1355 5074 }
Chris@1355 5075
Chris@1355 5076 void
Chris@1356 5077 MainWindow::winnowInstants()
Chris@1356 5078 {
Chris@1356 5079 QSettings settings;
Chris@1356 5080 settings.beginGroup("MainWindow");
Chris@1356 5081 int n = settings.value("winnow-subdivisions", 4).toInt();
Chris@1356 5082
Chris@1356 5083 bool ok;
Chris@1356 5084
Chris@1356 5085 n = QInputDialog::getInt(this,
Chris@1356 5086 tr("Winnow instants"),
Chris@1356 5087 tr("Remove all instants apart from multiples of:"),
Chris@1356 5088 n, 2, 96, 1, &ok);
Chris@1356 5089
Chris@1356 5090 if (ok) {
Chris@1356 5091 settings.setValue("winnow-subdivisions", n);
Chris@1356 5092 winnowInstantsBy(n);
Chris@1356 5093 }
Chris@1356 5094
Chris@1356 5095 settings.endGroup();
Chris@1356 5096 }
Chris@1356 5097
Chris@1356 5098 void
Chris@233 5099 MainWindow::modelGenerationFailed(QString transformName, QString message)
Chris@233 5100 {
Chris@247 5101 emit hideSplash();
Chris@247 5102
Chris@983 5103 QString quoted;
Chris@983 5104 if (transformName != "") {
Chris@983 5105 quoted = QString("\"%1\" ").arg(transformName);
Chris@983 5106 }
Chris@983 5107
Chris@233 5108 if (message != "") {
Chris@233 5109
Chris@233 5110 QMessageBox::warning
Chris@233 5111 (this,
Chris@233 5112 tr("Failed to generate layer"),
Chris@983 5113 tr("<b>Layer generation failed</b><p>Failed to generate derived layer.<p>The layer transform %1failed:<p>%2")
Chris@983 5114 .arg(quoted).arg(message),
Chris@233 5115 QMessageBox::Ok);
Chris@233 5116 } else {
Chris@233 5117 QMessageBox::warning
Chris@233 5118 (this,
Chris@233 5119 tr("Failed to generate layer"),
Chris@983 5120 tr("<b>Layer generation failed</b><p>Failed to generate a derived layer.<p>The layer transform %1failed.<p>No error information is available.")
Chris@983 5121 .arg(quoted),
Chris@233 5122 QMessageBox::Ok);
Chris@233 5123 }
Chris@233 5124 }
Chris@233 5125
Chris@233 5126 void
Chris@730 5127 MainWindow::modelGenerationWarning(QString /* transformName */, QString message)
Chris@233 5128 {
Chris@247 5129 emit hideSplash();
Chris@247 5130
Chris@233 5131 QMessageBox::warning
Chris@233 5132 (this, tr("Warning"), message, QMessageBox::Ok);
Chris@233 5133 }
Chris@233 5134
Chris@233 5135 void
Chris@233 5136 MainWindow::modelRegenerationFailed(QString layerName,
Chris@233 5137 QString transformName, QString message)
Chris@233 5138 {
Chris@247 5139 emit hideSplash();
Chris@247 5140
Chris@233 5141 if (message != "") {
Chris@233 5142
Chris@233 5143 QMessageBox::warning
Chris@233 5144 (this,
Chris@233 5145 tr("Failed to regenerate layer"),
Chris@233 5146 tr("<b>Layer generation failed</b><p>Failed to regenerate derived layer \"%1\" using new data model as input.<p>The layer transform \"%2\" failed:<p>%3")
Chris@233 5147 .arg(layerName).arg(transformName).arg(message),
Chris@233 5148 QMessageBox::Ok);
Chris@233 5149 } else {
Chris@233 5150 QMessageBox::warning
Chris@233 5151 (this,
Chris@233 5152 tr("Failed to regenerate layer"),
Chris@233 5153 tr("<b>Layer generation failed</b><p>Failed to regenerate derived layer \"%1\" using new data model as input.<p>The layer transform \"%2\" failed.<p>No error information is available.")
Chris@233 5154 .arg(layerName).arg(transformName),
Chris@233 5155 QMessageBox::Ok);
Chris@233 5156 }
Chris@233 5157 }
Chris@233 5158
Chris@233 5159 void
Chris@233 5160 MainWindow::modelRegenerationWarning(QString layerName,
Chris@730 5161 QString /* transformName */,
Chris@730 5162 QString message)
Chris@233 5163 {
Chris@247 5164 emit hideSplash();
Chris@247 5165
Chris@233 5166 QMessageBox::warning
Chris@233 5167 (this, tr("Warning"), tr("<b>Warning when regenerating layer</b><p>When regenerating the derived layer \"%1\" using new data model as input:<p>%2").arg(layerName).arg(message), QMessageBox::Ok);
Chris@233 5168 }
Chris@233 5169
Chris@233 5170 void
Chris@2537 5171 MainWindow::alignmentFailed(ModelId, QString message)
Chris@0 5172 {
Chris@0 5173 QMessageBox::warning
Chris@0 5174 (this,
Chris@233 5175 tr("Failed to calculate alignment"),
Chris@1151 5176 tr("<b>Alignment calculation failed</b><p>Failed to calculate an audio alignment:<p>%1")
Chris@1151 5177 .arg(message),
Chris@165 5178 QMessageBox::Ok);
Chris@0 5179 }
Chris@0 5180
Chris@0 5181 void
Chris@2475 5182 MainWindow::paneRightButtonMenuRequested(Pane *pane, QPoint position)
Chris@0 5183 {
Chris@0 5184 m_paneStack->setCurrentPane(pane);
Chris@0 5185 m_rightButtonMenu->popup(position);
Chris@0 5186 }
Chris@0 5187
Chris@0 5188 void
Chris@2475 5189 MainWindow::panePropertiesRightButtonMenuRequested(Pane *pane, QPoint position)
Chris@2475 5190 {
Chris@2475 5191 if (m_lastRightButtonPropertyMenu) {
Chris@2475 5192 delete m_lastRightButtonPropertyMenu;
Chris@2475 5193 }
Chris@2475 5194
Chris@2475 5195 QMenu *m = new QMenu;
Chris@2475 5196 IconLoader il;
Chris@2475 5197
Chris@2479 5198 MenuTitle::addTitle(m, tr("Pane"));
Chris@2475 5199
Chris@2528 5200 m_paneStack->setCurrentLayer(pane, nullptr);
Chris@2528 5201
Chris@2528 5202 m->addAction(il.load("editdelete"), tr("&Delete Pane"),
Chris@2528 5203 this, SLOT(deleteCurrentPane()));
Chris@2475 5204
Chris@2475 5205 m->popup(position);
Chris@2475 5206 m_lastRightButtonPropertyMenu = m;
Chris@2475 5207 }
Chris@2475 5208
Chris@2475 5209 void
Chris@2475 5210 MainWindow::layerPropertiesRightButtonMenuRequested(Pane *pane, Layer *layer, QPoint position)
Chris@2475 5211 {
Chris@2475 5212 if (m_lastRightButtonPropertyMenu) {
Chris@2475 5213 delete m_lastRightButtonPropertyMenu;
Chris@2475 5214 }
Chris@2475 5215
Chris@2475 5216 QMenu *m = new QMenu;
Chris@2475 5217 IconLoader il;
Chris@2475 5218
Chris@2479 5219 MenuTitle::addTitle(m, layer->getLayerPresentationName());
Chris@2475 5220
Chris@2528 5221 m_paneStack->setCurrentLayer(pane, layer);
Chris@2528 5222
Chris@2475 5223 m->addAction(tr("&Rename Layer..."),
Chris@2528 5224 this, SLOT(renameCurrentLayer()));
Chris@2475 5225
Chris@2475 5226 m->addAction(tr("Edit Layer Data"),
Chris@2528 5227 this, SLOT(editCurrentLayer()))
Chris@2475 5228 ->setEnabled(layer->isLayerEditable());
Chris@2528 5229
Chris@2528 5230 m->addAction(il.load("editdelete"), tr("&Delete Layer"),
Chris@2528 5231 this, SLOT(deleteCurrentLayer()));
Chris@2475 5232
Chris@2475 5233 m->popup(position);
Chris@2475 5234 m_lastRightButtonPropertyMenu = m;
Chris@2475 5235 }
Chris@2475 5236
Chris@2475 5237 void
Chris@0 5238 MainWindow::showLayerTree()
Chris@0 5239 {
Chris@219 5240 if (!m_layerTreeDialog.isNull()) {
Chris@219 5241 m_layerTreeDialog->show();
Chris@219 5242 m_layerTreeDialog->raise();
Chris@177 5243 return;
Chris@177 5244 }
Chris@177 5245
Chris@762 5246 m_layerTreeDialog = new LayerTreeDialog(m_paneStack, this);
Chris@232 5247 m_layerTreeDialog->setAttribute(Qt::WA_DeleteOnClose); // see below
Chris@219 5248 m_layerTreeDialog->show();
Chris@0 5249 }
Chris@0 5250
Chris@0 5251 void
Chris@306 5252 MainWindow::showActivityLog()
Chris@306 5253 {
Chris@306 5254 m_activityLog->show();
Chris@306 5255 m_activityLog->raise();
Chris@306 5256 m_activityLog->scrollToEnd();
Chris@306 5257 }
Chris@306 5258
Chris@306 5259 void
Chris@891 5260 MainWindow::showUnitConverter()
Chris@891 5261 {
Chris@891 5262 m_unitConverter->show();
Chris@891 5263 m_unitConverter->raise();
Chris@891 5264 }
Chris@891 5265
Chris@891 5266 void
Chris@0 5267 MainWindow::preferences()
Chris@0 5268 {
Chris@436 5269 bool goToTemplateTab =
Chris@436 5270 (sender() && sender()->objectName() == "set_default_template");
Chris@436 5271
Chris@0 5272 if (!m_preferencesDialog.isNull()) {
Chris@0 5273 m_preferencesDialog->show();
Chris@0 5274 m_preferencesDialog->raise();
Chris@436 5275 if (goToTemplateTab) {
Chris@436 5276 m_preferencesDialog->switchToTab(PreferencesDialog::TemplateTab);
Chris@436 5277 }
Chris@0 5278 return;
Chris@0 5279 }
Chris@0 5280
Chris@0 5281 m_preferencesDialog = new PreferencesDialog(this);
Chris@0 5282
Chris@1413 5283 connect(m_preferencesDialog, SIGNAL(audioDeviceChanged()),
Chris@1413 5284 this, SLOT(recreateAudioIO()));
Chris@1448 5285 connect(m_preferencesDialog, SIGNAL(coloursChanged()),
Chris@1448 5286 this, SLOT(coloursChanged()));
Chris@1413 5287
Chris@0 5288 // DeleteOnClose is safe here, because m_preferencesDialog is a
Chris@0 5289 // QPointer that will be zeroed when the dialog is deleted. We
Chris@0 5290 // use it in preference to leaving the dialog lying around because
Chris@0 5291 // if you Cancel the dialog, it resets the preferences state
Chris@0 5292 // without resetting its own widgets, so its state will be
Chris@0 5293 // incorrect when next shown unless we construct it afresh
Chris@0 5294 m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose);
Chris@0 5295
Chris@0 5296 m_preferencesDialog->show();
Chris@436 5297 if (goToTemplateTab) {
Chris@436 5298 m_preferencesDialog->switchToTab(PreferencesDialog::TemplateTab);
Chris@436 5299 }
Chris@0 5300 }
Chris@0 5301
Chris@0 5302 void
Chris@90 5303 MainWindow::mouseEnteredWidget()
Chris@90 5304 {
Chris@90 5305 QWidget *w = dynamic_cast<QWidget *>(sender());
Chris@90 5306 if (!w) return;
Chris@90 5307
Chris@2079 5308 QString mainText, editText;
Chris@2079 5309
Chris@1386 5310 if (w == m_mainLevelPan) {
Chris@2079 5311 mainText = tr("Adjust the master playback level and pan");
Chris@2079 5312 editText = tr("click then drag to adjust, ctrl+click to reset");
Chris@90 5313 } else if (w == m_playSpeed) {
Chris@2079 5314 mainText = tr("Adjust the master playback speed");
Chris@2079 5315 editText = tr("drag up/down to adjust, ctrl+click to reset");
Chris@2079 5316 }
Chris@2079 5317
Chris@2079 5318 if (mainText != "") {
Chris@2079 5319 contextHelpChanged(tr("%1: %2").arg(mainText).arg(editText));
Chris@90 5320 }
Chris@90 5321 }
Chris@90 5322
Chris@90 5323 void
Chris@90 5324 MainWindow::mouseLeftWidget()
Chris@90 5325 {
Chris@116 5326 contextHelpChanged("");
Chris@116 5327 }
Chris@116 5328
Chris@116 5329 void
Chris@0 5330 MainWindow::website()
Chris@0 5331 {
Chris@0 5332 openHelpUrl(tr("http://www.sonicvisualiser.org/"));
Chris@0 5333 }
Chris@0 5334
Chris@0 5335 void
Chris@0 5336 MainWindow::help()
Chris@0 5337 {
Chris@318 5338 openHelpUrl(tr("http://www.sonicvisualiser.org/doc/reference/%1/en/").arg(SV_VERSION));
Chris@0 5339 }
Chris@0 5340
Chris@0 5341 void
Chris@1516 5342 MainWindow::whatsNew()
Chris@1516 5343 {
Chris@1516 5344 QFile changelog(":CHANGELOG");
Chris@1516 5345 changelog.open(QFile::ReadOnly);
Chris@1516 5346 QByteArray content = changelog.readAll();
Chris@1516 5347 QString text = QString::fromUtf8(content);
Chris@1516 5348
Chris@1516 5349 QDialog *d = new QDialog(this);
Chris@1516 5350 d->setWindowTitle(tr("What's New"));
Chris@1516 5351
Chris@1516 5352 QGridLayout *layout = new QGridLayout;
Chris@1516 5353 d->setLayout(layout);
Chris@1516 5354
Chris@1516 5355 int row = 0;
Chris@1516 5356
Chris@1516 5357 QLabel *iconLabel = new QLabel;
Chris@1516 5358 iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64));
Chris@1516 5359 layout->addWidget(iconLabel, row, 0);
Chris@1516 5360
Chris@1516 5361 layout->addWidget
Chris@1516 5362 (new QLabel(tr("<h3>What's New in %1</h3>")
Chris@1516 5363 .arg(QApplication::applicationName())),
Chris@1516 5364 row++, 1);
Chris@1516 5365 layout->setColumnStretch(2, 10);
Chris@1516 5366
Chris@1516 5367 QTextEdit *textEdit = new QTextEdit;
Chris@1516 5368 layout->addWidget(textEdit, row++, 1, 1, 2);
Chris@1516 5369
Chris@1516 5370 if (m_newerVersionIs != "") {
Chris@1516 5371 layout->addWidget(new QLabel(tr("<b>Note:</b> A newer version of Sonic Visualiser is available.<br>(Version %1 is available; you are using version %2)").arg(m_newerVersionIs).arg(SV_VERSION)), row++, 1, 1, 2);
Chris@1516 5372 }
Chris@1516 5373
Chris@1516 5374 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
Chris@1516 5375 layout->addWidget(bb, row++, 0, 1, 3);
Chris@1516 5376 connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
Chris@1516 5377
Chris@2340 5378 // Remove spurious linefeeds from DOS line endings
Chris@2312 5379 text.replace('\r', "");
Chris@2340 5380
Chris@2340 5381 // Un-wrap indented paragraphs (assume they are always preceded by
Chris@2340 5382 // an empty line, so don't get merged into prior para)
Chris@1516 5383 text.replace(QRegExp("(.)\n +(.)"), "\\1 \\2");
Chris@2340 5384
Chris@2340 5385 // Rest of para following a " - " at start becomes bulleted entry
Chris@1516 5386 text.replace(QRegExp("\n - ([^\n]+)"), "\n<li>\\1</li>");
Chris@2340 5387
Chris@2340 5388 // Line-ending ":" introduces the bulleted list
Chris@1516 5389 text.replace(QRegExp(": *\n"), ":\n<ul>\n");
Chris@2340 5390
Chris@2340 5391 // Blank line (after unwrapping) ends the bulleted list
Chris@1516 5392 text.replace(QRegExp("</li>\n\\s*\n"), "</li>\n</ul>\n\n");
Chris@2340 5393
Chris@2340 5394 // Text leading up to that line-ending ":" becomes bold heading
Chris@1516 5395 text.replace(QRegExp("\n(\\w[^:\n]+:)"), "\n<p><b>\\1</b></p>");
Chris@1516 5396
Chris@1516 5397 textEdit->setHtml(text);
Chris@1516 5398 textEdit->setReadOnly(true);
Chris@1516 5399
cannam@1517 5400 d->setMinimumSize(m_viewManager->scalePixelSize(520),
cannam@1517 5401 m_viewManager->scalePixelSize(450));
Chris@1516 5402
Chris@1516 5403 d->exec();
Chris@1516 5404
Chris@1516 5405 delete d;
Chris@1516 5406 }
Chris@1516 5407
Chris@1610 5408 QString
Chris@1610 5409 MainWindow::getReleaseText() const
Chris@0 5410 {
Chris@0 5411 bool debug = false;
Chris@0 5412 QString version = "(unknown version)";
Chris@0 5413
Chris@0 5414 #ifdef BUILD_DEBUG
Chris@0 5415 debug = true;
Chris@285 5416 #endif // BUILD_DEBUG
Chris@0 5417 #ifdef SV_VERSION
Chris@0 5418 #ifdef SVNREV
Chris@0 5419 version = tr("Release %1 : Revision %2").arg(SV_VERSION).arg(SVNREV);
Chris@285 5420 #else // !SVNREV
Chris@0 5421 version = tr("Release %1").arg(SV_VERSION);
Chris@285 5422 #endif // SVNREV
Chris@285 5423 #else // !SV_VERSION
Chris@0 5424 #ifdef SVNREV
Chris@0 5425 version = tr("Unreleased : Revision %1").arg(SVNREV);
Chris@285 5426 #endif // SVNREV
Chris@285 5427 #endif // SV_VERSION
Chris@0 5428
Chris@1610 5429 return tr("%1 : %2 configuration, %3-bit build")
Chris@0 5430 .arg(version)
Chris@1264 5431 .arg(debug ? tr("Debug") : tr("Release"))
Chris@1264 5432 .arg(sizeof(void *) * 8);
Chris@1610 5433 }
Chris@1610 5434
Chris@1610 5435 void
Chris@1610 5436 MainWindow::about()
Chris@1610 5437 {
Chris@1610 5438 QString aboutText;
Chris@1610 5439
Chris@1610 5440 aboutText += tr("<h3>About Sonic Visualiser</h3>");
Chris@1610 5441 aboutText += tr("<p>Sonic Visualiser is a program for viewing and exploring audio data for semantic music analysis and annotation.<br><a href=\"http://www.sonicvisualiser.org/\">http://www.sonicvisualiser.org/</a></p>");
Chris@1610 5442 aboutText += QString("<p><small>%1</small></p>").arg(getReleaseText());
Chris@0 5443
Chris@1516 5444 if (m_oscQueue && m_oscQueue->isOK()) {
Chris@1516 5445 aboutText += tr("</small><p><small>The OSC URL for this instance is: \"%1\"").arg(m_oscQueue->getOSCURL());
Chris@1516 5446 }
Chris@1516 5447
Chris@1516 5448 aboutText += "</small><p><small>";
Chris@285 5449
Chris@1155 5450 aboutText += tr("With Qt v%1 &copy; The Qt Company").arg(QT_VERSION_STR);
Chris@285 5451
Chris@1516 5452 aboutText += "</small><small>";
Chris@1516 5453
Chris@0 5454 #ifdef HAVE_JACK
Chris@93 5455 #ifdef JACK_VERSION
Chris@285 5456 aboutText += tr("<br>With JACK audio output library v%1 &copy; Paul Davis and Jack O'Quin").arg(JACK_VERSION);
Chris@285 5457 #else // !JACK_VERSION
Chris@257 5458 aboutText += tr("<br>With JACK audio output library &copy; Paul Davis and Jack O'Quin");
Chris@285 5459 #endif // JACK_VERSION
Chris@285 5460 #endif // HAVE_JACK
Chris@0 5461 #ifdef HAVE_PORTAUDIO
Chris@257 5462 aboutText += tr("<br>With PortAudio audio output library &copy; Ross Bencina and Phil Burk");
Chris@285 5463 #endif // HAVE_PORTAUDIO
Chris@257 5464 #ifdef HAVE_LIBPULSE
Chris@285 5465 #ifdef LIBPULSE_VERSION
Chris@285 5466 aboutText += tr("<br>With PulseAudio audio output library v%1 &copy; Lennart Poettering and Pierre Ossman").arg(LIBPULSE_VERSION);
Chris@285 5467 #else // !LIBPULSE_VERSION
Chris@257 5468 aboutText += tr("<br>With PulseAudio audio output library &copy; Lennart Poettering and Pierre Ossman");
Chris@285 5469 #endif // LIBPULSE_VERSION
Chris@285 5470 #endif // HAVE_LIBPULSE
Chris@0 5471 #ifdef HAVE_OGGZ
Chris@93 5472 #ifdef OGGZ_VERSION
Chris@0 5473 aboutText += tr("<br>With Ogg file decoder (oggz v%1, fishsound v%2) &copy; CSIRO Australia").arg(OGGZ_VERSION).arg(FISHSOUND_VERSION);
Chris@285 5474 #else // !OGGZ_VERSION
Chris@93 5475 aboutText += tr("<br>With Ogg file decoder &copy; CSIRO Australia");
Chris@285 5476 #endif // OGGZ_VERSION
Chris@285 5477 #endif // HAVE_OGGZ
Chris@2311 5478 #ifdef HAVE_OPUS
Chris@2311 5479 aboutText += tr("<br>With Opus decoder &copy; Xiph.Org Foundation");
Chris@2311 5480 #endif // HAVE_OPUS
Chris@0 5481 #ifdef HAVE_MAD
Chris@93 5482 #ifdef MAD_VERSION
Chris@285 5483 aboutText += tr("<br>With MAD mp3 decoder v%1 &copy; Underbit Technologies Inc").arg(MAD_VERSION);
Chris@285 5484 #else // !MAD_VERSION
Chris@93 5485 aboutText += tr("<br>With MAD mp3 decoder &copy; Underbit Technologies Inc");
Chris@285 5486 #endif // MAD_VERSION
Chris@285 5487 #endif // HAVE_MAD
Chris@0 5488 #ifdef HAVE_SAMPLERATE
Chris@93 5489 #ifdef SAMPLERATE_VERSION
Chris@285 5490 aboutText += tr("<br>With libsamplerate v%1 &copy; Erik de Castro Lopo").arg(SAMPLERATE_VERSION);
Chris@285 5491 #else // !SAMPLERATE_VERSION
Chris@93 5492 aboutText += tr("<br>With libsamplerate &copy; Erik de Castro Lopo");
Chris@285 5493 #endif // SAMPLERATE_VERSION
Chris@285 5494 #endif // HAVE_SAMPLERATE
Chris@0 5495 #ifdef HAVE_SNDFILE
Chris@93 5496 #ifdef SNDFILE_VERSION
Chris@285 5497 aboutText += tr("<br>With libsndfile v%1 &copy; Erik de Castro Lopo").arg(SNDFILE_VERSION);
Chris@285 5498 #else // !SNDFILE_VERSION
Chris@93 5499 aboutText += tr("<br>With libsndfile &copy; Erik de Castro Lopo");
Chris@285 5500 #endif // SNDFILE_VERSION
Chris@285 5501 #endif // HAVE_SNDFILE
Chris@127 5502 #ifdef HAVE_FFTW3F
Chris@93 5503 #ifdef FFTW3_VERSION
Chris@285 5504 aboutText += tr("<br>With FFTW3 v%1 &copy; Matteo Frigo and MIT").arg(FFTW3_VERSION);
Chris@285 5505 #else // !FFTW3_VERSION
Chris@93 5506 aboutText += tr("<br>With FFTW3 &copy; Matteo Frigo and MIT");
Chris@285 5507 #endif // FFTW3_VERSION
Chris@285 5508 #endif // HAVE_FFTW3F
Chris@267 5509 #ifdef HAVE_RUBBERBAND
Chris@267 5510 #ifdef RUBBERBAND_VERSION
Chris@1315 5511 aboutText += tr("<br>With Rubber Band Library v%1 &copy; Particular Programs Ltd").arg(RUBBERBAND_VERSION);
Chris@285 5512 #else // !RUBBERBAND_VERSION
Chris@1315 5513 aboutText += tr("<br>With Rubber Band Library &copy; Particular Programs Ltd");
Chris@285 5514 #endif // RUBBERBAND_VERSION
Chris@285 5515 #endif // HAVE_RUBBERBAND
Chris@1315 5516 aboutText += tr("<br>With Vamp plugin support (API v%1, host SDK v%2) &copy; Chris Cannam and QMUL").arg(VAMP_API_VERSION).arg(VAMP_SDK_VERSION);
Chris@1636 5517 aboutText += tr("<br>With Piper Vamp protocol bridge &copy; QMUL");
Chris@0 5518 aboutText += tr("<br>With LADSPA plugin support (API v%1) &copy; Richard Furse, Paul Davis, Stefan Westerfeld").arg(LADSPA_VERSION);
Chris@0 5519 aboutText += tr("<br>With DSSI plugin support (API v%1) &copy; Chris Cannam, Steve Harris, Sean Bolton").arg(DSSI_VERSION);
Chris@285 5520 #ifdef REDLAND_VERSION
Chris@285 5521 aboutText += tr("<br>With Redland RDF datastore v%1 &copy; Dave Beckett and the University of Bristol").arg(REDLAND_VERSION);
Chris@285 5522 #else // !REDLAND_VERSION
Chris@285 5523 aboutText += tr("<br>With Redland RDF datastore &copy; Dave Beckett and the University of Bristol");
Chris@285 5524 #endif // REDLAND_VERSION
Chris@523 5525 aboutText += tr("<br>With Serd and Sord RDF parser and store &copy; David Robillard");
Chris@1315 5526 aboutText += tr("<br>With Dataquay Qt/RDF library &copy; Particular Programs Ltd");
Chris@1315 5527 aboutText += tr("<br>With Cap'n Proto serialisation &copy; Sandstorm Development Group");
Chris@300 5528 aboutText += tr("<br>With RtMidi &copy; Gary P. Scavone");
Chris@300 5529
Chris@69 5530 #ifdef HAVE_LIBLO
Chris@93 5531 #ifdef LIBLO_VERSION
Chris@285 5532 aboutText += tr("<br>With liblo Lite OSC library v%1 &copy; Steve Harris").arg(LIBLO_VERSION);
Chris@285 5533 #else // !LIBLO_VERSION
Chris@327 5534 aboutText += tr("<br>With liblo Lite OSC library &copy; Steve Harris");
Chris@285 5535 #endif // LIBLO_VERSION
Chris@285 5536
Chris@285 5537 aboutText += "</small></p>";
Chris@285 5538 #endif // HAVE_LIBLO
Chris@285 5539
Chris@1667 5540 aboutText += "<p><small>";
Chris@1667 5541 aboutText += tr("Russian UI translation contributed by Alexandre Prokoudine.");
Chris@1667 5542 aboutText += "<br>";
Chris@1667 5543 aboutText += tr("Czech UI translation contributed by Pavel Fric.");
Chris@1667 5544 aboutText += "</small></p>";
Chris@1667 5545
Chris@0 5546 aboutText +=
Chris@2561 5547 "<p><small>Sonic Visualiser Copyright &copy; 2005&ndash;2020 Chris Cannam and "
Chris@1667 5548 "Queen Mary, University of London.</small></p>";
Chris@1667 5549
Chris@1667 5550 aboutText +=
Chris@285 5551 "<p><small>This program is free software; you can redistribute it and/or "
Chris@231 5552 "modify it under the terms of the GNU General Public License as "
Chris@231 5553 "published by the Free Software Foundation; either version 2 of the "
Chris@0 5554 "License, or (at your option) any later version.<br>See the file "
Chris@285 5555 "COPYING included with this distribution for more information.</small></p>";
Chris@1516 5556
Chris@1516 5557 // use our own dialog so we can influence the size
Chris@1516 5558
Chris@1516 5559 QDialog *d = new QDialog(this);
Chris@1516 5560
Chris@1516 5561 d->setWindowTitle(tr("About %1").arg(QApplication::applicationName()));
Chris@1516 5562
Chris@1516 5563 QGridLayout *layout = new QGridLayout;
Chris@1516 5564 d->setLayout(layout);
Chris@1516 5565
Chris@1516 5566 int row = 0;
Chris@0 5567
Chris@1516 5568 QLabel *iconLabel = new QLabel;
Chris@1516 5569 iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64));
Chris@1516 5570 layout->addWidget(iconLabel, row, 0, Qt::AlignTop);
Chris@1516 5571
Chris@1516 5572 QLabel *mainText = new QLabel();
Chris@1516 5573 layout->addWidget(mainText, row, 1, 1, 2);
Chris@1516 5574
Chris@1516 5575 layout->setRowStretch(row, 10);
Chris@1516 5576 layout->setColumnStretch(1, 10);
Chris@1516 5577
Chris@1516 5578 ++row;
Chris@1516 5579
Chris@1516 5580 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
Chris@1516 5581 layout->addWidget(bb, row++, 0, 1, 3);
Chris@1516 5582 connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
Chris@1516 5583
Chris@1516 5584 // mainText->setHtml(aboutText);
Chris@1516 5585 // mainText->setReadOnly(true);
Chris@1516 5586 mainText->setWordWrap(true);
Chris@1516 5587 mainText->setOpenExternalLinks(true);
Chris@1516 5588 mainText->setText(aboutText);
Chris@1516 5589
Chris@1516 5590 d->setMinimumSize(m_viewManager->scalePixelSize(420),
Chris@1516 5591 m_viewManager->scalePixelSize(200));
Chris@1516 5592
Chris@1516 5593 d->exec();
Chris@1516 5594
Chris@1516 5595 delete d;
Chris@1516 5596 /*
Chris@1516 5597 QMessageBox about(QMessageBox::Information,
Chris@1516 5598 tr("About Sonic Visualiser"),
Chris@1516 5599 aboutText,
Chris@1516 5600 QMessageBox::StandardButtons(QMessageBox::Ok),
Chris@1516 5601 this);
Chris@1516 5602
Chris@1516 5603 QIcon icon = QApplication::windowIcon();
Chris@1516 5604 QSize size = icon.actualSize(QSize(64, 64));
Chris@1516 5605 about.setIconPixmap(icon.pixmap(size));
Chris@1516 5606
Chris@1516 5607 about.setMinimumSize(m_viewManager->scalePixelSize(400),
Chris@1516 5608 m_viewManager->scalePixelSize(400));
Chris@1516 5609
Chris@1516 5610 about.exec();
Chris@1516 5611 */
Chris@0 5612 }
Chris@0 5613
Chris@162 5614 void
Chris@162 5615 MainWindow::keyReference()
Chris@162 5616 {
Chris@162 5617 m_keyReference->show();
Chris@162 5618 }
Chris@162 5619
Chris@333 5620 void
Chris@333 5621 MainWindow::newerVersionAvailable(QString version)
Chris@333 5622 {
Chris@1516 5623 m_newerVersionIs = version;
Chris@1516 5624
Chris@334 5625 QSettings settings;
Chris@334 5626 settings.beginGroup("NewerVersionWarning");
Chris@334 5627 QString tag = QString("version-%1-available-show").arg(version);
Chris@334 5628 if (settings.value(tag, true).toBool()) {
Chris@334 5629 QString title(tr("Newer version available"));
Chris@663 5630 QString text(tr("<h3>Newer version available</h3><p>You are using version %1 of Sonic Visualiser, but version %2 is now available.</p><p>Please see the <a href=\"http://sonicvisualiser.org/\">Sonic Visualiser website</a> for more information.</p>").arg(SV_VERSION).arg(version));
Chris@334 5631 QMessageBox::information(this, title, text);
Chris@334 5632 settings.setValue(tag, false);
Chris@334 5633 }
Chris@334 5634 settings.endGroup();
Chris@333 5635 }
Chris@333 5636
Chris@333 5637