annotate main/MainWindow.cpp @ 2153:f57b0eea9201

Remove LTO again. It makes linking very slow, I'm not noticing any practical difference in performance, it doesn't work correctly on Travis image etc, and it sometimes breaks in incremental builds.
author Chris Cannam
date Thu, 06 Dec 2018 09:55:36 +0000
parents c476db6cf3eb
children b96492b41c74
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@2149 1946 QString path = files[i];
Chris@1253 1947 QAction *action = new QAction(path, this);
Chris@2149 1948 action->setObjectName(path);
Chris@2149 1949 connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile()));
Chris@162 1950 if (i == 0) {
Chris@162 1951 action->setShortcut(tr("Ctrl+R"));
Chris@162 1952 m_keyReference->registerShortcut
Chris@163 1953 (tr("Re-open"),
Chris@528 1954 action->shortcut().toString(),
Chris@163 1955 tr("Re-open the current or most recently opened file"));
Chris@162 1956 }
Chris@1770 1957 m_recentFilesMenu->addAction(action);
Chris@0 1958 }
Chris@0 1959 }
Chris@0 1960
Chris@0 1961 void
Chris@423 1962 MainWindow::setupTemplatesMenu()
Chris@423 1963 {
Chris@423 1964 m_templatesMenu->clear();
Chris@423 1965
Chris@455 1966 QAction *defaultAction = new QAction(tr("Standard Waveform"), this);
Chris@435 1967 defaultAction->setObjectName("default");
Chris@435 1968 connect(defaultAction, SIGNAL(triggered()), this, SLOT(applyTemplate()));
Chris@435 1969 m_templatesMenu->addAction(defaultAction);
Chris@435 1970
Chris@435 1971 m_templatesMenu->addSeparator();
Chris@435 1972
Chris@435 1973 QAction *action = 0;
Chris@435 1974
Chris@435 1975 QStringList templates = ResourceFinder().getResourceFiles("templates", "svt");
Chris@435 1976
Chris@436 1977 bool havePersonal = false;
Chris@436 1978
Chris@435 1979 // (ordered by name)
Chris@435 1980 std::set<QString> byName;
Chris@435 1981 foreach (QString t, templates) {
Chris@436 1982 if (!t.startsWith(":")) havePersonal = true;
Chris@435 1983 byName.insert(QFileInfo(t).baseName());
Chris@435 1984 }
Chris@435 1985
Chris@435 1986 foreach (QString t, byName) {
Chris@435 1987 if (t.toLower() == "default") continue;
Chris@435 1988 action = new QAction(t, this);
Chris@435 1989 connect(action, SIGNAL(triggered()), this, SLOT(applyTemplate()));
Chris@435 1990 m_templatesMenu->addAction(action);
Chris@435 1991 }
Chris@435 1992
Chris@435 1993 if (!templates.empty()) m_templatesMenu->addSeparator();
Chris@435 1994
Chris@435 1995 if (!m_templateWatcher) {
Chris@435 1996 m_templateWatcher = new QFileSystemWatcher(this);
Chris@435 1997 m_templateWatcher->addPath(ResourceFinder().getResourceSaveDir("templates"));
Chris@435 1998 connect(m_templateWatcher, SIGNAL(directoryChanged(const QString &)),
Chris@435 1999 this, SLOT(setupTemplatesMenu()));
Chris@435 2000 }
Chris@436 2001
Chris@436 2002 QAction *setDefaultAction = new QAction(tr("Choose Default Template..."), this);
Chris@436 2003 setDefaultAction->setObjectName("set_default_template");
Chris@436 2004 connect(setDefaultAction, SIGNAL(triggered()), this, SLOT(preferences()));
Chris@436 2005 m_templatesMenu->addSeparator();
Chris@436 2006 m_templatesMenu->addAction(setDefaultAction);
Chris@436 2007
Chris@436 2008 m_manageTemplatesAction->setEnabled(havePersonal);
Chris@435 2009 }
Chris@435 2010
Chris@423 2011
Chris@423 2012 void
Chris@211 2013 MainWindow::setupRecentTransformsMenu()
Chris@34 2014 {
Chris@211 2015 m_recentTransformsMenu->clear();
Chris@211 2016 vector<QString> transforms = m_recentTransforms.getRecent();
Chris@34 2017 for (size_t i = 0; i < transforms.size(); ++i) {
Chris@211 2018 TransformActionReverseMap::iterator ti =
Chris@34 2019 m_transformActionsReverse.find(transforms[i]);
Chris@34 2020 if (ti == m_transformActionsReverse.end()) {
Chris@665 2021 cerr << "WARNING: MainWindow::setupRecentTransformsMenu: "
Chris@665 2022 << "Unknown transform \"" << transforms[i]
Chris@665 2023 << "\" in recent transforms list" << endl;
Chris@34 2024 continue;
Chris@34 2025 }
Chris@162 2026 if (i == 0) {
Chris@162 2027 ti->second->setShortcut(tr("Ctrl+T"));
Chris@162 2028 m_keyReference->registerShortcut
Chris@211 2029 (tr("Repeat Transform"),
Chris@528 2030 ti->second->shortcut().toString(),
Chris@163 2031 tr("Re-select the most recently run transform"));
Chris@216 2032 } else {
Chris@216 2033 ti->second->setShortcut(QString(""));
Chris@162 2034 }
Chris@1770 2035 m_recentTransformsMenu->addAction(ti->second);
Chris@34 2036 }
Chris@34 2037 }
Chris@34 2038
Chris@34 2039 void
Chris@95 2040 MainWindow::setupExistingLayersMenus()
Chris@0 2041 {
Chris@0 2042 if (!m_existingLayersMenu) return; // should have been created by setupMenus
Chris@0 2043
Chris@438 2044 // SVDEBUG << "MainWindow::setupExistingLayersMenu" << endl;
Chris@0 2045
Chris@0 2046 m_existingLayersMenu->clear();
Chris@0 2047 m_existingLayerActions.clear();
Chris@0 2048
Chris@95 2049 m_sliceMenu->clear();
Chris@95 2050 m_sliceActions.clear();
Chris@95 2051
Chris@168 2052 IconLoader il;
Chris@168 2053
Chris@33 2054 vector<Layer *> orderedLayers;
Chris@33 2055 set<Layer *> observedLayers;
Chris@95 2056 set<Layer *> sliceableLayers;
Chris@95 2057
Chris@95 2058 LayerFactory *factory = LayerFactory::getInstance();
Chris@0 2059
Chris@0 2060 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@0 2061
Chris@1770 2062 Pane *pane = m_paneStack->getPane(i);
Chris@1770 2063 if (!pane) continue;
Chris@1770 2064
Chris@1770 2065 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@1770 2066
Chris@1770 2067 Layer *layer = pane->getLayer(j);
Chris@1770 2068 if (!layer) continue;
Chris@1770 2069 if (observedLayers.find(layer) != observedLayers.end()) {
Chris@1770 2070 // cerr << "found duplicate layer " << layer << endl;
Chris@1770 2071 continue;
Chris@1770 2072 }
Chris@1770 2073
Chris@1770 2074 // cerr << "found new layer " << layer << " (name = "
Chris@1770 2075 // << layer->getLayerPresentationName() << ")" << endl;
Chris@1770 2076
Chris@1770 2077 orderedLayers.push_back(layer);
Chris@1770 2078 observedLayers.insert(layer);
Chris@95 2079
Chris@95 2080 if (factory->isLayerSliceable(layer)) {
Chris@95 2081 sliceableLayers.insert(layer);
Chris@95 2082 }
Chris@1770 2083 }
Chris@0 2084 }
Chris@0 2085
Chris@33 2086 map<QString, int> observedNames;
Chris@0 2087
Chris@137 2088 for (size_t i = 0; i < orderedLayers.size(); ++i) {
Chris@1770 2089
Chris@95 2090 Layer *layer = orderedLayers[i];
Chris@95 2091
Chris@1770 2092 QString name = layer->getLayerPresentationName();
Chris@1770 2093 int n = ++observedNames[name];
Chris@1770 2094 if (n > 1) name = QString("%1 <%2>").arg(name).arg(n);
Chris@1770 2095
Chris@1770 2096 QIcon icon = il.load(factory->getLayerIconName
Chris@168 2097 (factory->getLayerType(layer)));
Chris@95 2098
Chris@1770 2099 QAction *action = new QAction(icon, name, this);
Chris@1770 2100 connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
Chris@1770 2101 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@2093 2102 m_existingLayerActions.push_back({ action, layer });
Chris@1770 2103
Chris@1770 2104 m_existingLayersMenu->addAction(action);
Chris@95 2105
Chris@95 2106 if (sliceableLayers.find(layer) != sliceableLayers.end()) {
Chris@95 2107 action = new QAction(icon, name, this);
Chris@95 2108 connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
Chris@95 2109 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@2093 2110 m_sliceActions.push_back({ action, layer });
Chris@95 2111 m_sliceMenu->addAction(action);
Chris@95 2112 }
Chris@0 2113 }
Chris@95 2114
Chris@95 2115 m_sliceMenu->setEnabled(!m_sliceActions.empty());
Chris@0 2116 }
Chris@0 2117
Chris@0 2118 void
Chris@0 2119 MainWindow::setupToolbars()
Chris@0 2120 {
Chris@162 2121 m_keyReference->setCategory(tr("Playback and Transport Controls"));
Chris@162 2122
Chris@168 2123 IconLoader il;
Chris@168 2124
Chris@155 2125 QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back"));
Chris@155 2126 menu->setTearOffEnabled(true);
Chris@155 2127 m_rightButtonMenu->addSeparator();
Chris@155 2128 m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback"));
Chris@155 2129
Chris@155 2130 QToolBar *toolbar = addToolBar(tr("Playback Toolbar"));
Chris@155 2131
Chris@265 2132 m_rwdStartAction = toolbar->addAction(il.load("rewind-start"),
Chris@265 2133 tr("Rewind to Start"));
Chris@265 2134 m_rwdStartAction->setShortcut(tr("Home"));
Chris@265 2135 m_rwdStartAction->setStatusTip(tr("Rewind to the start"));
Chris@265 2136 connect(m_rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart()));
Chris@265 2137 connect(this, SIGNAL(canPlay(bool)), m_rwdStartAction, SLOT(setEnabled(bool)));
Chris@265 2138
Chris@265 2139 m_rwdAction = toolbar->addAction(il.load("rewind"), tr("Rewind"));
Chris@155 2140 m_rwdAction->setShortcut(tr("PgUp"));
Chris@163 2141 m_rwdAction->setStatusTip(tr("Rewind to the previous time instant or time ruler notch"));
Chris@155 2142 connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind()));
Chris@155 2143 connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool)));
Chris@155 2144
Chris@323 2145 m_rwdSimilarAction = new QAction(tr("Rewind to Similar Point"), this);
Chris@323 2146 m_rwdSimilarAction->setShortcut(tr("Shift+PgUp"));
Chris@323 2147 m_rwdSimilarAction->setStatusTip(tr("Rewind to the previous similarly valued time instant"));
Chris@323 2148 connect(m_rwdSimilarAction, SIGNAL(triggered()), this, SLOT(rewindSimilar()));
Chris@323 2149 connect(this, SIGNAL(canRewind(bool)), m_rwdSimilarAction, SLOT(setEnabled(bool)));
Chris@323 2150
Chris@265 2151 m_playAction = toolbar->addAction(il.load("playpause"),
Chris@265 2152 tr("Play / Pause"));
Chris@265 2153 m_playAction->setCheckable(true);
Chris@1790 2154
Chris@1790 2155 /*: This text is a shortcut label referring to the space-bar on
Chris@1790 2156 the keyboard. It probably should not be translated, and
Chris@1790 2157 certainly should not be translated as if referring to an empty
Chris@1790 2158 void or to the extra-terrestrial universe.
Chris@1790 2159 */
Chris@265 2160 m_playAction->setShortcut(tr("Space"));
Chris@1790 2161
Chris@265 2162 m_playAction->setStatusTip(tr("Start or stop playback from the current position"));
Chris@265 2163 connect(m_playAction, SIGNAL(triggered()), this, SLOT(play()));
Chris@0 2164 connect(m_playSource, SIGNAL(playStatusChanged(bool)),
Chris@1770 2165 m_playAction, SLOT(setChecked(bool)));
Chris@305 2166 connect(m_playSource, SIGNAL(playStatusChanged(bool)),
Chris@305 2167 this, SLOT(playStatusChanged(bool)));
Chris@265 2168 connect(this, SIGNAL(canPlay(bool)), m_playAction, SLOT(setEnabled(bool)));
Chris@155 2169
Chris@168 2170 m_ffwdAction = toolbar->addAction(il.load("ffwd"),
Chris@286 2171 tr("Fast Forward"));
Chris@155 2172 m_ffwdAction->setShortcut(tr("PgDown"));
Chris@163 2173 m_ffwdAction->setStatusTip(tr("Fast-forward to the next time instant or time ruler notch"));
Chris@155 2174 connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd()));
Chris@155 2175 connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool)));
Chris@155 2176
Chris@323 2177 m_ffwdSimilarAction = new QAction(tr("Fast Forward to Similar Point"), this);
Chris@323 2178 m_ffwdSimilarAction->setShortcut(tr("Shift+PgDown"));
Chris@323 2179 m_ffwdSimilarAction->setStatusTip(tr("Fast-forward to the next similarly valued time instant"));
Chris@323 2180 connect(m_ffwdSimilarAction, SIGNAL(triggered()), this, SLOT(ffwdSimilar()));
Chris@323 2181 connect(this, SIGNAL(canFfwd(bool)), m_ffwdSimilarAction, SLOT(setEnabled(bool)));
Chris@323 2182
Chris@265 2183 m_ffwdEndAction = toolbar->addAction(il.load("ffwd-end"),
Chris@265 2184 tr("Fast Forward to End"));
Chris@265 2185 m_ffwdEndAction->setShortcut(tr("End"));
Chris@265 2186 m_ffwdEndAction->setStatusTip(tr("Fast-forward to the end"));
Chris@265 2187 connect(m_ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd()));
Chris@265 2188 connect(this, SIGNAL(canPlay(bool)), m_ffwdEndAction, SLOT(setEnabled(bool)));
Chris@0 2189
Chris@1047 2190 m_recordAction = toolbar->addAction(il.load("record"),
Chris@1047 2191 tr("Record"));
Chris@1047 2192 m_recordAction->setCheckable(true);
Chris@1047 2193 m_recordAction->setShortcut(tr("Ctrl+Space"));
Chris@1047 2194 m_recordAction->setStatusTip(tr("Record a new audio file"));
Chris@1047 2195 connect(m_recordAction, SIGNAL(triggered()), this, SLOT(record()));
Chris@1047 2196 connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)),
Chris@1770 2197 m_recordAction, SLOT(setChecked(bool)));
Chris@1047 2198 connect(this, SIGNAL(canRecord(bool)),
Chris@1047 2199 m_recordAction, SLOT(setEnabled(bool)));
Chris@1047 2200
Chris@0 2201 toolbar = addToolBar(tr("Play Mode Toolbar"));
Chris@0 2202
Chris@265 2203 m_playSelectionAction = toolbar->addAction(il.load("playselection"),
Chris@265 2204 tr("Constrain Playback to Selection"));
Chris@265 2205 m_playSelectionAction->setCheckable(true);
Chris@265 2206 m_playSelectionAction->setChecked(m_viewManager->getPlaySelectionMode());
Chris@265 2207 m_playSelectionAction->setShortcut(tr("s"));
Chris@265 2208 m_playSelectionAction->setStatusTip(tr("Constrain playback to the selected regions"));
Chris@69 2209 connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)),
Chris@265 2210 m_playSelectionAction, SLOT(setChecked(bool)));
Chris@265 2211 connect(m_playSelectionAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled()));
Chris@265 2212 connect(this, SIGNAL(canPlaySelection(bool)), m_playSelectionAction, SLOT(setEnabled(bool)));
Chris@265 2213
Chris@265 2214 m_playLoopAction = toolbar->addAction(il.load("playloop"),
Chris@265 2215 tr("Loop Playback"));
Chris@265 2216 m_playLoopAction->setCheckable(true);
Chris@265 2217 m_playLoopAction->setChecked(m_viewManager->getPlayLoopMode());
Chris@265 2218 m_playLoopAction->setShortcut(tr("l"));
Chris@265 2219 m_playLoopAction->setStatusTip(tr("Loop playback"));
Chris@69 2220 connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)),
Chris@265 2221 m_playLoopAction, SLOT(setChecked(bool)));
Chris@265 2222 connect(m_playLoopAction, SIGNAL(triggered()), this, SLOT(playLoopToggled()));
Chris@265 2223 connect(this, SIGNAL(canPlay(bool)), m_playLoopAction, SLOT(setEnabled(bool)));
Chris@155 2224
Chris@207 2225 m_soloAction = toolbar->addAction(il.load("solo"),
Chris@323 2226 tr("Solo Current Pane"));
Chris@207 2227 m_soloAction->setCheckable(true);
Chris@207 2228 m_soloAction->setChecked(m_viewManager->getPlaySoloMode());
Chris@207 2229 m_prevSolo = m_viewManager->getPlaySoloMode();
Chris@207 2230 m_soloAction->setShortcut(tr("o"));
Chris@207 2231 m_soloAction->setStatusTip(tr("Solo the current pane during playback"));
Chris@180 2232 connect(m_viewManager, SIGNAL(playSoloModeChanged(bool)),
Chris@207 2233 m_soloAction, SLOT(setChecked(bool)));
Chris@207 2234 connect(m_soloAction, SIGNAL(triggered()), this, SLOT(playSoloToggled()));
Chris@207 2235 connect(this, SIGNAL(canChangeSolo(bool)), m_soloAction, SLOT(setEnabled(bool)));
Chris@180 2236
Chris@208 2237 QAction *alAction = 0;
Chris@208 2238 if (Document::canAlign()) {
Chris@208 2239 alAction = toolbar->addAction(il.load("align"),
Chris@208 2240 tr("Align File Timelines"));
Chris@208 2241 alAction->setCheckable(true);
Chris@208 2242 alAction->setChecked(m_viewManager->getAlignMode());
Chris@208 2243 alAction->setStatusTip(tr("Treat multiple audio files as versions of the same work, and align their timelines"));
Chris@208 2244 connect(m_viewManager, SIGNAL(alignModeChanged(bool)),
Chris@208 2245 alAction, SLOT(setChecked(bool)));
Chris@208 2246 connect(alAction, SIGNAL(triggered()), this, SLOT(alignToggled()));
Chris@208 2247 connect(this, SIGNAL(canAlign(bool)), alAction, SLOT(setEnabled(bool)));
Chris@208 2248 }
Chris@206 2249
Chris@265 2250 m_keyReference->registerShortcut(m_playAction);
Chris@1056 2251 m_keyReference->registerShortcut(m_recordAction);
Chris@265 2252 m_keyReference->registerShortcut(m_playSelectionAction);
Chris@265 2253 m_keyReference->registerShortcut(m_playLoopAction);
Chris@207 2254 m_keyReference->registerShortcut(m_soloAction);
Chris@208 2255 if (alAction) m_keyReference->registerShortcut(alAction);
Chris@162 2256 m_keyReference->registerShortcut(m_rwdAction);
Chris@162 2257 m_keyReference->registerShortcut(m_ffwdAction);
Chris@323 2258 m_keyReference->registerShortcut(m_rwdSimilarAction);
Chris@323 2259 m_keyReference->registerShortcut(m_ffwdSimilarAction);
Chris@265 2260 m_keyReference->registerShortcut(m_rwdStartAction);
Chris@265 2261 m_keyReference->registerShortcut(m_ffwdEndAction);
Chris@265 2262
Chris@265 2263 menu->addAction(m_playAction);
Chris@1056 2264 menu->addAction(m_recordAction);
Chris@265 2265 menu->addAction(m_playSelectionAction);
Chris@265 2266 menu->addAction(m_playLoopAction);
Chris@207 2267 menu->addAction(m_soloAction);
Chris@208 2268 if (alAction) menu->addAction(alAction);
Chris@155 2269 menu->addSeparator();
Chris@155 2270 menu->addAction(m_rwdAction);
Chris@155 2271 menu->addAction(m_ffwdAction);
Chris@155 2272 menu->addSeparator();
Chris@323 2273 menu->addAction(m_rwdSimilarAction);
Chris@323 2274 menu->addAction(m_ffwdSimilarAction);
Chris@323 2275 menu->addSeparator();
Chris@265 2276 menu->addAction(m_rwdStartAction);
Chris@265 2277 menu->addAction(m_ffwdEndAction);
Chris@155 2278 menu->addSeparator();
Chris@1055 2279 menu->addAction(m_recordAction);
Chris@1055 2280 menu->addSeparator();
Chris@155 2281
Chris@265 2282 m_rightButtonPlaybackMenu->addAction(m_playAction);
Chris@265 2283 m_rightButtonPlaybackMenu->addAction(m_playSelectionAction);
Chris@265 2284 m_rightButtonPlaybackMenu->addAction(m_playLoopAction);
Chris@207 2285 m_rightButtonPlaybackMenu->addAction(m_soloAction);
Chris@208 2286 if (alAction) m_rightButtonPlaybackMenu->addAction(alAction);
Chris@155 2287 m_rightButtonPlaybackMenu->addSeparator();
Chris@155 2288 m_rightButtonPlaybackMenu->addAction(m_rwdAction);
Chris@155 2289 m_rightButtonPlaybackMenu->addAction(m_ffwdAction);
Chris@155 2290 m_rightButtonPlaybackMenu->addSeparator();
Chris@265 2291 m_rightButtonPlaybackMenu->addAction(m_rwdStartAction);
Chris@265 2292 m_rightButtonPlaybackMenu->addAction(m_ffwdEndAction);
Chris@155 2293 m_rightButtonPlaybackMenu->addSeparator();
Chris@1055 2294 m_rightButtonPlaybackMenu->addAction(m_recordAction);
Chris@1055 2295 m_rightButtonPlaybackMenu->addSeparator();
Chris@155 2296
Chris@155 2297 QAction *fastAction = menu->addAction(tr("Speed Up"));
Chris@155 2298 fastAction->setShortcut(tr("Ctrl+PgUp"));
Chris@163 2299 fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch"));
Chris@155 2300 connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback()));
Chris@155 2301 connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool)));
Chris@155 2302
Chris@155 2303 QAction *slowAction = menu->addAction(tr("Slow Down"));
Chris@155 2304 slowAction->setShortcut(tr("Ctrl+PgDown"));
Chris@163 2305 slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch"));
Chris@155 2306 connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback()));
Chris@155 2307 connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool)));
Chris@155 2308
Chris@155 2309 QAction *normalAction = menu->addAction(tr("Restore Normal Speed"));
Chris@155 2310 normalAction->setShortcut(tr("Ctrl+Home"));
Chris@163 2311 normalAction->setStatusTip(tr("Restore non-time-stretched playback"));
Chris@155 2312 connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback()));
Chris@155 2313 connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool)));
Chris@155 2314
Chris@162 2315 m_keyReference->registerShortcut(fastAction);
Chris@162 2316 m_keyReference->registerShortcut(slowAction);
Chris@162 2317 m_keyReference->registerShortcut(normalAction);
Chris@162 2318
Chris@155 2319 m_rightButtonPlaybackMenu->addAction(fastAction);
Chris@155 2320 m_rightButtonPlaybackMenu->addAction(slowAction);
Chris@155 2321 m_rightButtonPlaybackMenu->addAction(normalAction);
Chris@0 2322
Chris@0 2323 toolbar = addToolBar(tr("Edit Toolbar"));
Chris@0 2324 CommandHistory::getInstance()->registerToolbar(toolbar);
Chris@0 2325
Chris@0 2326 toolbar = addToolBar(tr("Tools Toolbar"));
Chris@0 2327 QActionGroup *group = new QActionGroup(this);
Chris@2093 2328 m_toolActions.clear();
Chris@2093 2329
Chris@705 2330 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2331 QAction *action = toolbar->addAction(il.load("navigate"), tr("Navigate"));
Chris@0 2332 action->setCheckable(true);
Chris@0 2333 action->setChecked(true);
Chris@0 2334 action->setShortcut(tr("1"));
Chris@90 2335 action->setStatusTip(tr("Navigate"));
Chris@0 2336 connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected()));
Chris@596 2337 connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger()));
Chris@0 2338 group->addAction(action);
Chris@162 2339 m_keyReference->registerShortcut(action);
Chris@2093 2340 m_toolActions.push_back({ ViewManager::NavigateMode, action });
Chris@705 2341
Chris@705 2342 m_keyReference->setCategory
Chris@705 2343 (tr("Navigate Tool Mouse Actions"));
Chris@705 2344 m_keyReference->registerShortcut
Chris@705 2345 (tr("Navigate"), tr("Left"),
Chris@705 2346 tr("Click left button and drag to move around"));
Chris@705 2347 m_keyReference->registerShortcut
Chris@705 2348 (tr("Zoom to Area"), tr("Shift+Left"),
Chris@705 2349 tr("Shift-click left button and drag to zoom to a rectangular area"));
Chris@705 2350 m_keyReference->registerShortcut
Chris@705 2351 (tr("Relocate"), tr("Double-Click Left"),
Chris@705 2352 tr("Double-click left button to jump to clicked location"));
Chris@705 2353 m_keyReference->registerShortcut
Chris@705 2354 (tr("Edit"), tr("Double-Click Left"),
Chris@705 2355 tr("Double-click left button on an item to edit it"));
Chris@705 2356
Chris@705 2357 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2358 action = toolbar->addAction(il.load("select"), tr("Select"));
Chris@0 2359 action->setCheckable(true);
Chris@0 2360 action->setShortcut(tr("2"));
Chris@90 2361 action->setStatusTip(tr("Select ranges"));
Chris@0 2362 connect(action, SIGNAL(triggered()), this, SLOT(toolSelectSelected()));
Chris@0 2363 group->addAction(action);
Chris@162 2364 m_keyReference->registerShortcut(action);
Chris@2093 2365 m_toolActions.push_back({ ViewManager::SelectMode, action });
Chris@705 2366
Chris@705 2367 m_keyReference->setCategory
Chris@705 2368 (tr("Select Tool Mouse Actions"));
Chris@705 2369 m_keyReference->registerShortcut
Chris@705 2370 (tr("Select"), tr("Left"),
Chris@705 2371 tr("Click left button and drag to select region; drag region edge to resize"));
Chris@705 2372 #ifdef Q_OS_MAC
Chris@705 2373 m_keyReference->registerShortcut
Chris@705 2374 (tr("Multi Select"), tr("Ctrl+Left"),
Chris@705 2375 tr("Cmd-click left button and drag to select an additional region"));
Chris@705 2376 #else
Chris@705 2377 m_keyReference->registerShortcut
Chris@705 2378 (tr("Multi Select"), tr("Ctrl+Left"),
Chris@705 2379 tr("Ctrl-click left button and drag to select an additional region"));
Chris@705 2380 #endif
Chris@705 2381 m_keyReference->registerShortcut
Chris@705 2382 (tr("Fine Select"), tr("Shift+Left"),
Chris@705 2383 tr("Shift-click left button and drag to select without snapping to items or grid"));
Chris@705 2384
Chris@705 2385 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2386 action = toolbar->addAction(il.load("move"), tr("Edit"));
Chris@0 2387 action->setCheckable(true);
Chris@0 2388 action->setShortcut(tr("3"));
Chris@90 2389 action->setStatusTip(tr("Edit items in layer"));
Chris@0 2390 connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected()));
Chris@0 2391 connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@0 2392 group->addAction(action);
Chris@162 2393 m_keyReference->registerShortcut(action);
Chris@2093 2394 m_toolActions.push_back({ ViewManager::EditMode, action });
Chris@705 2395
Chris@705 2396 m_keyReference->setCategory
Chris@705 2397 (tr("Edit Tool Mouse Actions"));
Chris@705 2398 m_keyReference->registerShortcut
Chris@705 2399 (tr("Move"), tr("Left"),
Chris@705 2400 tr("Click left button on an item or selected region and drag to move"));
Chris@705 2401 m_keyReference->registerShortcut
Chris@705 2402 (tr("Edit"), tr("Double-Click Left"),
Chris@705 2403 tr("Double-click left button on an item to edit it"));
Chris@705 2404
Chris@705 2405 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2406 action = toolbar->addAction(il.load("draw"), tr("Draw"));
Chris@0 2407 action->setCheckable(true);
Chris@0 2408 action->setShortcut(tr("4"));
Chris@90 2409 action->setStatusTip(tr("Draw new items in layer"));
Chris@0 2410 connect(action, SIGNAL(triggered()), this, SLOT(toolDrawSelected()));
Chris@0 2411 connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@0 2412 group->addAction(action);
Chris@162 2413 m_keyReference->registerShortcut(action);
Chris@2093 2414 m_toolActions.push_back({ ViewManager::DrawMode, action });
Chris@0 2415
Chris@705 2416 m_keyReference->setCategory
Chris@705 2417 (tr("Draw Tool Mouse Actions"));
Chris@705 2418 m_keyReference->registerShortcut
Chris@705 2419 (tr("Draw"), tr("Left"),
Chris@705 2420 tr("Click left button and drag to create new item"));
Chris@705 2421
Chris@705 2422 m_keyReference->setCategory(tr("Tool Selection"));
Chris@2093 2423 action = toolbar->addAction(il.load("erase"), tr("Erase"));
Chris@217 2424 action->setCheckable(true);
Chris@217 2425 action->setShortcut(tr("5"));
Chris@217 2426 action->setStatusTip(tr("Erase items from layer"));
Chris@217 2427 connect(action, SIGNAL(triggered()), this, SLOT(toolEraseSelected()));
Chris@217 2428 connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@217 2429 group->addAction(action);
Chris@217 2430 m_keyReference->registerShortcut(action);
Chris@2093 2431 m_toolActions.push_back({ ViewManager::EraseMode, action });
Chris@217 2432
Chris@705 2433 m_keyReference->setCategory
Chris@705 2434 (tr("Erase Tool Mouse Actions"));
Chris@705 2435 m_keyReference->registerShortcut
Chris@705 2436 (tr("Erase"), tr("Left"),
Chris@705 2437 tr("Click left button on an item to remove it from the layer"));
Chris@705 2438
Chris@705 2439 m_keyReference->setCategory(tr("Tool Selection"));
Chris@265 2440 action = toolbar->addAction(il.load("measure"), tr("Measure"));
Chris@151 2441 action->setCheckable(true);
Chris@217 2442 action->setShortcut(tr("6"));
Chris@151 2443 action->setStatusTip(tr("Make measurements in layer"));
Chris@151 2444 connect(action, SIGNAL(triggered()), this, SLOT(toolMeasureSelected()));
Chris@169 2445 connect(this, SIGNAL(canMeasureLayer(bool)), action, SLOT(setEnabled(bool)));
Chris@151 2446 group->addAction(action);
Chris@162 2447 m_keyReference->registerShortcut(action);
Chris@2093 2448 m_toolActions.push_back({ ViewManager::MeasureMode, action });
Chris@151 2449
Chris@705 2450 m_keyReference->setCategory
Chris@705 2451 (tr("Measure Tool Mouse Actions"));
Chris@705 2452 m_keyReference->registerShortcut
Chris@705 2453 (tr("Measure Area"), tr("Left"),
Chris@705 2454 tr("Click left button and drag to measure a rectangular area"));
Chris@705 2455 m_keyReference->registerShortcut
Chris@705 2456 (tr("Measure Item"), tr("Double-Click Left"),
Chris@705 2457 tr("Click left button and drag to measure extents of an item or shape"));
Chris@705 2458 m_keyReference->registerShortcut
Chris@705 2459 (tr("Zoom to Area"), tr("Shift+Left"),
Chris@705 2460 tr("Shift-click left button and drag to zoom to a rectangular area"));
Chris@705 2461
Chris@0 2462 toolNavigateSelected();
Chris@163 2463
Chris@163 2464 Pane::registerShortcuts(*m_keyReference);
Chris@0 2465 }
Chris@0 2466
Chris@0 2467 void
Chris@265 2468 MainWindow::connectLayerEditDialog(ModelDataTableDialog *dialog)
Chris@265 2469 {
Chris@265 2470 MainWindowBase::connectLayerEditDialog(dialog);
Chris@265 2471 QToolBar *toolbar = dialog->getPlayToolbar();
Chris@265 2472 if (toolbar) {
Chris@265 2473 toolbar->addAction(m_rwdStartAction);
Chris@265 2474 toolbar->addAction(m_rwdAction);
Chris@265 2475 toolbar->addAction(m_playAction);
Chris@265 2476 toolbar->addAction(m_ffwdAction);
Chris@265 2477 toolbar->addAction(m_ffwdEndAction);
Chris@265 2478 }
Chris@265 2479 }
Chris@265 2480
Chris@265 2481 void
Chris@0 2482 MainWindow::updateMenuStates()
Chris@0 2483 {
Chris@200 2484 MainWindowBase::updateMenuStates();
Chris@200 2485
Chris@117 2486 Pane *currentPane = 0;
Chris@117 2487 Layer *currentLayer = 0;
Chris@117 2488
Chris@117 2489 if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
Chris@117 2490 if (currentPane) currentLayer = currentPane->getSelectedLayer();
Chris@117 2491
Chris@0 2492 bool haveCurrentPane =
Chris@117 2493 (currentPane != 0);
Chris@0 2494 bool haveCurrentLayer =
Chris@117 2495 (haveCurrentPane &&
Chris@117 2496 (currentLayer != 0));
Chris@206 2497 bool havePlayTarget =
Chris@1770 2498 (m_playTarget != 0 || m_audioIO != 0);
Chris@0 2499 bool haveSelection =
Chris@1770 2500 (m_viewManager &&
Chris@1770 2501 !m_viewManager->getSelections().empty());
Chris@0 2502 bool haveCurrentEditableLayer =
Chris@1770 2503 (haveCurrentLayer &&
Chris@1770 2504 currentLayer->isLayerEditable());
Chris@0 2505 bool haveCurrentTimeInstantsLayer =
Chris@1770 2506 (haveCurrentLayer &&
Chris@1770 2507 dynamic_cast<TimeInstantLayer *>(currentLayer));
Chris@0 2508 bool haveCurrentTimeValueLayer =
Chris@1770 2509 (haveCurrentLayer &&
Chris@1770 2510 dynamic_cast<TimeValueLayer *>(currentLayer));
Chris@207 2511
Chris@314 2512 bool alignMode = m_viewManager && m_viewManager->getAlignMode();
Chris@314 2513 emit canChangeSolo(havePlayTarget && !alignMode);
Chris@207 2514 emit canAlign(havePlayTarget && m_document && m_document->canAlign());
Chris@206 2515
Chris@200 2516 emit canChangePlaybackSpeed(true);
Chris@200 2517 int v = m_playSpeed->value();
Chris@200 2518 emit canSpeedUpPlayback(v < m_playSpeed->maximum());
Chris@200 2519 emit canSlowDownPlayback(v > m_playSpeed->minimum());
Chris@155 2520
Chris@164 2521 if (m_viewManager &&
Chris@164 2522 (m_viewManager->getToolMode() == ViewManager::MeasureMode)) {
Chris@164 2523 emit canDeleteSelection(haveCurrentLayer);
Chris@164 2524 m_deleteSelectedAction->setText(tr("&Delete Current Measurement"));
Chris@164 2525 m_deleteSelectedAction->setStatusTip(tr("Delete the measurement currently under the mouse pointer"));
Chris@164 2526 } else {
Chris@164 2527 emit canDeleteSelection(haveSelection && haveCurrentEditableLayer);
Chris@164 2528 m_deleteSelectedAction->setText(tr("&Delete Selected Items"));
Chris@164 2529 m_deleteSelectedAction->setStatusTip(tr("Delete items in current selection from the current layer"));
Chris@164 2530 }
Chris@164 2531
Chris@155 2532 if (m_ffwdAction && m_rwdAction) {
Chris@155 2533 if (haveCurrentTimeInstantsLayer) {
Chris@155 2534 m_ffwdAction->setText(tr("Fast Forward to Next Instant"));
Chris@155 2535 m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer"));
Chris@155 2536 m_rwdAction->setText(tr("Rewind to Previous Instant"));
Chris@155 2537 m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer"));
Chris@155 2538 } else if (haveCurrentTimeValueLayer) {
Chris@155 2539 m_ffwdAction->setText(tr("Fast Forward to Next Point"));
Chris@155 2540 m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer"));
Chris@155 2541 m_rwdAction->setText(tr("Rewind to Previous Point"));
Chris@155 2542 m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer"));
Chris@155 2543 } else {
Chris@155 2544 m_ffwdAction->setText(tr("Fast Forward"));
Chris@155 2545 m_ffwdAction->setStatusTip(tr("Fast forward"));
Chris@155 2546 m_rwdAction->setText(tr("Rewind"));
Chris@155 2547 m_rwdAction->setStatusTip(tr("Rewind"));
Chris@155 2548 }
Chris@155 2549 }
Chris@0 2550 }
Chris@0 2551
Chris@0 2552 void
Chris@0 2553 MainWindow::updateDescriptionLabel()
Chris@0 2554 {
Chris@0 2555 if (!getMainModel()) {
Chris@1770 2556 m_descriptionLabel->setText(tr("No audio file loaded."));
Chris@1770 2557 return;
Chris@0 2558 }
Chris@0 2559
Chris@0 2560 QString description;
Chris@0 2561
Chris@1404 2562 //!!!???
Chris@1404 2563
Chris@922 2564 sv_samplerate_t ssr = getMainModel()->getSampleRate();
Chris@922 2565 sv_samplerate_t tsr = ssr;
Chris@1405 2566 if (m_playSource) tsr = m_playSource->getDeviceSampleRate();
Chris@0 2567
Chris@0 2568 if (ssr != tsr) {
Chris@1770 2569 description = tr("%1Hz (resampling to %2Hz)").arg(ssr).arg(tsr);
Chris@0 2570 } else {
Chris@1770 2571 description = QString("%1Hz").arg(ssr);
Chris@0 2572 }
Chris@0 2573
Chris@0 2574 description = QString("%1 - %2")
Chris@1770 2575 .arg(RealTime::frame2RealTime(getMainModel()->getEndFrame(), ssr)
Chris@1770 2576 .toText(false).c_str())
Chris@1770 2577 .arg(description);
Chris@0 2578
Chris@0 2579 m_descriptionLabel->setText(description);
Chris@0 2580 }
Chris@0 2581
Chris@0 2582 void
Chris@0 2583 MainWindow::documentModified()
Chris@0 2584 {
Chris@200 2585 //!!!
Chris@200 2586 MainWindowBase::documentModified();
Chris@0 2587 }
Chris@0 2588
Chris@0 2589 void
Chris@0 2590 MainWindow::documentRestored()
Chris@0 2591 {
Chris@200 2592 //!!!
Chris@200 2593 MainWindowBase::documentRestored();
Chris@0 2594 }
Chris@0 2595
Chris@0 2596 void
Chris@0 2597 MainWindow::toolNavigateSelected()
Chris@0 2598 {
Chris@0 2599 m_viewManager->setToolMode(ViewManager::NavigateMode);
Chris@0 2600 }
Chris@0 2601
Chris@0 2602 void
Chris@0 2603 MainWindow::toolSelectSelected()
Chris@0 2604 {
Chris@0 2605 m_viewManager->setToolMode(ViewManager::SelectMode);
Chris@0 2606 }
Chris@0 2607
Chris@0 2608 void
Chris@0 2609 MainWindow::toolEditSelected()
Chris@0 2610 {
Chris@0 2611 m_viewManager->setToolMode(ViewManager::EditMode);
Chris@0 2612 }
Chris@0 2613
Chris@0 2614 void
Chris@0 2615 MainWindow::toolDrawSelected()
Chris@0 2616 {
Chris@0 2617 m_viewManager->setToolMode(ViewManager::DrawMode);
Chris@0 2618 }
Chris@0 2619
Chris@151 2620 void
Chris@217 2621 MainWindow::toolEraseSelected()
Chris@217 2622 {
Chris@217 2623 m_viewManager->setToolMode(ViewManager::EraseMode);
Chris@217 2624 }
Chris@217 2625
Chris@217 2626 void
Chris@151 2627 MainWindow::toolMeasureSelected()
Chris@151 2628 {
Chris@151 2629 m_viewManager->setToolMode(ViewManager::MeasureMode);
Chris@151 2630 }
Chris@151 2631
Chris@0 2632 void
Chris@0 2633 MainWindow::importAudio()
Chris@0 2634 {
Chris@88 2635 QString path = getOpenFileName(FileFinder::AudioFile);
Chris@0 2636
Chris@0 2637 if (path != "") {
Chris@1770 2638 if (openAudio(path, ReplaceSession) == FileOpenFailed) {
Chris@247 2639 emit hideSplash();
Chris@1770 2640 QMessageBox::critical(this, tr("Failed to open file"),
Chris@1770 2641 tr("<b>File open failed</b><p>Audio file \"%1\" could not be opened").arg(path));
Chris@1770 2642 }
Chris@0 2643 }
Chris@0 2644 }
Chris@0 2645
Chris@0 2646 void
Chris@0 2647 MainWindow::importMoreAudio()
Chris@0 2648 {
Chris@88 2649 QString path = getOpenFileName(FileFinder::AudioFile);
Chris@0 2650
Chris@0 2651 if (path != "") {
Chris@1770 2652 if (openAudio(path, CreateAdditionalModel) == FileOpenFailed) {
Chris@247 2653 emit hideSplash();
Chris@1770 2654 QMessageBox::critical(this, tr("Failed to open file"),
Chris@1770 2655 tr("<b>File open failed</b><p>Audio file \"%1\" could not be opened").arg(path));
Chris@1770 2656 }
Chris@0 2657 }
Chris@0 2658 }
Chris@0 2659
Chris@0 2660 void
Chris@508 2661 MainWindow::replaceMainAudio()
Chris@508 2662 {
Chris@508 2663 QString path = getOpenFileName(FileFinder::AudioFile);
Chris@508 2664
Chris@508 2665 if (path != "") {
Chris@1770 2666 if (openAudio(path, ReplaceMainModel) == FileOpenFailed) {
Chris@508 2667 emit hideSplash();
Chris@1770 2668 QMessageBox::critical(this, tr("Failed to open file"),
Chris@1770 2669 tr("<b>File open failed</b><p>Audio file \"%1\" could not be opened").arg(path));
Chris@1770 2670 }
Chris@508 2671 }
Chris@508 2672 }
Chris@508 2673
Chris@508 2674 void
Chris@0 2675 MainWindow::exportAudio()
Chris@0 2676 {
Chris@631 2677 exportAudio(false);
Chris@631 2678 }
Chris@631 2679
Chris@631 2680 void
Chris@631 2681 MainWindow::exportAudioData()
Chris@631 2682 {
Chris@631 2683 exportAudio(true);
Chris@631 2684 }
Chris@631 2685
Chris@631 2686 void
Chris@631 2687 MainWindow::exportAudio(bool asData)
Chris@631 2688 {
Chris@0 2689 if (!getMainModel()) return;
Chris@0 2690
Chris@320 2691 RangeSummarisableTimeValueModel *model = getMainModel();
Chris@320 2692 std::set<RangeSummarisableTimeValueModel *> otherModels;
Chris@320 2693 RangeSummarisableTimeValueModel *current = model;
Chris@320 2694 if (m_paneStack) {
Chris@320 2695 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@320 2696 Pane *pane = m_paneStack->getPane(i);
Chris@320 2697 if (!pane) continue;
Chris@320 2698 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@320 2699 Layer *layer = pane->getLayer(j);
Chris@320 2700 if (!layer) continue;
Chris@432 2701 cerr << "layer = " << layer->objectName() << endl;
Chris@320 2702 Model *m = layer->getModel();
Chris@320 2703 RangeSummarisableTimeValueModel *wm =
Chris@320 2704 dynamic_cast<RangeSummarisableTimeValueModel *>(m);
Chris@320 2705 if (wm) {
Chris@432 2706 cerr << "found: " << wm->objectName() << endl;
Chris@320 2707 otherModels.insert(wm);
Chris@320 2708 if (pane == m_paneStack->getCurrentPane()) {
Chris@320 2709 current = wm;
Chris@320 2710 }
Chris@320 2711 }
Chris@320 2712 }
Chris@320 2713 }
Chris@320 2714 }
Chris@320 2715 if (!otherModels.empty()) {
Chris@320 2716 std::map<QString, RangeSummarisableTimeValueModel *> m;
Chris@320 2717 m[tr("1. %2").arg(model->objectName())] = model;
Chris@320 2718 int n = 2;
Chris@320 2719 int c = 0;
Chris@320 2720 for (std::set<RangeSummarisableTimeValueModel *>::const_iterator i
Chris@320 2721 = otherModels.begin();
Chris@320 2722 i != otherModels.end(); ++i) {
Chris@320 2723 if (*i == model) continue;
Chris@320 2724 m[tr("%1. %2").arg(n).arg((*i)->objectName())] = *i;
Chris@320 2725 ++n;
Chris@320 2726 if (*i == current) c = n-1;
Chris@320 2727 }
Chris@320 2728 QStringList items;
Chris@320 2729 for (std::map<QString, RangeSummarisableTimeValueModel *>
Chris@320 2730 ::const_iterator i = m.begin();
Chris@320 2731 i != m.end(); ++i) {
Chris@320 2732 items << i->first;
Chris@320 2733 }
Chris@325 2734 if (items.size() > 1) {
Chris@325 2735 bool ok = false;
Chris@325 2736 QString item = QInputDialog::getItem
Chris@325 2737 (this, tr("Select audio file to export"),
Chris@325 2738 tr("Which audio file do you want to export from?"),
Chris@325 2739 items, c, false, &ok);
Chris@325 2740 if (!ok || item.isEmpty()) return;
Chris@325 2741 if (m.find(item) == m.end()) {
Chris@665 2742 cerr << "WARNING: Model " << item
Chris@325 2743 << " not found in list!" << endl;
Chris@325 2744 } else {
Chris@325 2745 model = m[item];
Chris@325 2746 }
Chris@320 2747 }
Chris@320 2748 }
Chris@320 2749
Chris@631 2750 QString path;
Chris@631 2751 if (asData) {
Chris@631 2752 path = getSaveFileName(FileFinder::CSVFile);
Chris@631 2753 } else {
Chris@631 2754 path = getSaveFileName(FileFinder::AudioFile);
Chris@631 2755 }
Chris@0 2756 if (path == "") return;
Chris@0 2757
Chris@0 2758 bool ok = false;
Chris@0 2759 QString error;
Chris@0 2760
Chris@0 2761 MultiSelection ms = m_viewManager->getSelection();
Chris@0 2762 MultiSelection::SelectionList selections = m_viewManager->getSelections();
Chris@0 2763
Chris@0 2764 bool multiple = false;
Chris@0 2765
Chris@38 2766 MultiSelection *selectionToWrite = 0;
Chris@38 2767
Chris@38 2768 if (selections.size() == 1) {
Chris@0 2769
Chris@1770 2770 QStringList items;
Chris@1770 2771 items << tr("Export the selected region only")
Chris@1770 2772 << tr("Export the whole audio file");
Chris@1770 2773
Chris@1770 2774 bool ok = false;
Chris@1770 2775 QString item = ListInputDialog::getItem
Chris@1770 2776 (this, tr("Select region to export"),
Chris@1770 2777 tr("Which region from the original audio file do you want to export?"),
Chris@1770 2778 items, 0, &ok);
Chris@1770 2779
Chris@1770 2780 if (!ok || item.isEmpty()) return;
Chris@1770 2781
Chris@1770 2782 if (item == items[0]) selectionToWrite = &ms;
Chris@38 2783
Chris@38 2784 } else if (selections.size() > 1) {
Chris@0 2785
Chris@631 2786 if (!asData) { // Multi-file export not supported for data
Chris@631 2787
Chris@631 2788 QStringList items;
Chris@631 2789 items << tr("Export the selected regions into a single file")
Chris@631 2790 << tr("Export the selected regions into separate files")
Chris@631 2791 << tr("Export the whole file");
Chris@631 2792
Chris@631 2793 QString item = ListInputDialog::getItem
Chris@631 2794 (this, tr("Select region to export"),
Chris@631 2795 tr("Multiple regions of the original audio file are selected.\nWhat do you want to export?"),
Chris@631 2796 items, 0, &ok);
Chris@1770 2797
Chris@631 2798 if (!ok || item.isEmpty()) return;
Chris@631 2799
Chris@631 2800 if (item == items[0]) {
Chris@631 2801 selectionToWrite = &ms;
Chris@631 2802 } else if (item == items[1]) {
Chris@631 2803 multiple = true;
Chris@631 2804 }
Chris@631 2805
Chris@631 2806 } else { // asData
Chris@38 2807 selectionToWrite = &ms;
Chris@631 2808 }
Chris@631 2809
Chris@631 2810 if (multiple) { // Can only happen when asData false
Chris@0 2811
Chris@1770 2812 int n = 1;
Chris@1770 2813 QString base = path;
Chris@1770 2814 base.replace(".wav", "");
Chris@1770 2815
Chris@1770 2816 for (MultiSelection::SelectionList::iterator i = selections.begin();
Chris@1770 2817 i != selections.end(); ++i) {
Chris@1770 2818
Chris@1770 2819 MultiSelection subms;
Chris@1770 2820 subms.setSelection(*i);
Chris@1770 2821
Chris@1770 2822 QString subpath = QString("%1.%2.wav").arg(base).arg(n);
Chris@1770 2823 ++n;
Chris@1770 2824
Chris@1770 2825 if (QFileInfo(subpath).exists()) {
Chris@1770 2826 error = tr("Fragment file %1 already exists, aborting").arg(subpath);
Chris@1770 2827 break;
Chris@1770 2828 }
Chris@1770 2829
Chris@1770 2830 WavFileWriter subwriter(subpath,
Chris@320 2831 model->getSampleRate(),
Chris@428 2832 model->getChannelCount(),
Chris@428 2833 WavFileWriter::WriteToTemporary);
Chris@320 2834 subwriter.writeModel(model, &subms);
Chris@1770 2835 ok = subwriter.isOK();
Chris@1770 2836
Chris@1770 2837 if (!ok) {
Chris@1770 2838 error = subwriter.getError();
Chris@1770 2839 break;
Chris@1770 2840 }
Chris@1770 2841 }
Chris@1770 2842 }
Chris@0 2843 }
Chris@0 2844
Chris@38 2845 if (!multiple) {
Chris@631 2846 if (asData) {
Chris@1779 2847 stop();
Chris@1779 2848 ProgressDialog dialog {
Chris@1779 2849 QObject::tr("Exporting audio data..."),
Chris@1779 2850 true,
Chris@1779 2851 0,
Chris@1779 2852 this,
Chris@1779 2853 Qt::ApplicationModal
Chris@1779 2854 };
Chris@1779 2855 CSVFileWriter writer(path, model, &dialog,
Chris@631 2856 ((QFileInfo(path).suffix() == "csv") ?
Chris@631 2857 "," : "\t"));
Chris@631 2858 if (selectionToWrite) {
Chris@1779 2859 writer.writeSelection(*selectionToWrite);
Chris@631 2860 } else {
Chris@631 2861 writer.write();
Chris@631 2862 }
Chris@631 2863 ok = writer.isOK();
Chris@631 2864 error = writer.getError();
Chris@631 2865 } else {
Chris@631 2866 WavFileWriter writer(path,
Chris@631 2867 model->getSampleRate(),
Chris@631 2868 model->getChannelCount(),
Chris@631 2869 WavFileWriter::WriteToTemporary);
Chris@631 2870 writer.writeModel(model, selectionToWrite);
Chris@631 2871 ok = writer.isOK();
Chris@631 2872 error = writer.getError();
Chris@631 2873 }
Chris@0 2874 }
Chris@0 2875
Chris@0 2876 if (ok) {
Chris@310 2877 if (multiple) {
Chris@310 2878 emit activity(tr("Export multiple audio files"));
Chris@310 2879 } else {
Chris@310 2880 emit activity(tr("Export audio to \"%1\"").arg(path));
Chris@34 2881 m_recentFiles.addFile(path);
Chris@0 2882 }
Chris@0 2883 } else {
Chris@1770 2884 QMessageBox::critical(this, tr("Failed to write file"), error);
Chris@0 2885 }
Chris@0 2886 }
Chris@0 2887
Chris@0 2888 void
Chris@1995 2889 MainWindow::convertAudio()
Chris@1902 2890 {
Chris@1902 2891 QString path = getOpenFileName(FileFinder::CSVFile);
Chris@1902 2892 if (path == "") return;
Chris@1902 2893
Chris@1995 2894 sv_samplerate_t defaultRate = 44100;
Chris@1902 2895
Chris@1902 2896 CSVFormat format(path);
Chris@1902 2897 format.setModelType(CSVFormat::WaveFileModel);
Chris@1902 2898 format.setTimingType(CSVFormat::ImplicitTiming);
Chris@1902 2899 format.setTimeUnits(CSVFormat::TimeAudioFrames);
Chris@1995 2900 format.setSampleRate(defaultRate); // as a default for the dialog
Chris@1995 2901
Chris@1987 2902 {
Chris@1989 2903 CSVAudioFormatDialog *dialog = new CSVAudioFormatDialog(this, format);
Chris@1987 2904 if (dialog->exec() != QDialog::Accepted) {
Chris@1987 2905 delete dialog;
Chris@1987 2906 return;
Chris@1987 2907 }
Chris@1987 2908 format = dialog->getFormat();
Chris@1987 2909 delete dialog;
Chris@1987 2910 }
Chris@1987 2911
Chris@1902 2912 FileOpenStatus status = FileOpenSucceeded;
Chris@1911 2913
Chris@1987 2914 ProgressDialog *progress = new ProgressDialog
Chris@2001 2915 (tr("Converting audio data..."), true, 0, this, Qt::ApplicationModal);
Chris@1902 2916
Chris@1902 2917 WaveFileModel *model = qobject_cast<WaveFileModel *>
Chris@1902 2918 (DataFileReaderFactory::loadCSV
Chris@1902 2919 (path, format,
Chris@1995 2920 getMainModel() ? getMainModel()->getSampleRate() : defaultRate,
Chris@1987 2921 progress));
Chris@1987 2922
Chris@1987 2923 if (progress->wasCancelled()) {
Chris@1911 2924
Chris@1911 2925 delete model;
Chris@1911 2926 status = FileOpenCancelled;
Chris@1911 2927
Chris@1911 2928 } else if (!model || !model->isOK()) {
Chris@1902 2929
Chris@1902 2930 delete model;
Chris@1902 2931 status = FileOpenFailed;
Chris@1902 2932
Chris@1902 2933 } else {
Chris@1902 2934
Chris@1902 2935 status = addOpenedAudioModel(path,
Chris@1902 2936 model,
Chris@1902 2937 CreateAdditionalModel,
Chris@1902 2938 getDefaultSessionTemplate(),
Chris@1902 2939 false);
Chris@1902 2940 }
Chris@1902 2941
Chris@1987 2942 delete progress;
Chris@1911 2943
Chris@1902 2944 if (status == FileOpenFailed) {
Chris@1902 2945 emit hideSplash();
Chris@1902 2946 QMessageBox::critical(this, tr("Failed to open file"),
Chris@1902 2947 tr("<b>File open failed</b><p>Audio data file %1 could not be opened.").arg(path));
Chris@1902 2948 }
Chris@1902 2949 }
Chris@1902 2950
Chris@1902 2951 void
Chris@0 2952 MainWindow::importLayer()
Chris@0 2953 {
Chris@0 2954 Pane *pane = m_paneStack->getCurrentPane();
Chris@0 2955
Chris@0 2956 if (!pane) {
Chris@1770 2957 // shouldn't happen, as the menu action should have been disabled
Chris@1770 2958 cerr << "WARNING: MainWindow::importLayer: no current pane" << endl;
Chris@1770 2959 return;
Chris@0 2960 }
Chris@0 2961
Chris@0 2962 if (!getMainModel()) {
Chris@1770 2963 // shouldn't happen, as the menu action should have been disabled
Chris@1770 2964 cerr << "WARNING: MainWindow::importLayer: No main model -- hence no default sample rate available" << endl;
Chris@1770 2965 return;
Chris@0 2966 }
Chris@0 2967
Chris@88 2968 QString path = getOpenFileName(FileFinder::LayerFile);
Chris@0 2969
Chris@0 2970 if (path != "") {
Chris@0 2971
Chris@197 2972 FileOpenStatus status = openLayer(path);
Chris@193 2973
Chris@193 2974 if (status == FileOpenFailed) {
Chris@247 2975 emit hideSplash();
Chris@0 2976 QMessageBox::critical(this, tr("Failed to open file"),
Chris@193 2977 tr("<b>File open failed</b><p>Layer file %1 could not be opened.").arg(path));
Chris@0 2978 return;
Chris@193 2979 } else if (status == FileOpenWrongMode) {
Chris@247 2980 emit hideSplash();
Chris@193 2981 QMessageBox::critical(this, tr("Failed to open file"),
Chris@294 2982 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 2983 }
Chris@0 2984 }
Chris@0 2985 }
Chris@0 2986
Chris@0 2987 void
Chris@0 2988 MainWindow::exportLayer()
Chris@0 2989 {
Chris@0 2990 Pane *pane = m_paneStack->getCurrentPane();
Chris@0 2991 if (!pane) return;
Chris@0 2992
Chris@0 2993 Layer *layer = pane->getSelectedLayer();
Chris@0 2994 if (!layer) return;
Chris@0 2995
Chris@0 2996 Model *model = layer->getModel();
Chris@0 2997 if (!model) return;
Chris@0 2998
Chris@185 2999 FileFinder::FileType type = FileFinder::LayerFileNoMidi;
Chris@185 3000
Chris@185 3001 if (dynamic_cast<NoteModel *>(model)) type = FileFinder::LayerFile;
Chris@185 3002
Chris@185 3003 QString path = getSaveFileName(type);
Chris@0 3004
Chris@0 3005 if (path == "") return;
Chris@0 3006
Chris@0 3007 if (QFileInfo(path).suffix() == "") path += ".svl";
Chris@0 3008
Chris@185 3009 QString suffix = QFileInfo(path).suffix().toLower();
Chris@185 3010
Chris@0 3011 QString error;
Chris@0 3012
Chris@185 3013 if (suffix == "xml" || suffix == "svl") {
Chris@0 3014
Chris@0 3015 QFile file(path);
Chris@0 3016 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
Chris@0 3017 error = tr("Failed to open file %1 for writing").arg(path);
Chris@0 3018 } else {
Chris@0 3019 QTextStream out(&file);
Chris@911 3020 out.setCodec(QTextCodec::codecForName("UTF-8"));
Chris@0 3021 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
Chris@0 3022 << "<!DOCTYPE sonic-visualiser>\n"
Chris@0 3023 << "<sv>\n"
Chris@0 3024 << " <data>\n";
Chris@0 3025
Chris@0 3026 model->toXml(out, " ");
Chris@0 3027
Chris@0 3028 out << " </data>\n"
Chris@0 3029 << " <display>\n";
Chris@0 3030
Chris@0 3031 layer->toXml(out, " ");
Chris@0 3032
Chris@0 3033 out << " </display>\n"
Chris@0 3034 << "</sv>\n";
Chris@0 3035 }
Chris@0 3036
Chris@185 3037 } else if (suffix == "mid" || suffix == "midi") {
Chris@185 3038
Chris@185 3039 NoteModel *nm = dynamic_cast<NoteModel *>(model);
Chris@185 3040
Chris@185 3041 if (!nm) {
Chris@185 3042 error = tr("Can't export non-note layers to MIDI");
Chris@185 3043 } else {
Chris@694 3044 MIDIFileWriter writer(path, nm, nm->getSampleRate());
Chris@185 3045 writer.write();
Chris@185 3046 if (!writer.isOK()) {
Chris@185 3047 error = writer.getError();
Chris@185 3048 }
Chris@185 3049 }
Chris@185 3050
Chris@291 3051 } else if (suffix == "ttl" || suffix == "n3") {
Chris@291 3052
Chris@522 3053 if (!RDFExporter::canExportModel(model)) {
Chris@522 3054 error = tr("Sorry, cannot export this layer type to RDF (supported types are: region, note, text, time instants, time values)");
Chris@522 3055 } else {
Chris@522 3056 RDFExporter exporter(path, model);
Chris@522 3057 exporter.write();
Chris@522 3058 if (!exporter.isOK()) {
Chris@522 3059 error = exporter.getError();
Chris@522 3060 }
Chris@291 3061 }
Chris@291 3062
Chris@0 3063 } else {
Chris@0 3064
Chris@0 3065 CSVFileWriter writer(path, model,
Chris@185 3066 ((suffix == "csv") ? "," : "\t"));
Chris@0 3067 writer.write();
Chris@0 3068
Chris@0 3069 if (!writer.isOK()) {
Chris@0 3070 error = writer.getError();
Chris@0 3071 }
Chris@0 3072 }
Chris@0 3073
Chris@0 3074 if (error != "") {
Chris@0 3075 QMessageBox::critical(this, tr("Failed to write file"), error);
Chris@0 3076 } else {
Chris@34 3077 m_recentFiles.addFile(path);
Chris@310 3078 emit activity(tr("Export layer to \"%1\"").arg(path));
Chris@0 3079 }
Chris@0 3080 }
Chris@0 3081
Chris@121 3082 void
Chris@121 3083 MainWindow::exportImage()
Chris@121 3084 {
Chris@121 3085 Pane *pane = m_paneStack->getCurrentPane();
Chris@121 3086 if (!pane) return;
Chris@121 3087
Chris@121 3088 QString path = getSaveFileName(FileFinder::ImageFile);
Chris@121 3089 if (path == "") return;
Chris@1460 3090 if (QFileInfo(path).suffix() == "") path += ".png";
Chris@1451 3091
Chris@123 3092 bool haveSelection = m_viewManager && !m_viewManager->getSelections().empty();
Chris@123 3093
Chris@123 3094 QSize total, visible, selected;
Chris@1451 3095 total = pane->getRenderedImageSize();
Chris@1451 3096 visible = pane->getRenderedPartImageSize(pane->getFirstVisibleFrame(),
Chris@1451 3097 pane->getLastVisibleFrame());
Chris@123 3098
Chris@922 3099 sv_frame_t sf0 = 0, sf1 = 0;
Chris@123 3100
Chris@123 3101 if (haveSelection) {
Chris@123 3102 MultiSelection::SelectionList selections = m_viewManager->getSelections();
Chris@123 3103 sf0 = selections.begin()->getStartFrame();
Chris@123 3104 MultiSelection::SelectionList::iterator e = selections.end();
Chris@123 3105 --e;
Chris@123 3106 sf1 = e->getEndFrame();
Chris@1451 3107 selected = pane->getRenderedPartImageSize(sf0, sf1);
Chris@123 3108 }
Chris@123 3109
Chris@123 3110 QStringList items;
Chris@125 3111 items << tr("Export the whole pane (%1x%2 pixels)")
Chris@123 3112 .arg(total.width()).arg(total.height());
Chris@123 3113 items << tr("Export the visible area only (%1x%2 pixels)")
Chris@123 3114 .arg(visible.width()).arg(visible.height());
Chris@123 3115 if (haveSelection) {
Chris@123 3116 items << tr("Export the selection extent (%1x%2 pixels)")
Chris@123 3117 .arg(selected.width()).arg(selected.height());
Chris@124 3118 } else {
Chris@124 3119 items << tr("Export the selection extent");
Chris@123 3120 }
Chris@123 3121
Chris@123 3122 QSettings settings;
Chris@123 3123 settings.beginGroup("MainWindow");
Chris@123 3124 int deflt = settings.value("lastimageexportregion", 0).toInt();
Chris@123 3125 if (deflt == 2 && !haveSelection) deflt = 1;
Chris@1460 3126 if (deflt == 0 && total.width() > 32767) deflt = 1;
Chris@124 3127
Chris@124 3128 ListInputDialog *lid = new ListInputDialog
Chris@123 3129 (this, tr("Select region to export"),
Chris@123 3130 tr("Which region of the current pane do you want to export as an image?"),
Chris@124 3131 items, deflt);
Chris@124 3132
Chris@124 3133 if (!haveSelection) {
Chris@124 3134 lid->setItemAvailability(2, false);
Chris@124 3135 }
Chris@1460 3136 if (total.width() > 32767) { // appears to be limit of a QImage
Chris@124 3137 lid->setItemAvailability(0, false);
Chris@124 3138 lid->setFootnote(tr("Note: the whole pane is too wide to be exported as a single image."));
Chris@124 3139 }
Chris@124 3140
Chris@124 3141 bool ok = lid->exec();
Chris@124 3142 QString item = lid->getCurrentString();
Chris@124 3143 delete lid;
Chris@1770 3144
Chris@123 3145 if (!ok || item.isEmpty()) return;
Chris@123 3146
Chris@123 3147 settings.setValue("lastimageexportregion", deflt);
Chris@123 3148
Chris@1460 3149 QImage *image = 0;
Chris@1451 3150
Chris@1460 3151 if (item == items[0]) {
Chris@1460 3152 image = pane->renderToNewImage();
Chris@1460 3153 } else if (item == items[1]) {
Chris@1460 3154 image = pane->renderPartToNewImage(pane->getFirstVisibleFrame(),
Chris@1460 3155 pane->getLastVisibleFrame());
Chris@1460 3156 } else if (haveSelection) {
Chris@1460 3157 image = pane->renderPartToNewImage(sf0, sf1);
Chris@1451 3158 }
Chris@1460 3159
Chris@1460 3160 if (!image) return;
Chris@1460 3161
Chris@1460 3162 if (!image->save(path, "PNG")) {
Chris@1460 3163 QMessageBox::critical(this, tr("Failed to save image file"),
Chris@1460 3164 tr("Failed to save image file %1").arg(path));
Chris@1460 3165 }
Chris@1460 3166
Chris@1460 3167 delete image;
Chris@1451 3168 }
Chris@1451 3169
Chris@1451 3170 void
Chris@1451 3171 MainWindow::exportSVG()
Chris@1451 3172 {
Chris@1451 3173 Pane *pane = m_paneStack->getCurrentPane();
Chris@1451 3174 if (!pane) return;
Chris@1451 3175
Chris@1451 3176 QString path = getSaveFileName(FileFinder::SVGFile);
Chris@1451 3177 if (path == "") return;
Chris@1451 3178 if (QFileInfo(path).suffix() == "") path += ".svg";
Chris@1451 3179
Chris@1451 3180 bool haveSelection = m_viewManager && !m_viewManager->getSelections().empty();
Chris@1451 3181
Chris@1451 3182 sv_frame_t sf0 = 0, sf1 = 0;
Chris@1451 3183
Chris@1451 3184 if (haveSelection) {
Chris@1451 3185 MultiSelection::SelectionList selections = m_viewManager->getSelections();
Chris@1451 3186 sf0 = selections.begin()->getStartFrame();
Chris@1451 3187 MultiSelection::SelectionList::iterator e = selections.end();
Chris@1451 3188 --e;
Chris@1451 3189 sf1 = e->getEndFrame();
Chris@1451 3190 }
Chris@1451 3191
Chris@1451 3192 QStringList items;
Chris@1451 3193 items << tr("Export the whole pane");
Chris@1451 3194 items << tr("Export the visible area only");
Chris@1451 3195 items << tr("Export the selection extent");
Chris@1451 3196
Chris@1451 3197 QSettings settings;
Chris@1451 3198 settings.beginGroup("MainWindow");
Chris@1451 3199 int deflt = settings.value("lastsvgexportregion", 0).toInt();
Chris@1451 3200 if (deflt == 2 && !haveSelection) deflt = 1;
Chris@1451 3201
Chris@1451 3202 ListInputDialog *lid = new ListInputDialog
Chris@1451 3203 (this, tr("Select region to export"),
Chris@1451 3204 tr("Which region of the current pane do you want to export as a scalable SVG image?"),
Chris@1451 3205 items, deflt);
Chris@1451 3206
Chris@1451 3207 if (!haveSelection) {
Chris@1451 3208 lid->setItemAvailability(2, false);
Chris@1451 3209 }
Chris@1451 3210
Chris@1451 3211 bool ok = lid->exec();
Chris@1451 3212 QString item = lid->getCurrentString();
Chris@1451 3213 delete lid;
Chris@1770 3214
Chris@1451 3215 if (!ok || item.isEmpty()) return;
Chris@1451 3216
Chris@1451 3217 settings.setValue("lastsvgexportregion", deflt);
Chris@1451 3218
Chris@1451 3219 bool result = false;
Chris@1451 3220
Chris@123 3221 if (item == items[0]) {
Chris@1451 3222 result = pane->renderToSvgFile(path );
Chris@123 3223 } else if (item == items[1]) {
Chris@1451 3224 result = pane->renderPartToSvgFile(path,
Chris@1451 3225 pane->getFirstVisibleFrame(),
Chris@1451 3226 pane->getLastVisibleFrame());
Chris@123 3227 } else if (haveSelection) {
Chris@1451 3228 result = pane->renderPartToSvgFile(path, sf0, sf1);
Chris@121 3229 }
Chris@121 3230
Chris@1451 3231 if (!result) {
Chris@1451 3232 QMessageBox::critical(this, tr("Failed to save SVG file"),
Chris@1451 3233 tr("Failed to save SVG file %1").arg(path));
Chris@1451 3234 }
Chris@121 3235 }
Chris@121 3236
Chris@0 3237 void
Chris@1056 3238 MainWindow::browseRecordedAudio()
Chris@1056 3239 {
Chris@1056 3240 if (!m_recordTarget) return;
Chris@1056 3241
Chris@1995 3242 QString path = RecordDirectory::getRecordContainerDirectory();
Chris@1995 3243 if (path == "") path = RecordDirectory::getRecordDirectory();
Chris@1056 3244 if (path == "") return;
Chris@1056 3245
Chris@1056 3246 openLocalFolder(path);
Chris@1056 3247 }
Chris@1056 3248
Chris@1056 3249 void
Chris@0 3250 MainWindow::newSession()
Chris@0 3251 {
Chris@0 3252 if (!checkSaveModified()) return;
Chris@0 3253
Chris@0 3254 closeSession();
Chris@2062 3255 stop();
Chris@0 3256 createDocument();
Chris@0 3257
Chris@0 3258 Pane *pane = m_paneStack->addPane();
Chris@0 3259
Chris@90 3260 connect(pane, SIGNAL(contextHelpChanged(const QString &)),
Chris@116 3261 this, SLOT(contextHelpChanged(const QString &)));
Chris@90 3262
Chris@0 3263 if (!m_timeRulerLayer) {
Chris@1770 3264 m_timeRulerLayer = m_document->createMainModelLayer
Chris@1770 3265 (LayerFactory::TimeRuler);
Chris@0 3266 }
Chris@0 3267
Chris@0 3268 m_document->addLayerToView(pane, m_timeRulerLayer);
Chris@0 3269
Chris@0 3270 Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform);
Chris@0 3271 m_document->addLayerToView(pane, waveform);
Chris@0 3272
Chris@65 3273 m_overview->registerView(pane);
Chris@0 3274
Chris@0 3275 CommandHistory::getInstance()->clear();
Chris@0 3276 CommandHistory::getInstance()->documentSaved();
Chris@0 3277 documentRestored();
Chris@0 3278 updateMenuStates();
Chris@0 3279 }
Chris@0 3280
Chris@0 3281 void
Chris@303 3282 MainWindow::documentReplaced()
Chris@303 3283 {
Chris@303 3284 if (m_document) {
Chris@303 3285 connect(m_document, SIGNAL(activity(QString)),
Chris@303 3286 m_activityLog, SLOT(activityHappened(QString)));
Chris@303 3287 }
Chris@303 3288 }
Chris@303 3289
Chris@303 3290 void
Chris@0 3291 MainWindow::closeSession()
Chris@0 3292 {
Chris@0 3293 if (!checkSaveModified()) return;
Chris@0 3294
Chris@0 3295 while (m_paneStack->getPaneCount() > 0) {
Chris@0 3296
Chris@1770 3297 Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1);
Chris@1770 3298
Chris@1770 3299 while (pane->getLayerCount() > 0) {
Chris@1770 3300 m_document->removeLayerFromView
Chris@1770 3301 (pane, pane->getLayer(pane->getLayerCount() - 1));
Chris@1770 3302 }
Chris@1770 3303
Chris@1770 3304 m_overview->unregisterView(pane);
Chris@1770 3305 m_paneStack->deletePane(pane);
Chris@0 3306 }
Chris@0 3307
Chris@0 3308 while (m_paneStack->getHiddenPaneCount() > 0) {
Chris@0 3309
Chris@1770 3310 Pane *pane = m_paneStack->getHiddenPane
Chris@1770 3311 (m_paneStack->getHiddenPaneCount() - 1);
Chris@1770 3312
Chris@1770 3313 while (pane->getLayerCount() > 0) {
Chris@1770 3314 m_document->removeLayerFromView
Chris@1770 3315 (pane, pane->getLayer(pane->getLayerCount() - 1));
Chris@1770 3316 }
Chris@1770 3317
Chris@1770 3318 m_overview->unregisterView(pane);
Chris@1770 3319 m_paneStack->deletePane(pane);
Chris@0 3320 }
Chris@0 3321
Chris@504 3322 delete m_layerTreeDialog.data();
Chris@504 3323 delete m_preferencesDialog.data();
Chris@504 3324
Chris@504 3325 m_activityLog->hide();
Chris@891 3326 m_unitConverter->hide();
Chris@504 3327 m_keyReference->hide();
Chris@504 3328
Chris@0 3329 delete m_document;
Chris@0 3330 m_document = 0;
Chris@0 3331 m_viewManager->clearSelections();
Chris@0 3332 m_timeRulerLayer = 0; // document owned this
Chris@0 3333
Chris@0 3334 m_sessionFile = "";
Chris@518 3335 setWindowTitle(QApplication::applicationName());
Chris@0 3336
Chris@0 3337 CommandHistory::getInstance()->clear();
Chris@0 3338 CommandHistory::getInstance()->documentSaved();
Chris@0 3339 documentRestored();
Chris@0 3340 }
Chris@0 3341
Chris@0 3342 void
Chris@0 3343 MainWindow::openSomething()
Chris@0 3344 {
Chris@0 3345 QString orig = m_audioFile;
Chris@0 3346 if (orig == "") orig = ".";
Chris@0 3347 else orig = QFileInfo(orig).absoluteDir().canonicalPath();
Chris@0 3348
Chris@88 3349 QString path = getOpenFileName(FileFinder::AnyFile);
Chris@0 3350
Chris@0 3351 if (path.isEmpty()) return;
Chris@0 3352
Chris@844 3353 FileOpenStatus status = openPath(path, ReplaceSession);
Chris@193 3354
Chris@193 3355 if (status == FileOpenFailed) {
Chris@247 3356 emit hideSplash();
Chris@193 3357 QMessageBox::critical(this, tr("Failed to open file"),
Chris@193 3358 tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path));
Chris@193 3359 } else if (status == FileOpenWrongMode) {
Chris@247 3360 emit hideSplash();
Chris@193 3361 QMessageBox::critical(this, tr("Failed to open file"),
Chris@294 3362 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 3363 }
Chris@0 3364 }
Chris@0 3365
Chris@0 3366 void
Chris@86 3367 MainWindow::openLocation()
Chris@86 3368 {
Chris@103 3369 QSettings settings;
Chris@103 3370 settings.beginGroup("MainWindow");
Chris@103 3371 QString lastLocation = settings.value("lastremote", "").toString();
Chris@103 3372
Chris@86 3373 bool ok = false;
Chris@86 3374 QString text = QInputDialog::getText
Chris@86 3375 (this, tr("Open Location"),
Chris@86 3376 tr("Please enter the URL of the location to open:"),
Chris@103 3377 QLineEdit::Normal, lastLocation, &ok);
Chris@103 3378
Chris@103 3379 if (!ok) return;
Chris@103 3380
Chris@103 3381 settings.setValue("lastremote", text);
Chris@103 3382
Chris@103 3383 if (text.isEmpty()) return;
Chris@86 3384
Chris@844 3385 FileOpenStatus status = openPath(text, AskUser);
Chris@193 3386
Chris@193 3387 if (status == FileOpenFailed) {
Chris@247 3388 emit hideSplash();
Chris@86 3389 QMessageBox::critical(this, tr("Failed to open location"),
Chris@193 3390 tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text));
Chris@193 3391 } else if (status == FileOpenWrongMode) {
Chris@247 3392 emit hideSplash();
Chris@193 3393 QMessageBox::critical(this, tr("Failed to open location"),
Chris@294 3394 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 3395 }
Chris@86 3396 }
Chris@86 3397
Chris@86 3398 void
Chris@2149 3399 MainWindow::openRecentFile()
Chris@0 3400 {
Chris@0 3401 QObject *obj = sender();
Chris@0 3402 QAction *action = dynamic_cast<QAction *>(obj);
Chris@0 3403
Chris@0 3404 if (!action) {
Chris@1770 3405 cerr << "WARNING: MainWindow::openRecentFile: sender is not an action"
Chris@2149 3406 << endl;
Chris@1770 3407 return;
Chris@0 3408 }
Chris@0 3409
Chris@2149 3410 QString path = action->objectName();
Chris@2149 3411
Chris@2149 3412 if (path == "") {
Chris@2149 3413 cerr << "WARNING: MainWindow::openRecentFile: action incorrectly named"
Chris@2149 3414 << endl;
Chris@2149 3415 return;
Chris@2149 3416 }
Chris@0 3417
Chris@844 3418 FileOpenStatus status = openPath(path, ReplaceSession);
Chris@193 3419
Chris@193 3420 if (status == FileOpenFailed) {
Chris@247 3421 emit hideSplash();
Chris@193 3422 QMessageBox::critical(this, tr("Failed to open location"),
Chris@193 3423 tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path));
Chris@193 3424 } else if (status == FileOpenWrongMode) {
Chris@247 3425 emit hideSplash();
Chris@193 3426 QMessageBox::critical(this, tr("Failed to open location"),
Chris@294 3427 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 3428 }
Chris@0 3429 }
Chris@0 3430
Chris@0 3431 void
Chris@435 3432 MainWindow::applyTemplate()
Chris@435 3433 {
Chris@435 3434 QObject *s = sender();
Chris@435 3435 QAction *action = qobject_cast<QAction *>(s);
Chris@435 3436
Chris@435 3437 if (!action) {
Chris@1770 3438 cerr << "WARNING: MainWindow::applyTemplate: sender is not an action"
Chris@1770 3439 << endl;
Chris@1770 3440 return;
Chris@435 3441 }
Chris@435 3442
Chris@435 3443 QString n = action->objectName();
Chris@435 3444 if (n == "") n = action->text();
Chris@435 3445
Chris@435 3446 if (n == "") {
Chris@665 3447 cerr << "WARNING: MainWindow::applyTemplate: sender has no name"
Chris@665 3448 << endl;
Chris@435 3449 return;
Chris@435 3450 }
Chris@435 3451
Chris@435 3452 QString mainModelLocation;
Chris@435 3453 WaveFileModel *mm = getMainModel();
Chris@435 3454 if (mm) mainModelLocation = mm->getLocation();
Chris@435 3455 if (mainModelLocation != "") {
Chris@435 3456 openAudio(mainModelLocation, ReplaceSession, n);
Chris@435 3457 } else {
Chris@435 3458 openSessionTemplate(n);
Chris@435 3459 }
Chris@435 3460 }
Chris@435 3461
Chris@435 3462 void
Chris@425 3463 MainWindow::saveSessionAsTemplate()
Chris@425 3464 {
Chris@762 3465 QDialog *d = new QDialog(this);
Chris@483 3466 d->setWindowTitle(tr("Enter template name"));
Chris@483 3467
Chris@483 3468 QGridLayout *layout = new QGridLayout;
Chris@483 3469 d->setLayout(layout);
Chris@483 3470
Chris@483 3471 layout->addWidget(new QLabel(tr("Please enter a name for the saved template:")),
Chris@483 3472 0, 0);
Chris@483 3473 QLineEdit *lineEdit = new QLineEdit;
Chris@483 3474 layout->addWidget(lineEdit, 1, 0);
Chris@483 3475 QCheckBox *makeDefault = new QCheckBox(tr("Set as default template for future audio files"));
Chris@483 3476 layout->addWidget(makeDefault, 2, 0);
Chris@425 3477
Chris@483 3478 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok |
Chris@483 3479 QDialogButtonBox::Cancel);
Chris@483 3480 layout->addWidget(bb, 3, 0);
Chris@483 3481 connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
Chris@483 3482 connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
Chris@483 3483 connect(bb, SIGNAL(rejected()), d, SLOT(reject()));
Chris@483 3484
Chris@483 3485 if (d->exec() == QDialog::Accepted) {
Chris@483 3486
Chris@483 3487 QString name = lineEdit->text();
Chris@483 3488 name.replace(QRegExp("[^\\w\\s\\.\"'-]"), "_");
Chris@483 3489
Chris@483 3490 ResourceFinder rf;
Chris@483 3491 QString dir = rf.getResourceSaveDir("templates");
Chris@483 3492 QString filename = QString("%1/%2.svt").arg(dir).arg(name);
Chris@483 3493 if (QFile(filename).exists()) {
Chris@483 3494 if (QMessageBox::warning(this,
Chris@483 3495 tr("Template file exists"),
Chris@483 3496 tr("<b>Template file exists</b><p>The template \"%1\" already exists.<br>Overwrite it?").arg(name),
Chris@483 3497 QMessageBox::Ok | QMessageBox::Cancel,
Chris@483 3498 QMessageBox::Cancel) != QMessageBox::Ok) {
Chris@1516 3499 delete d;
Chris@483 3500 return;
Chris@483 3501 }
Chris@483 3502 }
Chris@483 3503
Chris@483 3504 if (saveSessionTemplate(filename)) {
Chris@483 3505 if (makeDefault->isChecked()) {
Chris@483 3506 setDefaultSessionTemplate(name);
Chris@483 3507 }
Chris@431 3508 }
Chris@431 3509 }
Chris@1516 3510
Chris@1516 3511 delete d;
Chris@425 3512 }
Chris@425 3513
Chris@425 3514 void
Chris@425 3515 MainWindow::manageSavedTemplates()
Chris@425 3516 {
Chris@425 3517 ResourceFinder rf;
Chris@1930 3518 openLocalFolder(rf.getResourceSaveDir("templates"));
Chris@423 3519 }
Chris@423 3520
Chris@423 3521 void
Chris@200 3522 MainWindow::paneAdded(Pane *pane)
Chris@200 3523 {
Chris@200 3524 if (m_overview) m_overview->registerView(pane);
Chris@200 3525 }
Chris@200 3526
Chris@200 3527 void
Chris@200 3528 MainWindow::paneHidden(Pane *pane)
Chris@200 3529 {
Chris@200 3530 if (m_overview) m_overview->unregisterView(pane);
Chris@200 3531 }
Chris@200 3532
Chris@200 3533 void
Chris@200 3534 MainWindow::paneAboutToBeDeleted(Pane *pane)
Chris@200 3535 {
Chris@200 3536 if (m_overview) m_overview->unregisterView(pane);
Chris@200 3537 }
Chris@200 3538
Chris@200 3539 void
Chris@193 3540 MainWindow::paneDropAccepted(Pane *pane, QStringList uriList)
Chris@193 3541 {
Chris@193 3542 if (pane) m_paneStack->setCurrentPane(pane);
Chris@193 3543
Chris@193 3544 for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) {
Chris@193 3545
Chris@295 3546 FileOpenStatus status;
Chris@295 3547
Chris@295 3548 if (i == uriList.begin()) {
Chris@844 3549 status = openPath(*i, ReplaceCurrentPane);
Chris@295 3550 } else {
Chris@844 3551 status = openPath(*i, CreateAdditionalModel);
Chris@295 3552 }
Chris@193 3553
Chris@193 3554 if (status == FileOpenFailed) {
Chris@247 3555 emit hideSplash();
Chris@193 3556 QMessageBox::critical(this, tr("Failed to open dropped URL"),
Chris@193 3557 tr("<b>Open failed</b><p>Dropped URL \"%1\" could not be opened").arg(*i));
Chris@295 3558 break;
Chris@193 3559 } else if (status == FileOpenWrongMode) {
Chris@247 3560 emit hideSplash();
Chris@193 3561 QMessageBox::critical(this, tr("Failed to open dropped URL"),
Chris@294 3562 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 3563 break;
Chris@295 3564 } else if (status == FileOpenCancelled) {
Chris@295 3565 break;
Chris@193 3566 }
Chris@193 3567 }
Chris@193 3568 }
Chris@193 3569
Chris@193 3570 void
Chris@193 3571 MainWindow::paneDropAccepted(Pane *pane, QString text)
Chris@193 3572 {
Chris@193 3573 if (pane) m_paneStack->setCurrentPane(pane);
Chris@193 3574
Chris@193 3575 QUrl testUrl(text);
Chris@193 3576 if (testUrl.scheme() == "file" ||
Chris@193 3577 testUrl.scheme() == "http" ||
Chris@193 3578 testUrl.scheme() == "ftp") {
Chris@193 3579 QStringList list;
Chris@193 3580 list.push_back(text);
Chris@193 3581 paneDropAccepted(pane, list);
Chris@193 3582 return;
Chris@193 3583 }
Chris@193 3584
Chris@193 3585 //!!! open as text -- but by importing as if a CSV, or just adding
Chris@193 3586 //to a text layer?
Chris@193 3587 }
Chris@193 3588
Chris@193 3589 void
Chris@0 3590 MainWindow::closeEvent(QCloseEvent *e)
Chris@0 3591 {
Chris@2027 3592 SVDEBUG << "MainWindow::closeEvent" << endl;
Chris@118 3593
Chris@136 3594 if (m_openingAudioFile) {
Chris@2027 3595 SVCERR << "Busy - ignoring close event" << endl;
Chris@1770 3596 e->ignore();
Chris@1770 3597 return;
Chris@136 3598 }
Chris@136 3599
Chris@70 3600 if (!m_abandoning && !checkSaveModified()) {
Chris@2027 3601 SVCERR << "Close refused by user - ignoring close event" << endl;
Chris@1770 3602 e->ignore();
Chris@1770 3603 return;
Chris@0 3604 }
Chris@0 3605
Chris@5 3606 QSettings settings;
Chris@5 3607 settings.beginGroup("MainWindow");
Chris@624 3608 settings.setValue("maximised", isMaximized());
Chris@624 3609 if (!isMaximized()) {
Chris@624 3610 settings.setValue("size", size());
Chris@624 3611 settings.setValue("position", pos());
Chris@624 3612 }
Chris@5 3613 settings.endGroup();
Chris@5 3614
Chris@163 3615 if (m_preferencesDialog &&
Chris@163 3616 m_preferencesDialog->isVisible()) {
Chris@1434 3617 m_preferencesDialog->applicationClosing(true);
Chris@163 3618 }
Chris@163 3619
Chris@200 3620 closeSession();
Chris@200 3621
Chris@0 3622 e->accept();
Chris@489 3623
Chris@0 3624 return;
Chris@0 3625 }
Chris@0 3626
Chris@0 3627 bool
Chris@11 3628 MainWindow::commitData(bool mayAskUser)
Chris@11 3629 {
Chris@11 3630 if (mayAskUser) {
Chris@163 3631 bool rv = checkSaveModified();
Chris@163 3632 if (rv) {
Chris@163 3633 if (m_preferencesDialog &&
Chris@163 3634 m_preferencesDialog->isVisible()) {
Chris@163 3635 m_preferencesDialog->applicationClosing(false);
Chris@163 3636 }
Chris@163 3637 }
Chris@163 3638 return rv;
Chris@11 3639 } else {
Chris@163 3640 if (m_preferencesDialog &&
Chris@163 3641 m_preferencesDialog->isVisible()) {
Chris@163 3642 m_preferencesDialog->applicationClosing(true);
Chris@163 3643 }
Chris@11 3644 if (!m_documentModified) return true;
Chris@11 3645
Chris@11 3646 // If we can't check with the user first, then we can't save
Chris@11 3647 // to the original session file (even if we have it) -- have
Chris@11 3648 // to use a temporary file
Chris@11 3649
Chris@11 3650 QString svDirBase = ".sv1";
Chris@11 3651 QString svDir = QDir::home().filePath(svDirBase);
Chris@11 3652
Chris@11 3653 if (!QFileInfo(svDir).exists()) {
Chris@11 3654 if (!QDir::home().mkdir(svDirBase)) return false;
Chris@11 3655 } else {
Chris@11 3656 if (!QFileInfo(svDir).isDir()) return false;
Chris@11 3657 }
Chris@11 3658
Chris@11 3659 // This name doesn't have to be unguessable
Chris@93 3660 #ifndef _WIN32
Chris@11 3661 QString fname = QString("tmp-%1-%2.sv")
Chris@11 3662 .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz"))
Chris@11 3663 .arg(QProcess().pid());
Chris@93 3664 #else
Chris@93 3665 QString fname = QString("tmp-%1.sv")
Chris@93 3666 .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz"));
Chris@93 3667 #endif
Chris@11 3668 QString fpath = QDir(svDir).filePath(fname);
Chris@11 3669 if (saveSessionFile(fpath)) {
Chris@34 3670 m_recentFiles.addFile(fpath);
Chris@310 3671 emit activity(tr("Export image to \"%1\"").arg(fpath));
Chris@11 3672 return true;
Chris@11 3673 } else {
Chris@11 3674 return false;
Chris@11 3675 }
Chris@11 3676 }
Chris@11 3677 }
Chris@11 3678
Chris@11 3679 bool
Chris@0 3680 MainWindow::checkSaveModified()
Chris@0 3681 {
Chris@0 3682 // Called before some destructive operation (e.g. new session,
Chris@0 3683 // exit program). Return true if we can safely proceed, false to
Chris@0 3684 // cancel.
Chris@0 3685
Chris@0 3686 if (!m_documentModified) return true;
Chris@0 3687
Chris@247 3688 emit hideSplash();
Chris@247 3689
Chris@0 3690 int button =
Chris@1770 3691 QMessageBox::warning(this,
Chris@1770 3692 tr("Session modified"),
Chris@1770 3693 tr("<b>Session modified</b><p>The current session has been modified.<br>Do you want to save it?"),
Chris@1770 3694 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@165 3695 QMessageBox::Yes);
Chris@0 3696
Chris@0 3697 if (button == QMessageBox::Yes) {
Chris@1770 3698 saveSession();
Chris@1770 3699 if (m_documentModified) { // save failed -- don't proceed!
Chris@1770 3700 return false;
Chris@1770 3701 } else {
Chris@0 3702 return true; // saved, so it's safe to continue now
Chris@0 3703 }
Chris@0 3704 } else if (button == QMessageBox::No) {
Chris@1770 3705 m_documentModified = false; // so we know to abandon it
Chris@1770 3706 return true;
Chris@0 3707 }
Chris@0 3708
Chris@0 3709 // else cancel
Chris@0 3710 return false;
Chris@0 3711 }
Chris@0 3712
Chris@290 3713 bool
Chris@294 3714 MainWindow::shouldCreateNewSessionForRDFAudio(bool *cancel)
Chris@290 3715 {
Chris@294 3716 //!!! This is very similar to part of MainWindowBase::openAudio --
Chris@294 3717 //!!! make them a bit more uniform
Chris@294 3718
Chris@294 3719 QSettings settings;
Chris@294 3720 settings.beginGroup("MainWindow");
Chris@294 3721 bool prevNewSession = settings.value("newsessionforrdfaudio", true).toBool();
Chris@294 3722 settings.endGroup();
Chris@294 3723 bool newSession = true;
Chris@294 3724
Chris@294 3725 QStringList items;
Chris@294 3726 items << tr("Close the current session and create a new one")
Chris@294 3727 << tr("Add this data to the current session");
Chris@294 3728
Chris@294 3729 bool ok = false;
Chris@294 3730 QString item = ListInputDialog::getItem
Chris@294 3731 (this, tr("Select target for import"),
Chris@294 3732 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 3733 items, prevNewSession ? 0 : 1, &ok);
Chris@294 3734
Chris@294 3735 if (!ok || item.isEmpty()) {
Chris@294 3736 *cancel = true;
Chris@290 3737 return false;
Chris@290 3738 }
Chris@294 3739
Chris@294 3740 newSession = (item == items[0]);
Chris@294 3741 settings.beginGroup("MainWindow");
Chris@294 3742 settings.setValue("newsessionforrdfaudio", newSession);
Chris@294 3743 settings.endGroup();
Chris@294 3744
Chris@294 3745 if (newSession) return true;
Chris@294 3746 else return false;
Chris@290 3747 }
Chris@290 3748
Chris@0 3749 void
Chris@0 3750 MainWindow::saveSession()
Chris@0 3751 {
Chris@0 3752 if (m_sessionFile != "") {
Chris@1770 3753 if (!saveSessionFile(m_sessionFile)) {
Chris@1770 3754 QMessageBox::critical(this, tr("Failed to save file"),
Chris@1770 3755 tr("<b>Save failed</b><p>Session file \"%1\" could not be saved.").arg(m_sessionFile));
Chris@1770 3756 } else {
Chris@1770 3757 CommandHistory::getInstance()->documentSaved();
Chris@1770 3758 documentRestored();
Chris@1770 3759 }
Chris@0 3760 } else {
Chris@1770 3761 saveSessionAs();
Chris@0 3762 }
Chris@0 3763 }
Chris@0 3764
Chris@0 3765 void
Chris@0 3766 MainWindow::saveSessionAs()
Chris@0 3767 {
Chris@0 3768 QString orig = m_audioFile;
Chris@0 3769 if (orig == "") orig = ".";
Chris@0 3770 else orig = QFileInfo(orig).absoluteDir().canonicalPath();
Chris@0 3771
Chris@88 3772 QString path = getSaveFileName(FileFinder::SessionFile);
Chris@81 3773
Chris@81 3774 if (path == "") return;
Chris@0 3775
Chris@0 3776 if (!saveSessionFile(path)) {
Chris@1770 3777 QMessageBox::critical(this, tr("Failed to save file"),
Chris@1770 3778 tr("<b>Save failed</b><p>Session file \"%1\" could not be saved.").arg(path));
Chris@0 3779 } else {
Chris@1770 3780 setWindowTitle(tr("%1: %2")
Chris@518 3781 .arg(QApplication::applicationName())
Chris@1770 3782 .arg(QFileInfo(path).fileName()));
Chris@1770 3783 m_sessionFile = path;
Chris@1770 3784 CommandHistory::getInstance()->documentSaved();
Chris@1770 3785 documentRestored();
Chris@34 3786 m_recentFiles.addFile(path);
Chris@310 3787 emit activity(tr("Save session as \"%1\"").arg(path));
Chris@0 3788 }
Chris@0 3789 }
Chris@0 3790
Chris@90 3791 void
Chris@72 3792 MainWindow::preferenceChanged(PropertyContainer::PropertyName name)
Chris@72 3793 {
Chris@200 3794 MainWindowBase::preferenceChanged(name);
Chris@200 3795
Chris@1448 3796 if (name == "Background Mode") {
Chris@1448 3797 coloursChanged();
Chris@200 3798 }
Chris@0 3799 }
Chris@0 3800
Chris@0 3801 void
Chris@1448 3802 MainWindow::coloursChanged()
Chris@1448 3803 {
Chris@1448 3804 QSettings settings;
Chris@1448 3805 settings.beginGroup("Preferences");
Chris@1448 3806 QString defaultColourName(tr("Green"));
Chris@1448 3807 if (m_viewManager && m_viewManager->getGlobalDarkBackground()) {
Chris@1448 3808 defaultColourName = tr("Bright Green");
Chris@1448 3809 }
Chris@1448 3810 ColourDatabase *cdb = ColourDatabase::getInstance();
cannam@1463 3811 QColor colour = QColor
cannam@1463 3812 (settings.value("overview-colour",
cannam@1463 3813 cdb->getColour(defaultColourName).name()).toString());
Chris@1448 3814 settings.endGroup();
Chris@1448 3815
Chris@1448 3816 int index = cdb->getColourIndex(colour);
Chris@1448 3817 if (index >= 0) {
Chris@1448 3818 m_panLayer->setBaseColour(index);
Chris@1448 3819 }
Chris@1448 3820 }
Chris@1448 3821
Chris@1448 3822 void
Chris@239 3823 MainWindow::propertyStacksResized(int width)
Chris@239 3824 {
Chris@438 3825 // SVDEBUG << "MainWindow::propertyStacksResized(" << width << ")" << endl;
Chris@239 3826
Chris@239 3827 if (!m_playControlsSpacer) return;
Chris@239 3828
Chris@239 3829 int spacerWidth = width - m_playControlsWidth - 4;
Chris@239 3830
Chris@438 3831 // SVDEBUG << "resizing spacer from " << m_playControlsSpacer->width() << " to " << spacerWidth << endl;
Chris@239 3832
Chris@239 3833 m_playControlsSpacer->setFixedSize(QSize(spacerWidth, 2));
Chris@239 3834 }
Chris@239 3835
Chris@239 3836 void
Chris@0 3837 MainWindow::addPane()
Chris@0 3838 {
Chris@0 3839 QObject *s = sender();
Chris@0 3840 QAction *action = dynamic_cast<QAction *>(s);
Chris@753 3841
Chris@753 3842 cerr << "addPane: sender is " << s << ", action is " << action << ", name " << action->text() << endl;
Chris@0 3843
Chris@0 3844 if (!action) {
Chris@1770 3845 cerr << "WARNING: MainWindow::addPane: sender is not an action"
Chris@1770 3846 << endl;
Chris@1770 3847 return;
Chris@0 3848 }
Chris@0 3849
Chris@2093 3850 PaneActions::iterator i = m_paneActions.begin();
Chris@2093 3851 while (i != m_paneActions.end()) {
Chris@2093 3852 if (i->first == action) break;
Chris@2093 3853 ++i;
Chris@2093 3854 }
Chris@0 3855
Chris@0 3856 if (i == m_paneActions.end()) {
Chris@1770 3857 cerr << "WARNING: MainWindow::addPane: unknown action "
Chris@2093 3858 << action->objectName() << endl;
Chris@753 3859 cerr << "known actions are:" << endl;
Chris@2093 3860 for (PaneActions::const_iterator i = m_paneActions.begin();
Chris@753 3861 i != m_paneActions.end(); ++i) {
Chris@753 3862 cerr << i->first << ", name " << i->first->text() << endl;
Chris@753 3863 }
Chris@1770 3864 return;
Chris@0 3865 }
Chris@0 3866
Chris@69 3867 addPane(i->second, action->text());
Chris@69 3868 }
Chris@69 3869
Chris@69 3870 void
Chris@232 3871 MainWindow::addPane(const LayerConfiguration &configuration, QString text)
Chris@69 3872 {
Chris@69 3873 CommandHistory::getInstance()->startCompoundOperation(text, true);
Chris@0 3874
Chris@0 3875 AddPaneCommand *command = new AddPaneCommand(this);
Chris@0 3876 CommandHistory::getInstance()->addCommand(command);
Chris@0 3877
Chris@0 3878 Pane *pane = command->getPane();
Chris@0 3879
Chris@69 3880 if (configuration.layer == LayerFactory::Spectrum) {
Chris@109 3881 pane->setPlaybackFollow(PlaybackScrollContinuous);
Chris@110 3882 pane->setFollowGlobalZoom(false);
Chris@2011 3883 pane->setZoomLevel(ZoomLevel(ZoomLevel::FramesPerPixel, 512));
Chris@7 3884 }
Chris@7 3885
Chris@69 3886 if (configuration.layer != LayerFactory::TimeRuler &&
Chris@69 3887 configuration.layer != LayerFactory::Spectrum) {
Chris@8 3888
Chris@1770 3889 if (!m_timeRulerLayer) {
Chris@1770 3890 // cerr << "no time ruler layer, creating one" << endl;
Chris@1770 3891 m_timeRulerLayer = m_document->createMainModelLayer
Chris@1770 3892 (LayerFactory::TimeRuler);
Chris@1770 3893 }
Chris@1770 3894
Chris@1770 3895 // SVDEBUG << "adding time ruler layer " << m_timeRulerLayer << endl;
Chris@1770 3896
Chris@1770 3897 m_document->addLayerToView(pane, m_timeRulerLayer);
Chris@0 3898 }
Chris@0 3899
Chris@69 3900 Layer *newLayer = m_document->createLayer(configuration.layer);
Chris@69 3901
Chris@69 3902 Model *suggestedModel = configuration.sourceModel;
Chris@66 3903 Model *model = 0;
Chris@66 3904
Chris@66 3905 if (suggestedModel) {
Chris@66 3906
Chris@66 3907 // check its validity
Chris@224 3908 std::vector<Model *> inputModels = m_document->getTransformInputModels();
Chris@66 3909 for (size_t j = 0; j < inputModels.size(); ++j) {
Chris@66 3910 if (inputModels[j] == suggestedModel) {
Chris@66 3911 model = suggestedModel;
Chris@66 3912 break;
Chris@66 3913 }
Chris@66 3914 }
Chris@66 3915
Chris@66 3916 if (!model) {
Chris@665 3917 cerr << "WARNING: Model " << (void *)suggestedModel
Chris@66 3918 << " appears in pane action map, but is not reported "
Chris@665 3919 << "by document as a valid transform source" << endl;
Chris@66 3920 }
Chris@66 3921 }
Chris@66 3922
Chris@346 3923 if (!model) {
Chris@346 3924 model = m_document->getMainModel();
Chris@346 3925 }
Chris@66 3926
Chris@66 3927 m_document->setModel(newLayer, model);
Chris@66 3928
Chris@69 3929 m_document->setChannel(newLayer, configuration.channel);
Chris@0 3930 m_document->addLayerToView(pane, newLayer);
Chris@0 3931
Chris@0 3932 m_paneStack->setCurrentPane(pane);
Chris@70 3933 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@0 3934
Chris@438 3935 // SVDEBUG << "MainWindow::addPane: global centre frame is "
Chris@433 3936 // << m_viewManager->getGlobalCentreFrame() << endl;
Chris@130 3937 // pane->setCentreFrame(m_viewManager->getGlobalCentreFrame());
Chris@73 3938
Chris@0 3939 CommandHistory::getInstance()->endCompoundOperation();
Chris@0 3940
Chris@0 3941 updateMenuStates();
Chris@0 3942 }
Chris@0 3943
Chris@0 3944 void
Chris@0 3945 MainWindow::addLayer()
Chris@0 3946 {
Chris@0 3947 QObject *s = sender();
Chris@0 3948 QAction *action = dynamic_cast<QAction *>(s);
Chris@0 3949
Chris@0 3950 if (!action) {
Chris@1770 3951 cerr << "WARNING: MainWindow::addLayer: sender is not an action"
Chris@1770 3952 << endl;
Chris@1770 3953 return;
Chris@0 3954 }
Chris@0 3955
Chris@0 3956 Pane *pane = m_paneStack->getCurrentPane();
Chris@0 3957
Chris@0 3958 if (!pane) {
Chris@1770 3959 cerr << "WARNING: MainWindow::addLayer: no current pane" << endl;
Chris@1770 3960 return;
Chris@0 3961 }
Chris@0 3962
Chris@2093 3963 ExistingLayerActions::iterator ei = m_existingLayerActions.begin();
Chris@2093 3964 while (ei != m_existingLayerActions.end()) {
Chris@2093 3965 if (ei->first == action) break;
Chris@2093 3966 ++ei;
Chris@2093 3967 }
Chris@0 3968
Chris@0 3969 if (ei != m_existingLayerActions.end()) {
Chris@1770 3970 Layer *newLayer = ei->second;
Chris@1770 3971 m_document->addLayerToView(pane, newLayer);
Chris@1770 3972 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@1770 3973 return;
Chris@0 3974 }
Chris@0 3975
Chris@2093 3976 ei = m_sliceActions.begin();
Chris@2093 3977 while (ei != m_sliceActions.end()) {
Chris@2093 3978 if (ei->first == action) break;
Chris@2093 3979 ++ei;
Chris@2093 3980 }
Chris@95 3981
Chris@95 3982 if (ei != m_sliceActions.end()) {
Chris@95 3983 Layer *newLayer = m_document->createLayer(LayerFactory::Slice);
Chris@95 3984 // document->setModel(newLayer, ei->second->getModel());
Chris@95 3985 SliceableLayer *source = dynamic_cast<SliceableLayer *>(ei->second);
Chris@95 3986 SliceLayer *dest = dynamic_cast<SliceLayer *>(newLayer);
Chris@95 3987 if (source && dest) {
Chris@205 3988 //!!!???
Chris@95 3989 dest->setSliceableModel(source->getSliceableModel());
Chris@95 3990 connect(source, SIGNAL(sliceableModelReplaced(const Model *, const Model *)),
Chris@95 3991 dest, SLOT(sliceableModelReplaced(const Model *, const Model *)));
Chris@95 3992 connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)),
Chris@95 3993 dest, SLOT(modelAboutToBeDeleted(Model *)));
Chris@95 3994 }
Chris@1770 3995 m_document->addLayerToView(pane, newLayer);
Chris@1770 3996 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@1770 3997 return;
Chris@95 3998 }
Chris@95 3999
Chris@2093 4000 TransformActions::iterator i = m_transformActions.begin();
Chris@2093 4001 while (i != m_transformActions.end()) {
Chris@2093 4002 if (i->first == action) break;
Chris@2093 4003 ++i;
Chris@2093 4004 }
Chris@34 4005
Chris@34 4006 if (i == m_transformActions.end()) {
Chris@0 4007
Chris@2093 4008 LayerActions::iterator i = m_layerActions.begin();
Chris@2093 4009 while (i != m_layerActions.end()) {
Chris@2093 4010 if (i->first == action) break;
Chris@2093 4011 ++i;
Chris@2093 4012 }
Chris@1770 4013
Chris@1770 4014 if (i == m_layerActions.end()) {
Chris@1770 4015 cerr << "WARNING: MainWindow::addLayer: unknown action "
Chris@1770 4016 << action->objectName() << endl;
Chris@1770 4017 return;
Chris@1770 4018 }
Chris@1770 4019
Chris@1770 4020 LayerFactory::LayerType type = i->second.layer;
Chris@1770 4021
Chris@1770 4022 LayerFactory::LayerTypeSet emptyTypes =
Chris@1770 4023 LayerFactory::getInstance()->getValidEmptyLayerTypes();
Chris@1770 4024
Chris@1770 4025 Layer *newLayer = 0;
Chris@1770 4026
Chris@1770 4027 if (emptyTypes.find(type) != emptyTypes.end()) {
Chris@1770 4028
Chris@1770 4029 newLayer = m_document->createEmptyLayer(type);
Chris@217 4030 if (newLayer) {
Chris@2093 4031 for (auto &a : m_toolActions) {
Chris@2093 4032 if (a.first == ViewManager::DrawMode) {
Chris@2093 4033 a.second->trigger();
Chris@2093 4034 break;
Chris@2093 4035 }
Chris@2093 4036 }
Chris@217 4037 }
Chris@0 4038
Chris@1770 4039 } else {
Chris@0 4040
Chris@346 4041 Model *model = i->second.sourceModel;
Chris@346 4042
Chris@415 4043 cerr << "model = "<< model << endl;
Chris@415 4044
Chris@346 4045 if (!model) {
Chris@346 4046 if (type == LayerFactory::TimeRuler) {
Chris@346 4047 newLayer = m_document->createMainModelLayer(type);
Chris@346 4048 } else {
Chris@346 4049 // if model is unspecified and this is not a
Chris@415 4050 // time-ruler layer, use any plausible model from
Chris@415 4051 // the current pane -- this is the case for
Chris@415 4052 // right-button menu layer additions
Chris@415 4053 Pane::ModelSet ms = pane->getModels();
Chris@415 4054 foreach (Model *m, ms) {
Chris@415 4055 RangeSummarisableTimeValueModel *r =
Chris@415 4056 dynamic_cast<RangeSummarisableTimeValueModel *>(m);
Chris@415 4057 if (r) model = m;
Chris@346 4058 }
Chris@346 4059 if (!model) model = getMainModel();
Chris@346 4060 }
Chris@346 4061 }
Chris@346 4062
Chris@346 4063 if (model) {
Chris@238 4064 newLayer = m_document->createLayer(type);
Chris@346 4065 if (m_document->isKnownModel(model)) {
Chris@238 4066 m_document->setChannel(newLayer, i->second.channel);
Chris@346 4067 m_document->setModel(newLayer, model);
Chris@238 4068 } else {
Chris@665 4069 cerr << "WARNING: MainWindow::addLayer: unknown model "
Chris@346 4070 << model
Chris@238 4071 << " (\""
Chris@838 4072 << model->objectName()
Chris@238 4073 << "\") in layer action map"
Chris@665 4074 << endl;
Chris@238 4075 }
Chris@232 4076 }
Chris@238 4077 }
Chris@0 4078
Chris@217 4079 if (newLayer) {
Chris@217 4080 m_document->addLayerToView(pane, newLayer);
Chris@217 4081 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@217 4082 }
Chris@0 4083
Chris@1770 4084 return;
Chris@0 4085 }
Chris@0 4086
Chris@224 4087 //!!! want to do something like this, but it's not supported in
Chris@224 4088 //ModelTransformerFactory yet
Chris@224 4089 /*
Chris@0 4090 int channel = -1;
Chris@0 4091 // pick up the default channel from any existing layers on the same pane
Chris@0 4092 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@1770 4093 int c = LayerFactory::getInstance()->getChannel(pane->getLayer(j));
Chris@1770 4094 if (c != -1) {
Chris@1770 4095 channel = c;
Chris@1770 4096 break;
Chris@1770 4097 }
Chris@0 4098 }
Chris@224 4099 */
Chris@0 4100
Chris@33 4101 // We always ask for configuration, even if the plugin isn't
Chris@33 4102 // supposed to be configurable, because we need to let the user
Chris@33 4103 // change the execution context (block size etc).
Chris@0 4104
Chris@224 4105 QString transformId = i->second;
Chris@274 4106
Chris@274 4107 addLayer(transformId);
Chris@274 4108 }
Chris@274 4109
Chris@274 4110 void
Chris@274 4111 MainWindow::addLayer(QString transformId)
Chris@274 4112 {
Chris@274 4113 Pane *pane = m_paneStack->getCurrentPane();
Chris@274 4114 if (!pane) {
Chris@1770 4115 cerr << "WARNING: MainWindow::addLayer: no current pane" << endl;
Chris@1770 4116 return;
Chris@274 4117 }
Chris@274 4118
Chris@1558 4119 Transform transform;
Chris@1558 4120 try {
Chris@1558 4121 transform = TransformFactory::getInstance()->
Chris@1558 4122 getDefaultTransformFor(transformId);
Chris@1558 4123 } catch (std::exception &e) { // e.g. Piper server failure
Chris@1558 4124 QMessageBox::critical
Chris@1558 4125 (this, tr("Failed to query transform attributes"),
Chris@1558 4126 tr("<b>Failed to query transform attributes</b><p>Plugin or server error: %1</p>")
Chris@1558 4127 .arg(e.what()));
Chris@1558 4128 return;
Chris@1558 4129 }
Chris@27 4130
Chris@66 4131 std::vector<Model *> candidateInputModels =
Chris@224 4132 m_document->getTransformInputModels();
Chris@53 4133
Chris@219 4134 Model *defaultInputModel = 0;
Chris@895 4135
Chris@219 4136 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@895 4137
Chris@219 4138 Layer *layer = pane->getLayer(j);
Chris@219 4139 if (!layer) continue;
Chris@895 4140
Chris@243 4141 if (LayerFactory::getInstance()->getLayerType(layer) !=
Chris@243 4142 LayerFactory::Waveform &&
Chris@243 4143 !layer->isLayerOpaque()) continue;
Chris@895 4144
Chris@219 4145 Model *model = layer->getModel();
Chris@219 4146 if (!model) continue;
Chris@895 4147
Chris@219 4148 for (size_t k = 0; k < candidateInputModels.size(); ++k) {
Chris@219 4149 if (candidateInputModels[k] == model) {
Chris@219 4150 defaultInputModel = model;
Chris@219 4151 break;
Chris@219 4152 }
Chris@219 4153 }
Chris@895 4154
Chris@219 4155 if (defaultInputModel) break;
Chris@219 4156 }
Chris@895 4157
Chris@1620 4158 AggregateWaveModel *aggregate = 0;
Chris@1620 4159
Chris@895 4160 if (candidateInputModels.size() > 1) {
Chris@895 4161 // Add an aggregate model as another option
Chris@895 4162 AggregateWaveModel::ChannelSpecList sl;
Chris@895 4163 foreach (Model *m, candidateInputModels) {
Chris@895 4164 RangeSummarisableTimeValueModel *r =
Chris@895 4165 qobject_cast<RangeSummarisableTimeValueModel *>(m);
Chris@895 4166 if (r) {
Chris@895 4167 sl.push_back(AggregateWaveModel::ModelChannelSpec(r, -1));
Chris@895 4168 }
Chris@895 4169 }
Chris@895 4170 if (!sl.empty()) {
Chris@1620 4171 aggregate = new AggregateWaveModel(sl);
Chris@895 4172 aggregate->setObjectName(tr("Multiplex all of the above"));
Chris@895 4173 candidateInputModels.push_back(aggregate);
Chris@895 4174 }
Chris@895 4175 }
Chris@219 4176
Chris@914 4177 sv_frame_t startFrame = 0, duration = 0;
Chris@914 4178 sv_frame_t endFrame = 0;
Chris@184 4179 m_viewManager->getSelection().getExtents(startFrame, endFrame);
Chris@184 4180 if (endFrame > startFrame) duration = endFrame - startFrame;
Chris@184 4181 else startFrame = 0;
Chris@184 4182
Chris@357 4183 TransformUserConfigurator configurator;
Chris@357 4184
Chris@224 4185 ModelTransformer::Input input = ModelTransformerFactory::getInstance()->
Chris@224 4186 getConfigurationForTransform
Chris@211 4187 (transform,
Chris@211 4188 candidateInputModels,
Chris@219 4189 defaultInputModel,
Chris@211 4190 m_playSource,
Chris@211 4191 startFrame,
Chris@357 4192 duration,
Chris@357 4193 &configurator);
Chris@211 4194
Chris@1623 4195 if (aggregate) {
Chris@1623 4196 if (input.getModel() == aggregate) {
Chris@1623 4197 aggregate->setObjectName(tr("Multiplexed audio"));
Chris@1623 4198 m_document->addAggregateModel(aggregate);
Chris@1623 4199 } else {
Chris@1623 4200 aggregate->aboutToDelete();
Chris@1623 4201 delete aggregate;
Chris@1623 4202 }
Chris@1620 4203 }
Chris@1620 4204
Chris@224 4205 if (!input.getModel()) return;
Chris@224 4206
Chris@438 4207 // SVDEBUG << "MainWindow::addLayer: Input model is " << input.getModel() << " \"" << input.getModel()->objectName() << "\"" << endl << "transform:" << endl << transform.toXmlString() << endl;
Chris@224 4208
Chris@1558 4209 try {
Chris@1558 4210 Layer *newLayer = m_document->createDerivedLayer(transform, input);
Chris@1558 4211 if (newLayer) {
Chris@1558 4212 m_document->addLayerToView(pane, newLayer);
Chris@1558 4213 m_document->setChannel(newLayer, input.getChannel());
Chris@1558 4214 m_recentTransforms.add(transformId);
Chris@1558 4215 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@1558 4216 }
Chris@1558 4217 } catch (std::exception &e) { // e.g. Piper server failure
Chris@1558 4218 QMessageBox::critical
Chris@1558 4219 (this, tr("Transform failed"),
Chris@1558 4220 tr("<b>Failed to run transform</b><p>Plugin or server error: %1</p>")
Chris@1558 4221 .arg(e.what()));
Chris@1558 4222 return;
Chris@0 4223 }
Chris@1558 4224
Chris@0 4225 updateMenuStates();
Chris@0 4226 }
Chris@0 4227
Chris@0 4228 void
Chris@0 4229 MainWindow::renameCurrentLayer()
Chris@0 4230 {
Chris@0 4231 Pane *pane = m_paneStack->getCurrentPane();
Chris@0 4232 if (pane) {
Chris@1770 4233 Layer *layer = pane->getSelectedLayer();
Chris@1770 4234 if (layer) {
Chris@1770 4235 bool ok = false;
Chris@1770 4236 QString newName = QInputDialog::getText
Chris@1770 4237 (this, tr("Rename Layer"),
Chris@1770 4238 tr("New name for this layer:"),
Chris@1770 4239 QLineEdit::Normal, layer->objectName(), &ok);
Chris@1770 4240 if (ok) {
Chris@1770 4241 layer->setPresentationName(newName);
Chris@1770 4242 setupExistingLayersMenus();
Chris@1770 4243 }
Chris@1770 4244 }
Chris@0 4245 }
Chris@0 4246 }
Chris@0 4247
Chris@0 4248 void
Chris@272 4249 MainWindow::findTransform()
Chris@272 4250 {
Chris@274 4251 TransformFinder *finder = new TransformFinder(this);
Chris@274 4252 if (!finder->exec()) {
Chris@274 4253 delete finder;
Chris@274 4254 return;
Chris@273 4255 }
Chris@274 4256 TransformId transform = finder->getTransform();
Chris@274 4257 delete finder;
Chris@287 4258
Chris@287 4259 if (getMainModel() != 0 && m_paneStack->getCurrentPane() != 0) {
Chris@287 4260 addLayer(transform);
Chris@287 4261 }
Chris@272 4262 }
Chris@272 4263
Chris@272 4264 void
Chris@207 4265 MainWindow::playSoloToggled()
Chris@207 4266 {
Chris@207 4267 MainWindowBase::playSoloToggled();
Chris@207 4268 m_soloModified = true;
Chris@207 4269 }
Chris@207 4270
Chris@207 4271 void
Chris@206 4272 MainWindow::alignToggled()
Chris@206 4273 {
Chris@206 4274 QAction *action = dynamic_cast<QAction *>(sender());
Chris@206 4275
Chris@207 4276 if (!m_viewManager) return;
Chris@207 4277
Chris@206 4278 if (action) {
Chris@1770 4279 m_viewManager->setAlignMode(action->isChecked());
Chris@206 4280 } else {
Chris@1770 4281 m_viewManager->setAlignMode(!m_viewManager->getAlignMode());
Chris@206 4282 }
Chris@206 4283
Chris@206 4284 if (m_viewManager->getAlignMode()) {
Chris@207 4285 m_prevSolo = m_soloAction->isChecked();
Chris@208 4286 if (!m_soloAction->isChecked()) {
Chris@208 4287 m_soloAction->setChecked(true);
Chris@208 4288 MainWindowBase::playSoloToggled();
Chris@208 4289 }
Chris@207 4290 m_soloModified = false;
Chris@207 4291 emit canChangeSolo(false);
Chris@206 4292 m_document->alignModels();
Chris@206 4293 m_document->setAutoAlignment(true);
Chris@206 4294 } else {
Chris@207 4295 if (!m_soloModified) {
Chris@208 4296 if (m_soloAction->isChecked() != m_prevSolo) {
Chris@208 4297 m_soloAction->setChecked(m_prevSolo);
Chris@208 4298 MainWindowBase::playSoloToggled();
Chris@208 4299 }
Chris@207 4300 }
Chris@207 4301 emit canChangeSolo(true);
Chris@206 4302 m_document->setAutoAlignment(false);
Chris@206 4303 }
Chris@206 4304
Chris@206 4305 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@206 4306
Chris@1770 4307 Pane *pane = m_paneStack->getPane(i);
Chris@1770 4308 if (!pane) continue;
Chris@206 4309
Chris@206 4310 pane->update();
Chris@206 4311 }
Chris@206 4312 }
Chris@206 4313
Chris@206 4314 void
Chris@59 4315 MainWindow::playSpeedChanged(int position)
Chris@0 4316 {
Chris@1031 4317 PlaySpeedRangeMapper mapper;
Chris@60 4318
Chris@922 4319 double percent = m_playSpeed->mappedValue();
Chris@922 4320 double factor = mapper.getFactorForValue(percent);
Chris@60 4321
Chris@1031 4322 // cerr << "play speed position = " << position << " (range 0-120) percent = " << percent << " factor = " << factor << endl;
Chris@1031 4323
Chris@1031 4324 int centre = m_playSpeed->defaultValue();
Chris@1031 4325
Chris@1031 4326 // Percentage is shown to 0dp if >100, to 1dp if <100; factor is
Chris@1031 4327 // shown to 3sf
Chris@1031 4328
Chris@1031 4329 char pcbuf[30];
Chris@1031 4330 char facbuf[30];
Chris@1031 4331
Chris@1031 4332 if (position == centre) {
Chris@155 4333 contextHelpChanged(tr("Playback speed: Normal"));
Chris@1031 4334 } else if (position < centre) {
Chris@1031 4335 sprintf(pcbuf, "%.1f", percent);
Chris@1031 4336 sprintf(facbuf, "%.3g", 1.0 / factor);
Chris@1031 4337 contextHelpChanged(tr("Playback speed: %1% (%2x slower)")
Chris@1031 4338 .arg(pcbuf)
Chris@1031 4339 .arg(facbuf));
Chris@155 4340 } else {
Chris@1031 4341 sprintf(pcbuf, "%.0f", percent);
Chris@1031 4342 sprintf(facbuf, "%.3g", factor);
Chris@1031 4343 contextHelpChanged(tr("Playback speed: %1% (%2x faster)")
Chris@1031 4344 .arg(pcbuf)
Chris@1031 4345 .arg(facbuf));
Chris@155 4346 }
Chris@155 4347
Chris@1031 4348 m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup
Chris@155 4349
Chris@155 4350 updateMenuStates();
Chris@16 4351 }
Chris@16 4352
Chris@26 4353 void
Chris@155 4354 MainWindow::speedUpPlayback()
Chris@155 4355 {
Chris@155 4356 int value = m_playSpeed->value();
Chris@155 4357 value = value + m_playSpeed->pageStep();
Chris@155 4358 if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum();
Chris@155 4359 m_playSpeed->setValue(value);
Chris@155 4360 }
Chris@155 4361
Chris@155 4362 void
Chris@155 4363 MainWindow::slowDownPlayback()
Chris@155 4364 {
Chris@155 4365 int value = m_playSpeed->value();
Chris@155 4366 value = value - m_playSpeed->pageStep();
Chris@155 4367 if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum();
Chris@155 4368 m_playSpeed->setValue(value);
Chris@155 4369 }
Chris@155 4370
Chris@155 4371 void
Chris@155 4372 MainWindow::restoreNormalPlayback()
Chris@155 4373 {
Chris@155 4374 m_playSpeed->setValue(m_playSpeed->defaultValue());
Chris@155 4375 }
Chris@155 4376
Chris@155 4377 void
Chris@227 4378 MainWindow::currentPaneChanged(Pane *pane)
Chris@227 4379 {
Chris@228 4380 MainWindowBase::currentPaneChanged(pane);
Chris@228 4381
Chris@227 4382 if (!pane || !m_panLayer) return;
Chris@761 4383
Chris@761 4384 // If this pane contains the main model, it usually makes sense to
Chris@761 4385 // show the main model in the pan layer even if it isn't the top
Chris@761 4386 // layer in the pane (e.g. if the top layer is one derived from
Chris@761 4387 // the main model).
Chris@761 4388 bool containsMainModel = false;
Chris@761 4389 for (int i = pane->getLayerCount(); i > 0; ) {
Chris@761 4390 --i;
Chris@761 4391 Layer *layer = pane->getLayer(i);
Chris@761 4392 if (layer &&
Chris@761 4393 LayerFactory::getInstance()->getLayerType(layer) ==
Chris@761 4394 LayerFactory::Waveform &&
Chris@761 4395 layer->getModel() == getMainModel()) {
Chris@761 4396 containsMainModel = true;
Chris@761 4397 break;
Chris@761 4398 }
Chris@761 4399 }
Chris@761 4400
Chris@1794 4401 bool panLayerSet = false;
Chris@1794 4402
Chris@227 4403 for (int i = pane->getLayerCount(); i > 0; ) {
Chris@227 4404 --i;
Chris@227 4405 Layer *layer = pane->getLayer(i);
Chris@1794 4406 RangeSummarisableTimeValueModel *tvm =
Chris@1794 4407 qobject_cast<RangeSummarisableTimeValueModel *>(layer->getModel());
Chris@1794 4408 if (tvm) {
Chris@1794 4409 auto type = LayerFactory::getInstance()->getLayerType(layer);
Chris@1794 4410 if (type != LayerFactory::TimeRuler) {
Chris@1794 4411 updateLayerShortcutsFor(tvm);
Chris@1794 4412 }
Chris@1794 4413 if (type == LayerFactory::Waveform) {
Chris@227 4414 m_panLayer->setModel(tvm);
Chris@1794 4415 panLayerSet = true;
Chris@1794 4416 break;
Chris@227 4417 }
Chris@227 4418 }
Chris@227 4419 }
Chris@1794 4420
Chris@1794 4421 if (containsMainModel && !panLayerSet) {
Chris@1794 4422 m_panLayer->setModel(getMainModel());
Chris@1794 4423 }
Chris@227 4424 }
Chris@227 4425
Chris@227 4426 void
Chris@116 4427 MainWindow::updateVisibleRangeDisplay(Pane *p) const
Chris@116 4428 {
Chris@116 4429 if (!getMainModel() || !p) {
Chris@116 4430 return;
Chris@116 4431 }
Chris@116 4432
Chris@117 4433 bool haveSelection = false;
Chris@922 4434 sv_frame_t startFrame = 0, endFrame = 0;
Chris@117 4435
Chris@117 4436 if (m_viewManager && m_viewManager->haveInProgressSelection()) {
Chris@117 4437
Chris@117 4438 bool exclusive = false;
Chris@117 4439 Selection s = m_viewManager->getInProgressSelection(exclusive);
Chris@117 4440
Chris@117 4441 if (!s.isEmpty()) {
Chris@117 4442 haveSelection = true;
Chris@117 4443 startFrame = s.getStartFrame();
Chris@117 4444 endFrame = s.getEndFrame();
Chris@117 4445 }
Chris@117 4446 }
Chris@117 4447
Chris@117 4448 if (!haveSelection) {
Chris@117 4449 startFrame = p->getFirstVisibleFrame();
Chris@117 4450 endFrame = p->getLastVisibleFrame();
Chris@117 4451 }
Chris@117 4452
Chris@116 4453 RealTime start = RealTime::frame2RealTime
Chris@117 4454 (startFrame, getMainModel()->getSampleRate());
Chris@116 4455
Chris@116 4456 RealTime end = RealTime::frame2RealTime
Chris@117 4457 (endFrame, getMainModel()->getSampleRate());
Chris@116 4458
Chris@116 4459 RealTime duration = end - start;
Chris@116 4460
Chris@116 4461 QString startStr, endStr, durationStr;
Chris@116 4462 startStr = start.toText(true).c_str();
Chris@116 4463 endStr = end.toText(true).c_str();
Chris@116 4464 durationStr = duration.toText(true).c_str();
Chris@116 4465
Chris@117 4466 if (haveSelection) {
Chris@117 4467 m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)")
Chris@117 4468 .arg(startStr).arg(endStr).arg(durationStr);
Chris@117 4469 } else {
Chris@117 4470 m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)")
Chris@117 4471 .arg(startStr).arg(endStr).arg(durationStr);
Chris@117 4472 }
Chris@116 4473
Chris@739 4474 if (getStatusLabel()->text() != m_myStatusMessage) {
Chris@739 4475 getStatusLabel()->setText(m_myStatusMessage);
Chris@737 4476 }
Chris@340 4477
Chris@340 4478 updatePositionStatusDisplays();
Chris@340 4479 }
Chris@340 4480
Chris@340 4481 void
Chris@340 4482 MainWindow::updatePositionStatusDisplays() const
Chris@340 4483 {
Chris@340 4484 if (!statusBar()->isVisible()) return;
Chris@340 4485
Chris@340 4486 Pane *pane = 0;
Chris@922 4487 sv_frame_t frame = m_viewManager->getPlaybackFrame();
Chris@340 4488
Chris@340 4489 if (m_paneStack) pane = m_paneStack->getCurrentPane();
Chris@340 4490 if (!pane) return;
Chris@340 4491
Chris@340 4492 int layers = pane->getLayerCount();
Chris@340 4493 if (layers == 0) m_currentLabel->setText("");
Chris@340 4494
Chris@340 4495 for (int i = layers-1; i >= 0; --i) {
Chris@340 4496 Layer *layer = pane->getLayer(i);
Chris@340 4497 if (!layer) continue;
Chris@340 4498 if (!layer->isLayerEditable()) continue;
Chris@340 4499 QString label = layer->getLabelPreceding
Chris@340 4500 (pane->alignFromReference(frame));
Chris@340 4501 m_currentLabel->setText(label);
Chris@340 4502 break;
Chris@340 4503 }
Chris@116 4504 }
Chris@116 4505
Chris@116 4506 void
Chris@1476 4507 MainWindow::monitoringLevelsChanged(float left, float right)
Chris@0 4508 {
Chris@1386 4509 m_mainLevelPan->setMonitoringLevels(left, right);
Chris@0 4510 }
Chris@0 4511
Chris@0 4512 void
Chris@921 4513 MainWindow::sampleRateMismatch(sv_samplerate_t requested,
Chris@921 4514 sv_samplerate_t actual,
Chris@0 4515 bool willResample)
Chris@0 4516 {
Chris@0 4517 if (!willResample) {
Chris@247 4518 emit hideSplash();
Chris@0 4519 QMessageBox::information
Chris@0 4520 (this, tr("Sample rate mismatch"),
Chris@193 4521 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 4522 .arg(requested).arg(actual));
Chris@0 4523 }
Chris@0 4524
Chris@0 4525 updateDescriptionLabel();
Chris@0 4526 }
Chris@0 4527
Chris@0 4528 void
Chris@42 4529 MainWindow::audioOverloadPluginDisabled()
Chris@42 4530 {
Chris@42 4531 QMessageBox::information
Chris@42 4532 (this, tr("Audio processing overload"),
Chris@193 4533 tr("<b>Overloaded</b><p>Audio effects plugin auditioning has been disabled due to a processing overload."));
Chris@42 4534 }
Chris@42 4535
Chris@42 4536 void
Chris@266 4537 MainWindow::audioTimeStretchMultiChannelDisabled()
Chris@266 4538 {
Chris@266 4539 static bool shownOnce = false;
Chris@266 4540 if (shownOnce) return;
Chris@266 4541 QMessageBox::information
Chris@266 4542 (this, tr("Audio processing overload"),
Chris@266 4543 tr("<b>Overloaded</b><p>Audio playback speed processing has been reduced to a single channel, due to a processing overload."));
Chris@266 4544 shownOnce = true;
Chris@266 4545 }
Chris@266 4546
Chris@1630 4547 /*
Chris@266 4548 void
Chris@1521 4549 MainWindow::betaReleaseWarning()
Chris@1521 4550 {
Chris@1521 4551 QMessageBox::information
Chris@1521 4552 (this, tr("Beta release"),
Chris@1521 4553 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 4554 }
Chris@1630 4555 */
Chris@1521 4556
Chris@1521 4557 void
Chris@1148 4558 MainWindow::pluginPopulationWarning()
Chris@1087 4559 {
Chris@1277 4560 QString scanWarning = PluginScan::getInstance()->getStartupFailureReport();
Chris@1277 4561 QString factWarning = TransformFactory::getInstance()->getStartupFailureReport();
Chris@1277 4562 QString warning;
Chris@1277 4563 if (factWarning != "") {
Chris@1277 4564 // The order of events on startup implies that, if scanWarning
Chris@1277 4565 // and factWarning are both present, then we have already been
Chris@1277 4566 // called once for scanWarning so don't want to report it again
Chris@1277 4567 warning = factWarning;
Chris@1277 4568 } else if (scanWarning != "") {
Chris@1277 4569 warning = scanWarning;
Chris@1277 4570 }
Chris@1277 4571 if (warning != "") {
Chris@1277 4572 emit hideSplash();
Chris@2067 4573 QMessageBox box;
Chris@2067 4574 box.setWindowTitle(tr("Problems loading plugins"));
Chris@2067 4575 box.setText(tr("<b>Failed to load plugins</b>"));
Chris@2067 4576 box.setInformativeText(warning);
Chris@2067 4577 box.setIcon(QMessageBox::Warning);
Chris@2067 4578 box.setStandardButtons(QMessageBox::Ok);
Chris@2067 4579 box.exec();
Chris@1277 4580 }
Chris@1087 4581 }
Chris@1087 4582
Chris@1087 4583 void
Chris@304 4584 MainWindow::midiEventsAvailable()
Chris@304 4585 {
Chris@307 4586 Pane *currentPane = 0;
Chris@307 4587 NoteLayer *currentNoteLayer = 0;
Chris@309 4588 TimeValueLayer *currentTimeValueLayer = 0;
Chris@307 4589
Chris@793 4590 if (m_paneStack) {
Chris@793 4591 currentPane = m_paneStack->getCurrentPane();
Chris@793 4592 }
Chris@793 4593
Chris@307 4594 if (currentPane) {
Chris@307 4595 currentNoteLayer = dynamic_cast<NoteLayer *>
Chris@307 4596 (currentPane->getSelectedLayer());
Chris@309 4597 currentTimeValueLayer = dynamic_cast<TimeValueLayer *>
Chris@309 4598 (currentPane->getSelectedLayer());
Chris@838 4599 } else {
Chris@793 4600 // discard these events
Chris@793 4601 while (m_midiInput->getEventsAvailable() > 0) {
Chris@793 4602 (void)m_midiInput->readEvent();
Chris@793 4603 }
Chris@793 4604 return;
Chris@793 4605 }
Chris@793 4606
Chris@305 4607 // This is called through a serialised signal/slot invocation
Chris@305 4608 // (across threads). It could happen quite some time after the
Chris@305 4609 // event was actually received, which is why event timestamping
Chris@305 4610 // happens in the MIDI input class and not here.
Chris@307 4611
Chris@305 4612 while (m_midiInput->getEventsAvailable() > 0) {
Chris@308 4613
Chris@305 4614 MIDIEvent ev(m_midiInput->readEvent());
Chris@307 4615
Chris@922 4616 sv_frame_t frame = currentPane->alignFromReference(ev.getTime());
Chris@309 4617
Chris@308 4618 bool noteOn = (ev.getMessageType() == MIDIConstants::MIDI_NOTE_ON &&
Chris@308 4619 ev.getVelocity() > 0);
Chris@308 4620
Chris@308 4621 bool noteOff = (ev.getMessageType() == MIDIConstants::MIDI_NOTE_OFF ||
Chris@308 4622 (ev.getMessageType() == MIDIConstants::MIDI_NOTE_ON &&
Chris@308 4623 ev.getVelocity() == 0));
Chris@308 4624
Chris@307 4625 if (currentNoteLayer) {
Chris@307 4626
Chris@310 4627 if (!m_playSource || !m_playSource->isPlaying()) continue;
Chris@310 4628
Chris@308 4629 if (noteOn) {
Chris@307 4630
Chris@309 4631 currentNoteLayer->addNoteOn(frame,
Chris@307 4632 ev.getPitch(),
Chris@307 4633 ev.getVelocity());
Chris@307 4634
Chris@308 4635 } else if (noteOff) {
Chris@307 4636
Chris@309 4637 currentNoteLayer->addNoteOff(frame,
Chris@307 4638 ev.getPitch());
Chris@307 4639
Chris@307 4640 }
Chris@307 4641
Chris@309 4642 continue;
Chris@309 4643 }
Chris@309 4644
Chris@309 4645 if (currentTimeValueLayer) {
Chris@308 4646
Chris@308 4647 if (!noteOn) continue;
Chris@310 4648
Chris@310 4649 if (!m_playSource || !m_playSource->isPlaying()) continue;
Chris@310 4650
Chris@309 4651 Model *model = static_cast<Layer *>(currentTimeValueLayer)->getModel();
Chris@309 4652 SparseTimeValueModel *tvm =
Chris@309 4653 dynamic_cast<SparseTimeValueModel *>(model);
Chris@309 4654 if (tvm) {
Chris@1754 4655 SparseTimeValueModel::Point point(frame,
Chris@1754 4656 float(ev.getPitch() % 12),
Chris@1754 4657 "");
Chris@309 4658 SparseTimeValueModel::AddPointCommand *command =
Chris@309 4659 new SparseTimeValueModel::AddPointCommand
Chris@309 4660 (tvm, point, tr("Add Point"));
Chris@309 4661 CommandHistory::getInstance()->addCommand(command);
Chris@309 4662 }
Chris@838 4663
Chris@309 4664 continue;
Chris@305 4665 }
Chris@309 4666
Chris@838 4667 // This is reached only if !currentNoteLayer and
Chris@838 4668 // !currentTimeValueLayer, i.e. there is some other sort of
Chris@838 4669 // layer that may be insertable-into
Chris@838 4670
Chris@309 4671 if (!noteOn) continue;
Chris@309 4672 insertInstantAt(ev.getTime());
Chris@304 4673 }
Chris@304 4674 }
Chris@304 4675
Chris@304 4676 void
Chris@730 4677 MainWindow::playStatusChanged(bool )
Chris@305 4678 {
Chris@307 4679 Pane *currentPane = 0;
Chris@307 4680 NoteLayer *currentNoteLayer = 0;
Chris@307 4681
Chris@307 4682 if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
Chris@307 4683 if (currentPane) {
Chris@307 4684 currentNoteLayer = dynamic_cast<NoteLayer *>(currentPane->getSelectedLayer());
Chris@307 4685 }
Chris@307 4686
Chris@307 4687 if (currentNoteLayer) {
Chris@307 4688 currentNoteLayer->abandonNoteOns();
Chris@307 4689 }
Chris@305 4690 }
Chris@305 4691
Chris@305 4692 void
Chris@200 4693 MainWindow::layerRemoved(Layer *layer)
Chris@0 4694 {
Chris@95 4695 setupExistingLayersMenus();
Chris@200 4696 MainWindowBase::layerRemoved(layer);
Chris@0 4697 }
Chris@0 4698
Chris@0 4699 void
Chris@0 4700 MainWindow::layerInAView(Layer *layer, bool inAView)
Chris@0 4701 {
Chris@95 4702 setupExistingLayersMenus();
Chris@200 4703 MainWindowBase::layerInAView(layer, inAView);
Chris@0 4704 }
Chris@0 4705
Chris@0 4706 void
Chris@0 4707 MainWindow::modelAdded(Model *model)
Chris@0 4708 {
Chris@200 4709 MainWindowBase::modelAdded(model);
Chris@66 4710 if (dynamic_cast<DenseTimeValueModel *>(model)) {
Chris@66 4711 setupPaneAndLayerMenus();
Chris@66 4712 }
Chris@0 4713 }
Chris@0 4714
Chris@0 4715 void
Chris@0 4716 MainWindow::mainModelChanged(WaveFileModel *model)
Chris@0 4717 {
Chris@0 4718 m_panLayer->setModel(model);
Chris@200 4719
Chris@200 4720 MainWindowBase::mainModelChanged(model);
Chris@200 4721
Chris@1055 4722 if (m_playTarget || m_audioIO) {
Chris@1386 4723 connect(m_mainLevelPan, SIGNAL(levelChanged(float)),
Chris@1035 4724 this, SLOT(mainModelGainChanged(float)));
Chris@1386 4725 connect(m_mainLevelPan, SIGNAL(panChanged(float)),
Chris@1386 4726 this, SLOT(mainModelPanChanged(float)));
Chris@1035 4727 }
Chris@1035 4728 }
Chris@1035 4729
Chris@1035 4730 void
Chris@1035 4731 MainWindow::mainModelGainChanged(float gain)
Chris@1035 4732 {
Chris@1035 4733 if (m_playTarget) {
Chris@1035 4734 m_playTarget->setOutputGain(gain);
Chris@1055 4735 } else if (m_audioIO) {
Chris@1055 4736 m_audioIO->setOutputGain(gain);
Chris@200 4737 }
Chris@0 4738 }
Chris@0 4739
Chris@0 4740 void
Chris@1386 4741 MainWindow::mainModelPanChanged(float balance)
Chris@1386 4742 {
Chris@1386 4743 // this is indeed stereo balance rather than pan
Chris@1386 4744 if (m_playTarget) {
Chris@1386 4745 m_playTarget->setOutputBalance(balance);
Chris@1386 4746 } else if (m_audioIO) {
Chris@1386 4747 m_audioIO->setOutputBalance(balance);
Chris@1386 4748 }
Chris@1386 4749 }
Chris@1386 4750
Chris@1386 4751 void
Chris@760 4752 MainWindow::modelAboutToBeDeleted(Model *model)
Chris@760 4753 {
Chris@760 4754 if (model == m_panLayer->getModel()) {
Chris@760 4755 if (model == getMainModel()) {
Chris@760 4756 m_panLayer->setModel(0);
Chris@760 4757 } else {
Chris@760 4758 m_panLayer->setModel(getMainModel());
Chris@760 4759 }
Chris@760 4760 }
Chris@760 4761 MainWindowBase::modelAboutToBeDeleted(model);
Chris@760 4762 }
Chris@760 4763
Chris@760 4764 void
Chris@200 4765 MainWindow::setInstantsNumbering()
Chris@0 4766 {
Chris@200 4767 QAction *a = dynamic_cast<QAction *>(sender());
Chris@200 4768 if (!a) return;
Chris@200 4769
Chris@2093 4770 int type = 0;
Chris@2093 4771 for (auto &ai : m_numberingActions) {
Chris@2093 4772 if (ai.first == a) type = ai.second;
Chris@2093 4773 }
Chris@200 4774
Chris@200 4775 if (m_labeller) m_labeller->setType(Labeller::ValueType(type));
Chris@200 4776
Chris@200 4777 QSettings settings;
Chris@200 4778 settings.beginGroup("MainWindow");
Chris@200 4779 settings.setValue("labellertype", type);
Chris@200 4780 settings.endGroup();
Chris@200 4781 }
Chris@200 4782
Chris@200 4783 void
Chris@200 4784 MainWindow::setInstantsCounterCycle()
Chris@200 4785 {
Chris@200 4786 QAction *a = dynamic_cast<QAction *>(sender());
Chris@200 4787 if (!a) return;
Chris@200 4788
Chris@200 4789 int cycle = a->text().toInt();
Chris@200 4790 if (cycle == 0) return;
Chris@200 4791
Chris@200 4792 if (m_labeller) m_labeller->setCounterCycleSize(cycle);
Chris@200 4793
Chris@200 4794 QSettings settings;
Chris@200 4795 settings.beginGroup("MainWindow");
Chris@200 4796 settings.setValue("labellercycle", cycle);
Chris@200 4797 settings.endGroup();
Chris@200 4798 }
Chris@200 4799
Chris@200 4800 void
Chris@597 4801 MainWindow::setInstantsCounters()
Chris@200 4802 {
Chris@200 4803 LabelCounterInputDialog dialog(m_labeller, this);
Chris@241 4804 dialog.setWindowTitle(tr("Reset Counters"));
Chris@200 4805 dialog.exec();
Chris@0 4806 }
Chris@0 4807
Chris@0 4808 void
Chris@597 4809 MainWindow::resetInstantsCounters()
Chris@597 4810 {
Chris@597 4811 if (m_labeller) m_labeller->resetCounters();
Chris@597 4812 }
Chris@597 4813
Chris@597 4814 void
Chris@1355 4815 MainWindow::subdivideInstants()
Chris@1355 4816 {
Chris@1355 4817 QSettings settings;
Chris@1355 4818 settings.beginGroup("MainWindow");
Chris@1355 4819 int n = settings.value("subdivisions", 4).toInt();
Chris@1355 4820
Chris@1355 4821 bool ok;
Chris@1355 4822
Chris@1355 4823 n = QInputDialog::getInt(this,
Chris@1355 4824 tr("Subdivide instants"),
Chris@1355 4825 tr("Number of subdivisions:"),
Chris@1355 4826 n, 2, 96, 1, &ok);
Chris@1355 4827
Chris@1355 4828 if (ok) {
Chris@1355 4829 settings.setValue("subdivisions", n);
Chris@1355 4830 subdivideInstantsBy(n);
Chris@1355 4831 }
Chris@1355 4832
Chris@1355 4833 settings.endGroup();
Chris@1355 4834 }
Chris@1355 4835
Chris@1355 4836 void
Chris@1356 4837 MainWindow::winnowInstants()
Chris@1356 4838 {
Chris@1356 4839 QSettings settings;
Chris@1356 4840 settings.beginGroup("MainWindow");
Chris@1356 4841 int n = settings.value("winnow-subdivisions", 4).toInt();
Chris@1356 4842
Chris@1356 4843 bool ok;
Chris@1356 4844
Chris@1356 4845 n = QInputDialog::getInt(this,
Chris@1356 4846 tr("Winnow instants"),
Chris@1356 4847 tr("Remove all instants apart from multiples of:"),
Chris@1356 4848 n, 2, 96, 1, &ok);
Chris@1356 4849
Chris@1356 4850 if (ok) {
Chris@1356 4851 settings.setValue("winnow-subdivisions", n);
Chris@1356 4852 winnowInstantsBy(n);
Chris@1356 4853 }
Chris@1356 4854
Chris@1356 4855 settings.endGroup();
Chris@1356 4856 }
Chris@1356 4857
Chris@1356 4858 void
Chris@233 4859 MainWindow::modelGenerationFailed(QString transformName, QString message)
Chris@233 4860 {
Chris@247 4861 emit hideSplash();
Chris@247 4862
Chris@983 4863 QString quoted;
Chris@983 4864 if (transformName != "") {
Chris@983 4865 quoted = QString("\"%1\" ").arg(transformName);
Chris@983 4866 }
Chris@983 4867
Chris@233 4868 if (message != "") {
Chris@233 4869
Chris@233 4870 QMessageBox::warning
Chris@233 4871 (this,
Chris@233 4872 tr("Failed to generate layer"),
Chris@983 4873 tr("<b>Layer generation failed</b><p>Failed to generate derived layer.<p>The layer transform %1failed:<p>%2")
Chris@983 4874 .arg(quoted).arg(message),
Chris@233 4875 QMessageBox::Ok);
Chris@233 4876 } else {
Chris@233 4877 QMessageBox::warning
Chris@233 4878 (this,
Chris@233 4879 tr("Failed to generate layer"),
Chris@983 4880 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 4881 .arg(quoted),
Chris@233 4882 QMessageBox::Ok);
Chris@233 4883 }
Chris@233 4884 }
Chris@233 4885
Chris@233 4886 void
Chris@730 4887 MainWindow::modelGenerationWarning(QString /* transformName */, QString message)
Chris@233 4888 {
Chris@247 4889 emit hideSplash();
Chris@247 4890
Chris@233 4891 QMessageBox::warning
Chris@233 4892 (this, tr("Warning"), message, QMessageBox::Ok);
Chris@233 4893 }
Chris@233 4894
Chris@233 4895 void
Chris@233 4896 MainWindow::modelRegenerationFailed(QString layerName,
Chris@233 4897 QString transformName, QString message)
Chris@233 4898 {
Chris@247 4899 emit hideSplash();
Chris@247 4900
Chris@233 4901 if (message != "") {
Chris@233 4902
Chris@233 4903 QMessageBox::warning
Chris@233 4904 (this,
Chris@233 4905 tr("Failed to regenerate layer"),
Chris@233 4906 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 4907 .arg(layerName).arg(transformName).arg(message),
Chris@233 4908 QMessageBox::Ok);
Chris@233 4909 } else {
Chris@233 4910 QMessageBox::warning
Chris@233 4911 (this,
Chris@233 4912 tr("Failed to regenerate layer"),
Chris@233 4913 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 4914 .arg(layerName).arg(transformName),
Chris@233 4915 QMessageBox::Ok);
Chris@233 4916 }
Chris@233 4917 }
Chris@233 4918
Chris@233 4919 void
Chris@233 4920 MainWindow::modelRegenerationWarning(QString layerName,
Chris@730 4921 QString /* transformName */,
Chris@730 4922 QString message)
Chris@233 4923 {
Chris@247 4924 emit hideSplash();
Chris@247 4925
Chris@233 4926 QMessageBox::warning
Chris@233 4927 (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 4928 }
Chris@233 4929
Chris@233 4930 void
Chris@1151 4931 MainWindow::alignmentFailed(QString message)
Chris@0 4932 {
Chris@0 4933 QMessageBox::warning
Chris@0 4934 (this,
Chris@233 4935 tr("Failed to calculate alignment"),
Chris@1151 4936 tr("<b>Alignment calculation failed</b><p>Failed to calculate an audio alignment:<p>%1")
Chris@1151 4937 .arg(message),
Chris@165 4938 QMessageBox::Ok);
Chris@0 4939 }
Chris@0 4940
Chris@0 4941 void
Chris@0 4942 MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position)
Chris@0 4943 {
Chris@438 4944 // SVDEBUG << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << endl;
Chris@0 4945 m_paneStack->setCurrentPane(pane);
Chris@0 4946 m_rightButtonMenu->popup(position);
Chris@0 4947 }
Chris@0 4948
Chris@0 4949 void
Chris@0 4950 MainWindow::showLayerTree()
Chris@0 4951 {
Chris@219 4952 if (!m_layerTreeDialog.isNull()) {
Chris@219 4953 m_layerTreeDialog->show();
Chris@219 4954 m_layerTreeDialog->raise();
Chris@177 4955 return;
Chris@177 4956 }
Chris@177 4957
Chris@762 4958 m_layerTreeDialog = new LayerTreeDialog(m_paneStack, this);
Chris@232 4959 m_layerTreeDialog->setAttribute(Qt::WA_DeleteOnClose); // see below
Chris@219 4960 m_layerTreeDialog->show();
Chris@0 4961 }
Chris@0 4962
Chris@0 4963 void
Chris@306 4964 MainWindow::showActivityLog()
Chris@306 4965 {
Chris@306 4966 m_activityLog->show();
Chris@306 4967 m_activityLog->raise();
Chris@306 4968 m_activityLog->scrollToEnd();
Chris@306 4969 }
Chris@306 4970
Chris@306 4971 void
Chris@891 4972 MainWindow::showUnitConverter()
Chris@891 4973 {
Chris@891 4974 m_unitConverter->show();
Chris@891 4975 m_unitConverter->raise();
Chris@891 4976 }
Chris@891 4977
Chris@891 4978 void
Chris@0 4979 MainWindow::preferences()
Chris@0 4980 {
Chris@436 4981 bool goToTemplateTab =
Chris@436 4982 (sender() && sender()->objectName() == "set_default_template");
Chris@436 4983
Chris@0 4984 if (!m_preferencesDialog.isNull()) {
Chris@0 4985 m_preferencesDialog->show();
Chris@0 4986 m_preferencesDialog->raise();
Chris@436 4987 if (goToTemplateTab) {
Chris@436 4988 m_preferencesDialog->switchToTab(PreferencesDialog::TemplateTab);
Chris@436 4989 }
Chris@0 4990 return;
Chris@0 4991 }
Chris@0 4992
Chris@0 4993 m_preferencesDialog = new PreferencesDialog(this);
Chris@0 4994
Chris@1413 4995 connect(m_preferencesDialog, SIGNAL(audioDeviceChanged()),
Chris@1413 4996 this, SLOT(recreateAudioIO()));
Chris@1448 4997 connect(m_preferencesDialog, SIGNAL(coloursChanged()),
Chris@1448 4998 this, SLOT(coloursChanged()));
Chris@1413 4999
Chris@0 5000 // DeleteOnClose is safe here, because m_preferencesDialog is a
Chris@0 5001 // QPointer that will be zeroed when the dialog is deleted. We
Chris@0 5002 // use it in preference to leaving the dialog lying around because
Chris@0 5003 // if you Cancel the dialog, it resets the preferences state
Chris@0 5004 // without resetting its own widgets, so its state will be
Chris@0 5005 // incorrect when next shown unless we construct it afresh
Chris@0 5006 m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose);
Chris@0 5007
Chris@0 5008 m_preferencesDialog->show();
Chris@436 5009 if (goToTemplateTab) {
Chris@436 5010 m_preferencesDialog->switchToTab(PreferencesDialog::TemplateTab);
Chris@436 5011 }
Chris@0 5012 }
Chris@0 5013
Chris@0 5014 void
Chris@90 5015 MainWindow::mouseEnteredWidget()
Chris@90 5016 {
Chris@90 5017 QWidget *w = dynamic_cast<QWidget *>(sender());
Chris@90 5018 if (!w) return;
Chris@90 5019
Chris@2079 5020 QString mainText, editText;
Chris@2079 5021
Chris@1386 5022 if (w == m_mainLevelPan) {
Chris@2079 5023 mainText = tr("Adjust the master playback level and pan");
Chris@2079 5024 editText = tr("click then drag to adjust, ctrl+click to reset");
Chris@90 5025 } else if (w == m_playSpeed) {
Chris@2079 5026 mainText = tr("Adjust the master playback speed");
Chris@2079 5027 editText = tr("drag up/down to adjust, ctrl+click to reset");
Chris@2079 5028 }
Chris@2079 5029
Chris@2079 5030 if (mainText != "") {
Chris@2079 5031 contextHelpChanged(tr("%1: %2").arg(mainText).arg(editText));
Chris@90 5032 }
Chris@90 5033 }
Chris@90 5034
Chris@90 5035 void
Chris@90 5036 MainWindow::mouseLeftWidget()
Chris@90 5037 {
Chris@116 5038 contextHelpChanged("");
Chris@116 5039 }
Chris@116 5040
Chris@116 5041 void
Chris@0 5042 MainWindow::website()
Chris@0 5043 {
Chris@0 5044 openHelpUrl(tr("http://www.sonicvisualiser.org/"));
Chris@0 5045 }
Chris@0 5046
Chris@0 5047 void
Chris@0 5048 MainWindow::help()
Chris@0 5049 {
Chris@318 5050 openHelpUrl(tr("http://www.sonicvisualiser.org/doc/reference/%1/en/").arg(SV_VERSION));
Chris@0 5051 }
Chris@0 5052
Chris@0 5053 void
Chris@1516 5054 MainWindow::whatsNew()
Chris@1516 5055 {
Chris@1516 5056 QFile changelog(":CHANGELOG");
Chris@1516 5057 changelog.open(QFile::ReadOnly);
Chris@1516 5058 QByteArray content = changelog.readAll();
Chris@1516 5059 QString text = QString::fromUtf8(content);
Chris@1516 5060
Chris@1516 5061 QDialog *d = new QDialog(this);
Chris@1516 5062 d->setWindowTitle(tr("What's New"));
Chris@1516 5063
Chris@1516 5064 QGridLayout *layout = new QGridLayout;
Chris@1516 5065 d->setLayout(layout);
Chris@1516 5066
Chris@1516 5067 int row = 0;
Chris@1516 5068
Chris@1516 5069 QLabel *iconLabel = new QLabel;
Chris@1516 5070 iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64));
Chris@1516 5071 layout->addWidget(iconLabel, row, 0);
Chris@1516 5072
Chris@1516 5073 layout->addWidget
Chris@1516 5074 (new QLabel(tr("<h3>What's New in %1</h3>")
Chris@1516 5075 .arg(QApplication::applicationName())),
Chris@1516 5076 row++, 1);
Chris@1516 5077 layout->setColumnStretch(2, 10);
Chris@1516 5078
Chris@1516 5079 QTextEdit *textEdit = new QTextEdit;
Chris@1516 5080 layout->addWidget(textEdit, row++, 1, 1, 2);
Chris@1516 5081
Chris@1516 5082 if (m_newerVersionIs != "") {
Chris@1516 5083 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 5084 }
Chris@1516 5085
Chris@1516 5086 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
Chris@1516 5087 layout->addWidget(bb, row++, 0, 1, 3);
Chris@1516 5088 connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
Chris@1516 5089
Chris@1516 5090 text.replace(QRegExp("(.)\n +(.)"), "\\1 \\2");
Chris@1516 5091 text.replace(QRegExp("\n - ([^\n]+)"), "\n<li>\\1</li>");
Chris@1516 5092 text.replace(QRegExp(": *\n"), ":\n<ul>\n");
Chris@1516 5093 text.replace(QRegExp("</li>\n\\s*\n"), "</li>\n</ul>\n\n");
Chris@1516 5094 text.replace(QRegExp("\n(\\w[^:\n]+:)"), "\n<p><b>\\1</b></p>");
Chris@1516 5095 // text.replace(QRegExp("<li>([^,.\n]+)([,.] +\\w)"), "<li><b>\\1</b>\\2");
Chris@1516 5096
Chris@1516 5097 textEdit->setHtml(text);
Chris@1516 5098 textEdit->setReadOnly(true);
Chris@1516 5099
cannam@1517 5100 d->setMinimumSize(m_viewManager->scalePixelSize(520),
cannam@1517 5101 m_viewManager->scalePixelSize(450));
Chris@1516 5102
Chris@1516 5103 d->exec();
Chris@1516 5104
Chris@1516 5105 delete d;
Chris@1516 5106 }
Chris@1516 5107
Chris@1610 5108 QString
Chris@1610 5109 MainWindow::getReleaseText() const
Chris@0 5110 {
Chris@0 5111 bool debug = false;
Chris@0 5112 QString version = "(unknown version)";
Chris@0 5113
Chris@0 5114 #ifdef BUILD_DEBUG
Chris@0 5115 debug = true;
Chris@285 5116 #endif // BUILD_DEBUG
Chris@0 5117 #ifdef SV_VERSION
Chris@0 5118 #ifdef SVNREV
Chris@0 5119 version = tr("Release %1 : Revision %2").arg(SV_VERSION).arg(SVNREV);
Chris@285 5120 #else // !SVNREV
Chris@0 5121 version = tr("Release %1").arg(SV_VERSION);
Chris@285 5122 #endif // SVNREV
Chris@285 5123 #else // !SV_VERSION
Chris@0 5124 #ifdef SVNREV
Chris@0 5125 version = tr("Unreleased : Revision %1").arg(SVNREV);
Chris@285 5126 #endif // SVNREV
Chris@285 5127 #endif // SV_VERSION
Chris@0 5128
Chris@1610 5129 return tr("%1 : %2 configuration, %3-bit build")
Chris@0 5130 .arg(version)
Chris@1264 5131 .arg(debug ? tr("Debug") : tr("Release"))
Chris@1264 5132 .arg(sizeof(void *) * 8);
Chris@1610 5133 }
Chris@1610 5134
Chris@1610 5135 void
Chris@1610 5136 MainWindow::about()
Chris@1610 5137 {
Chris@1610 5138 QString aboutText;
Chris@1610 5139
Chris@1610 5140 aboutText += tr("<h3>About Sonic Visualiser</h3>");
Chris@1610 5141 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 5142 aboutText += QString("<p><small>%1</small></p>").arg(getReleaseText());
Chris@0 5143
Chris@1516 5144 if (m_oscQueue && m_oscQueue->isOK()) {
Chris@1516 5145 aboutText += tr("</small><p><small>The OSC URL for this instance is: \"%1\"").arg(m_oscQueue->getOSCURL());
Chris@1516 5146 }
Chris@1516 5147
Chris@1516 5148 aboutText += "</small><p><small>";
Chris@285 5149
Chris@1155 5150 aboutText += tr("With Qt v%1 &copy; The Qt Company").arg(QT_VERSION_STR);
Chris@285 5151
Chris@1516 5152 aboutText += "</small><small>";
Chris@1516 5153
Chris@0 5154 #ifdef HAVE_JACK
Chris@93 5155 #ifdef JACK_VERSION
Chris@285 5156 aboutText += tr("<br>With JACK audio output library v%1 &copy; Paul Davis and Jack O'Quin").arg(JACK_VERSION);
Chris@285 5157 #else // !JACK_VERSION
Chris@257 5158 aboutText += tr("<br>With JACK audio output library &copy; Paul Davis and Jack O'Quin");
Chris@285 5159 #endif // JACK_VERSION
Chris@285 5160 #endif // HAVE_JACK
Chris@0 5161 #ifdef HAVE_PORTAUDIO
Chris@257 5162 aboutText += tr("<br>With PortAudio audio output library &copy; Ross Bencina and Phil Burk");
Chris@285 5163 #endif // HAVE_PORTAUDIO
Chris@257 5164 #ifdef HAVE_LIBPULSE
Chris@285 5165 #ifdef LIBPULSE_VERSION
Chris@285 5166 aboutText += tr("<br>With PulseAudio audio output library v%1 &copy; Lennart Poettering and Pierre Ossman").arg(LIBPULSE_VERSION);
Chris@285 5167 #else // !LIBPULSE_VERSION
Chris@257 5168 aboutText += tr("<br>With PulseAudio audio output library &copy; Lennart Poettering and Pierre Ossman");
Chris@285 5169 #endif // LIBPULSE_VERSION
Chris@285 5170 #endif // HAVE_LIBPULSE
Chris@0 5171 #ifdef HAVE_OGGZ
Chris@93 5172 #ifdef OGGZ_VERSION
Chris@0 5173 aboutText += tr("<br>With Ogg file decoder (oggz v%1, fishsound v%2) &copy; CSIRO Australia").arg(OGGZ_VERSION).arg(FISHSOUND_VERSION);
Chris@285 5174 #else // !OGGZ_VERSION
Chris@93 5175 aboutText += tr("<br>With Ogg file decoder &copy; CSIRO Australia");
Chris@285 5176 #endif // OGGZ_VERSION
Chris@285 5177 #endif // HAVE_OGGZ
Chris@0 5178 #ifdef HAVE_MAD
Chris@93 5179 #ifdef MAD_VERSION
Chris@285 5180 aboutText += tr("<br>With MAD mp3 decoder v%1 &copy; Underbit Technologies Inc").arg(MAD_VERSION);
Chris@285 5181 #else // !MAD_VERSION
Chris@93 5182 aboutText += tr("<br>With MAD mp3 decoder &copy; Underbit Technologies Inc");
Chris@285 5183 #endif // MAD_VERSION
Chris@285 5184 #endif // HAVE_MAD
Chris@0 5185 #ifdef HAVE_SAMPLERATE
Chris@93 5186 #ifdef SAMPLERATE_VERSION
Chris@285 5187 aboutText += tr("<br>With libsamplerate v%1 &copy; Erik de Castro Lopo").arg(SAMPLERATE_VERSION);
Chris@285 5188 #else // !SAMPLERATE_VERSION
Chris@93 5189 aboutText += tr("<br>With libsamplerate &copy; Erik de Castro Lopo");
Chris@285 5190 #endif // SAMPLERATE_VERSION
Chris@285 5191 #endif // HAVE_SAMPLERATE
Chris@0 5192 #ifdef HAVE_SNDFILE
Chris@93 5193 #ifdef SNDFILE_VERSION
Chris@285 5194 aboutText += tr("<br>With libsndfile v%1 &copy; Erik de Castro Lopo").arg(SNDFILE_VERSION);
Chris@285 5195 #else // !SNDFILE_VERSION
Chris@93 5196 aboutText += tr("<br>With libsndfile &copy; Erik de Castro Lopo");
Chris@285 5197 #endif // SNDFILE_VERSION
Chris@285 5198 #endif // HAVE_SNDFILE
Chris@127 5199 #ifdef HAVE_FFTW3F
Chris@93 5200 #ifdef FFTW3_VERSION
Chris@285 5201 aboutText += tr("<br>With FFTW3 v%1 &copy; Matteo Frigo and MIT").arg(FFTW3_VERSION);
Chris@285 5202 #else // !FFTW3_VERSION
Chris@93 5203 aboutText += tr("<br>With FFTW3 &copy; Matteo Frigo and MIT");
Chris@285 5204 #endif // FFTW3_VERSION
Chris@285 5205 #endif // HAVE_FFTW3F
Chris@267 5206 #ifdef HAVE_RUBBERBAND
Chris@267 5207 #ifdef RUBBERBAND_VERSION
Chris@1315 5208 aboutText += tr("<br>With Rubber Band Library v%1 &copy; Particular Programs Ltd").arg(RUBBERBAND_VERSION);
Chris@285 5209 #else // !RUBBERBAND_VERSION
Chris@1315 5210 aboutText += tr("<br>With Rubber Band Library &copy; Particular Programs Ltd");
Chris@285 5211 #endif // RUBBERBAND_VERSION
Chris@285 5212 #endif // HAVE_RUBBERBAND
Chris@1315 5213 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 5214 aboutText += tr("<br>With Piper Vamp protocol bridge &copy; QMUL");
Chris@0 5215 aboutText += tr("<br>With LADSPA plugin support (API v%1) &copy; Richard Furse, Paul Davis, Stefan Westerfeld").arg(LADSPA_VERSION);
Chris@0 5216 aboutText += tr("<br>With DSSI plugin support (API v%1) &copy; Chris Cannam, Steve Harris, Sean Bolton").arg(DSSI_VERSION);
Chris@285 5217 #ifdef REDLAND_VERSION
Chris@285 5218 aboutText += tr("<br>With Redland RDF datastore v%1 &copy; Dave Beckett and the University of Bristol").arg(REDLAND_VERSION);
Chris@285 5219 #else // !REDLAND_VERSION
Chris@285 5220 aboutText += tr("<br>With Redland RDF datastore &copy; Dave Beckett and the University of Bristol");
Chris@285 5221 #endif // REDLAND_VERSION
Chris@523 5222 aboutText += tr("<br>With Serd and Sord RDF parser and store &copy; David Robillard");
Chris@1315 5223 aboutText += tr("<br>With Dataquay Qt/RDF library &copy; Particular Programs Ltd");
Chris@1315 5224 aboutText += tr("<br>With Cap'n Proto serialisation &copy; Sandstorm Development Group");
Chris@300 5225 aboutText += tr("<br>With RtMidi &copy; Gary P. Scavone");
Chris@300 5226
Chris@69 5227 #ifdef HAVE_LIBLO
Chris@93 5228 #ifdef LIBLO_VERSION
Chris@285 5229 aboutText += tr("<br>With liblo Lite OSC library v%1 &copy; Steve Harris").arg(LIBLO_VERSION);
Chris@285 5230 #else // !LIBLO_VERSION
Chris@327 5231 aboutText += tr("<br>With liblo Lite OSC library &copy; Steve Harris");
Chris@285 5232 #endif // LIBLO_VERSION
Chris@285 5233
Chris@285 5234 aboutText += "</small></p>";
Chris@285 5235 #endif // HAVE_LIBLO
Chris@285 5236
Chris@1667 5237 aboutText += "<p><small>";
Chris@1667 5238 aboutText += tr("Russian UI translation contributed by Alexandre Prokoudine.");
Chris@1667 5239 aboutText += "<br>";
Chris@1667 5240 aboutText += tr("Czech UI translation contributed by Pavel Fric.");
Chris@1667 5241 aboutText += "</small></p>";
Chris@1667 5242
Chris@0 5243 aboutText +=
Chris@1960 5244 "<p><small>Sonic Visualiser Copyright &copy; 2005&ndash;2018 Chris Cannam and "
Chris@1667 5245 "Queen Mary, University of London.</small></p>";
Chris@1667 5246
Chris@1667 5247 aboutText +=
Chris@285 5248 "<p><small>This program is free software; you can redistribute it and/or "
Chris@231 5249 "modify it under the terms of the GNU General Public License as "
Chris@231 5250 "published by the Free Software Foundation; either version 2 of the "
Chris@0 5251 "License, or (at your option) any later version.<br>See the file "
Chris@285 5252 "COPYING included with this distribution for more information.</small></p>";
Chris@1516 5253
Chris@1516 5254 // use our own dialog so we can influence the size
Chris@1516 5255
Chris@1516 5256 QDialog *d = new QDialog(this);
Chris@1516 5257
Chris@1516 5258 d->setWindowTitle(tr("About %1").arg(QApplication::applicationName()));
Chris@1516 5259
Chris@1516 5260 QGridLayout *layout = new QGridLayout;
Chris@1516 5261 d->setLayout(layout);
Chris@1516 5262
Chris@1516 5263 int row = 0;
Chris@0 5264
Chris@1516 5265 QLabel *iconLabel = new QLabel;
Chris@1516 5266 iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64));
Chris@1516 5267 layout->addWidget(iconLabel, row, 0, Qt::AlignTop);
Chris@1516 5268
Chris@1516 5269 QLabel *mainText = new QLabel();
Chris@1516 5270 layout->addWidget(mainText, row, 1, 1, 2);
Chris@1516 5271
Chris@1516 5272 layout->setRowStretch(row, 10);
Chris@1516 5273 layout->setColumnStretch(1, 10);
Chris@1516 5274
Chris@1516 5275 ++row;
Chris@1516 5276
Chris@1516 5277 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
Chris@1516 5278 layout->addWidget(bb, row++, 0, 1, 3);
Chris@1516 5279 connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
Chris@1516 5280
Chris@1516 5281 // mainText->setHtml(aboutText);
Chris@1516 5282 // mainText->setReadOnly(true);
Chris@1516 5283 mainText->setWordWrap(true);
Chris@1516 5284 mainText->setOpenExternalLinks(true);
Chris@1516 5285 mainText->setText(aboutText);
Chris@1516 5286
Chris@1516 5287 d->setMinimumSize(m_viewManager->scalePixelSize(420),
Chris@1516 5288 m_viewManager->scalePixelSize(200));
Chris@1516 5289
Chris@1516 5290 d->exec();
Chris@1516 5291
Chris@1516 5292 delete d;
Chris@1516 5293 /*
Chris@1516 5294 QMessageBox about(QMessageBox::Information,
Chris@1516 5295 tr("About Sonic Visualiser"),
Chris@1516 5296 aboutText,
Chris@1516 5297 QMessageBox::StandardButtons(QMessageBox::Ok),
Chris@1516 5298 this);
Chris@1516 5299
Chris@1516 5300 QIcon icon = QApplication::windowIcon();
Chris@1516 5301 QSize size = icon.actualSize(QSize(64, 64));
Chris@1516 5302 about.setIconPixmap(icon.pixmap(size));
Chris@1516 5303
Chris@1516 5304 about.setMinimumSize(m_viewManager->scalePixelSize(400),
Chris@1516 5305 m_viewManager->scalePixelSize(400));
Chris@1516 5306
Chris@1516 5307 about.exec();
Chris@1516 5308 */
Chris@0 5309 }
Chris@0 5310
Chris@162 5311 void
Chris@162 5312 MainWindow::keyReference()
Chris@162 5313 {
Chris@162 5314 m_keyReference->show();
Chris@162 5315 }
Chris@162 5316
Chris@333 5317 void
Chris@333 5318 MainWindow::newerVersionAvailable(QString version)
Chris@333 5319 {
Chris@1516 5320 m_newerVersionIs = version;
Chris@1516 5321
Chris@334 5322 QSettings settings;
Chris@334 5323 settings.beginGroup("NewerVersionWarning");
Chris@334 5324 QString tag = QString("version-%1-available-show").arg(version);
Chris@334 5325 if (settings.value(tag, true).toBool()) {
Chris@334 5326 QString title(tr("Newer version available"));
Chris@663 5327 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 5328 QMessageBox::information(this, title, text);
Chris@334 5329 settings.setValue(tag, false);
Chris@334 5330 }
Chris@334 5331 settings.endGroup();
Chris@333 5332 }
Chris@333 5333
Chris@333 5334