annotate main/MainWindow.cpp @ 1453:a9448d12d792 3.0-integration

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