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