annotate main/MainWindow.cpp @ 2109:8c356423942c

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