annotate main/MainWindow.cpp @ 2265:d33dff02b39b sandbox-notarize

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