MainWindowBase.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 "MainWindowBase.h"
17 #include "Document.h"
18 
19 #include "view/Pane.h"
20 #include "view/PaneStack.h"
21 #include "data/model/ReadOnlyWaveFileModel.h"
22 #include "data/model/WritableWaveFileModel.h"
23 #include "data/model/SparseOneDimensionalModel.h"
24 #include "data/model/NoteModel.h"
25 #include "data/model/Labeller.h"
26 #include "data/model/TabularModel.h"
27 #include "view/ViewManager.h"
28 
29 #include "layer/WaveformLayer.h"
30 #include "layer/TimeRulerLayer.h"
31 #include "layer/TimeInstantLayer.h"
32 #include "layer/TimeValueLayer.h"
33 #include "layer/Colour3DPlotLayer.h"
34 #include "layer/SliceLayer.h"
35 #include "layer/SliceableLayer.h"
36 #include "layer/ImageLayer.h"
37 #include "layer/NoteLayer.h"
38 #include "layer/FlexiNoteLayer.h"
39 #include "layer/RegionLayer.h"
40 #include "layer/SpectrogramLayer.h"
41 
42 #include "widgets/ListInputDialog.h"
43 #include "widgets/CommandHistory.h"
44 #include "widgets/ProgressDialog.h"
45 #include "widgets/MIDIFileImportDialog.h"
46 #include "widgets/CSVFormatDialog.h"
47 #include "widgets/ModelDataTableDialog.h"
48 #include "widgets/InteractiveFileFinder.h"
49 
50 #include "audio/AudioCallbackPlaySource.h"
51 #include "audio/AudioCallbackRecordTarget.h"
52 #include "audio/PlaySpeedRangeMapper.h"
53 
54 #include "data/fileio/DataFileReaderFactory.h"
55 #include "data/fileio/PlaylistFileReader.h"
56 #include "data/fileio/WavFileWriter.h"
57 #include "data/fileio/MIDIFileWriter.h"
58 #include "data/fileio/CSVFileWriter.h"
59 #include "data/fileio/BZipFileDevice.h"
60 #include "data/fileio/FileSource.h"
61 #include "data/fileio/AudioFileReaderFactory.h"
62 #include "data/fileio/TextTest.h"
63 #include "rdf/RDFImporter.h"
64 #include "rdf/RDFExporter.h"
65 
66 #include "transform/ModelTransformerFactory.h"
67 
68 #include "base/RecentFiles.h"
69 
70 #include "base/XmlExportable.h"
71 #include "base/Profiler.h"
72 #include "base/Preferences.h"
73 #include "base/TempWriteFile.h"
74 #include "base/Exceptions.h"
75 #include "base/ResourceFinder.h"
76 
77 #include "data/osc/OSCQueue.h"
78 #include "data/midi/MIDIInput.h"
79 #include "OSCScript.h"
80 
81 #include "system/System.h"
82 
83 #include <bqaudioio/SystemPlaybackTarget.h>
84 #include <bqaudioio/SystemAudioIO.h>
85 #include <bqaudioio/AudioFactory.h>
86 
87 #include <QApplication>
88 #include <QMessageBox>
89 #include <QGridLayout>
90 #include <QLabel>
91 #include <QAction>
92 #include <QMenuBar>
93 #include <QToolBar>
94 #include <QInputDialog>
95 #include <QStatusBar>
96 #include <QTreeView>
97 #include <QFile>
98 #include <QFileInfo>
99 #include <QDir>
100 #include <QTextStream>
101 #include <QTextCodec>
102 #include <QProcess>
103 #include <QShortcut>
104 #include <QSettings>
105 #include <QDateTime>
106 #include <QProcess>
107 #include <QCheckBox>
108 #include <QRegExp>
109 #include <QScrollArea>
110 #include <QScreen>
111 #include <QSignalMapper>
112 
113 #include <iostream>
114 #include <cstdio>
115 #include <errno.h>
116 
117 using std::vector;
118 using std::map;
119 using std::set;
120 
121 #ifdef Q_WS_X11
122 #define Window X11Window
123 #include <X11/Xlib.h>
124 #include <X11/Xutil.h>
125 #include <X11/Xatom.h>
126 #include <X11/SM/SMlib.h>
127 
128 static int handle_x11_error(Display *dpy, XErrorEvent *err)
129 {
130  char errstr[256];
131  XGetErrorText(dpy, err->error_code, errstr, 256);
132  if (err->error_code != BadWindow) {
133  cerr << "Sonic Visualiser: X Error: "
134  << errstr << " " << int(err->error_code)
135  << "\nin major opcode: "
136  << int(err->request_code) << endl;
137  }
138  return 0;
139 }
140 #undef Window
141 #endif
142 
144  MIDIMode midiMode,
145  PaneStack::Options paneStackOptions) :
146  m_document(nullptr),
147  m_paneStack(nullptr),
148  m_viewManager(nullptr),
149  m_timeRulerLayer(nullptr),
150  m_audioMode(audioMode),
151  m_midiMode(midiMode),
152  m_playSource(nullptr),
153  m_recordTarget(nullptr),
154  m_playTarget(nullptr),
155  m_audioIO(nullptr),
156  m_oscQueue(nullptr),
157  m_oscQueueStarter(nullptr),
158  m_oscScript(nullptr),
159  m_midiInput(nullptr),
160  m_recentFiles("RecentFiles", 20),
161  m_recentTransforms("RecentTransforms", 20),
162  m_documentModified(false),
163  m_openingAudioFile(false),
164  m_handlingOSC(false),
165  m_labeller(nullptr),
166  m_lastPlayStatusSec(0),
167  m_initialDarkBackground(false),
168  m_defaultFfwdRwdStep(2, 0),
169  m_audioRecordMode(RecordCreateAdditionalModel),
170  m_statusLabel(nullptr),
171  m_iconsVisibleInMenus(true),
172  m_menuShortcutMapper(nullptr)
173 {
174  Profiler profiler("MainWindowBase::MainWindowBase");
175 
176  SVDEBUG << "MainWindowBase::MainWindowBase" << endl;
177 
178  qRegisterMetaType<sv_frame_t>("sv_frame_t");
179  qRegisterMetaType<sv_samplerate_t>("sv_samplerate_t");
180  qRegisterMetaType<ModelId>("ModelId");
181 
182 #ifdef Q_WS_X11
183  XSetErrorHandler(handle_x11_error);
184 #endif
185 
186  connect(this, SIGNAL(hideSplash()), this, SLOT(emitHideSplash()));
187 
188  connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()),
189  this, SLOT(documentModified()));
190  connect(CommandHistory::getInstance(), SIGNAL(documentRestored()),
191  this, SLOT(documentRestored()));
192 
193  SVDEBUG << "MainWindowBase: Creating view manager" << endl;
194 
195  m_viewManager = new ViewManager();
196  connect(m_viewManager, SIGNAL(selectionChanged()),
197  this, SLOT(updateMenuStates()));
198  connect(m_viewManager, SIGNAL(inProgressSelectionChanged()),
199  this, SLOT(inProgressSelectionChanged()));
200 
201  SVDEBUG << "MainWindowBase: Calculating view font size" << endl;
202 
203  // set a sensible default font size for views -- cannot do this
204  // in Preferences, which is in base and not supposed to use QtGui
205  int viewFontSize = int(QApplication::font().pointSize() * 0.9);
206  QSettings settings;
207  settings.beginGroup("Preferences");
208  viewFontSize = settings.value("view-font-size", viewFontSize).toInt();
209  settings.setValue("view-font-size", viewFontSize);
210  settings.endGroup();
211 
212  SVDEBUG << "MainWindowBase: View font size is " << viewFontSize << endl;
213 
214 #ifndef Q_OS_MAC
215 
216  Preferences::BackgroundMode mode =
217  Preferences::getInstance()->getBackgroundMode();
218 
219  m_initialDarkBackground = m_viewManager->getGlobalDarkBackground();
220 
221  if (OSReportsDarkThemeActive()) {
222  // NB !(OSReportsDarkThemeActive()) doesn't necessarily mean
223  // the theme is light - the function also cunningly returns
224  // false if it has no way to tell
226  }
227 
228  if (mode == Preferences::BackgroundFromTheme) {
229  m_viewManager->setGlobalDarkBackground
231  } else {
232  m_viewManager->setGlobalDarkBackground
233  (mode == Preferences::DarkBackground);
234  }
235 
236 #endif
237 
238  m_paneStack = new PaneStack(nullptr, m_viewManager, paneStackOptions);
239  connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)),
240  this, SLOT(currentPaneChanged(Pane *)));
241  connect(m_paneStack, SIGNAL(currentLayerChanged(Pane *, Layer *)),
242  this, SLOT(currentLayerChanged(Pane *, Layer *)));
243  connect(m_paneStack, SIGNAL(paneRightButtonMenuRequested(Pane *, QPoint)),
244  this, SLOT(paneRightButtonMenuRequested(Pane *, QPoint)));
245  connect(m_paneStack, SIGNAL(panePropertiesRightButtonMenuRequested(Pane *, QPoint)),
246  this, SLOT(panePropertiesRightButtonMenuRequested(Pane *, QPoint)));
247  connect(m_paneStack, SIGNAL(layerPropertiesRightButtonMenuRequested(Pane *, Layer *, QPoint)),
248  this, SLOT(layerPropertiesRightButtonMenuRequested(Pane *, Layer *, QPoint)));
249  connect(m_paneStack, SIGNAL(contextHelpChanged(const QString &)),
250  this, SLOT(contextHelpChanged(const QString &)));
251  connect(m_paneStack, SIGNAL(paneAdded(Pane *)),
252  this, SLOT(paneAdded(Pane *)));
253  connect(m_paneStack, SIGNAL(paneHidden(Pane *)),
254  this, SLOT(paneHidden(Pane *)));
255  connect(m_paneStack, SIGNAL(paneAboutToBeDeleted(Pane *)),
256  this, SLOT(paneAboutToBeDeleted(Pane *)));
257  connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QStringList)),
258  this, SLOT(paneDropAccepted(Pane *, QStringList)));
259  connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QString)),
260  this, SLOT(paneDropAccepted(Pane *, QString)));
261  connect(m_paneStack, SIGNAL(paneDeleteButtonClicked(Pane *)),
262  this, SLOT(paneDeleteButtonClicked(Pane *)));
263 
264  SVDEBUG << "MainWindowBase: Creating play source" << endl;
265 
266  m_playSource = new AudioCallbackPlaySource
267  (m_viewManager, QApplication::applicationName());
268 
271  SVDEBUG << "MainWindowBase: Creating record target" << endl;
272  m_recordTarget = new AudioCallbackRecordTarget
273  (m_viewManager, QApplication::applicationName());
274  connect(m_recordTarget,
275  SIGNAL(recordDurationChanged(sv_frame_t, sv_samplerate_t)),
276  this,
277  SLOT(recordDurationChanged(sv_frame_t, sv_samplerate_t)));
278  }
279 
280  connect(m_playSource, SIGNAL(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)),
281  this, SLOT(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)));
282  connect(m_playSource, SIGNAL(channelCountIncreased(int)),
283  this, SLOT(audioChannelCountIncreased(int)));
284  connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()),
285  this, SLOT(audioOverloadPluginDisabled()));
286 
287  connect(m_viewManager, SIGNAL(monitoringLevelsChanged(float, float)),
288  this, SLOT(monitoringLevelsChanged(float, float)));
289 
290  connect(m_viewManager, SIGNAL(playbackFrameChanged(sv_frame_t)),
291  this, SLOT(playbackFrameChanged(sv_frame_t)));
292 
293  connect(m_viewManager, SIGNAL(globalCentreFrameChanged(sv_frame_t)),
294  this, SLOT(globalCentreFrameChanged(sv_frame_t)));
295 
296  connect(m_viewManager, SIGNAL(viewCentreFrameChanged(View *, sv_frame_t)),
297  this, SLOT(viewCentreFrameChanged(View *, sv_frame_t)));
298 
299  connect(m_viewManager, SIGNAL(viewZoomLevelChanged(View *, ZoomLevel, bool)),
300  this, SLOT(viewZoomLevelChanged(View *, ZoomLevel, bool)));
301 
302  connect(Preferences::getInstance(),
303  SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
304  this,
305  SLOT(preferenceChanged(PropertyContainer::PropertyName)));
306 
307  SVDEBUG << "MainWindowBase: Creating labeller" << endl;
308 
309  Labeller::ValueType labellerType = Labeller::ValueFromTwoLevelCounter;
310  settings.beginGroup("MainWindow");
311 
312  labellerType = (Labeller::ValueType)
313  settings.value("labellertype", (int)labellerType).toInt();
314  int cycle = settings.value("labellercycle", 4).toInt();
315 
316  settings.endGroup();
317 
318  m_labeller = new Labeller(labellerType);
319  m_labeller->setCounterCycleSize(cycle);
320 
321  if (m_midiMode == MIDI_LISTEN) {
322  SVDEBUG << "MainWindowBase: Creating MIDI input" << endl;
323  m_midiInput = new MIDIInput(QApplication::applicationName(), this);
324  }
325 
326  QTimer::singleShot(1500, this, SIGNAL(hideSplash()));
327 
328  SVDEBUG << "MainWindowBase: Constructor done" << endl;
329 }
330 
332 {
333  SVDEBUG << "MainWindowBase::~MainWindowBase" << endl;
334 
335  // We have to delete the breakfastquay::SystemPlaybackTarget or
336  // breakfastquay::SystemAudioIO object (whichever we have -- it
337  // depends on whether we handle recording or not) before we delete
338  // the ApplicationPlaybackSource and ApplicationRecordTarget that
339  // they refer to.
340 
341  deleteAudioIO();
342 
343  // Then delete the Application objects.
344  delete m_playSource;
345  delete m_recordTarget;
346 
347  delete m_viewManager;
348  delete m_midiInput;
349 
350  if (m_oscScript) {
351  disconnect(m_oscScript, nullptr, nullptr, nullptr);
352  m_oscScript->abandon();
353  m_oscScript->wait(1000);
354  if (m_oscScript->isRunning()) {
355  m_oscScript->terminate();
356  m_oscScript->wait(1000);
357  }
358  delete m_oscScript;
359  }
360 
361  if (m_oscQueueStarter) {
362  disconnect(m_oscQueueStarter, nullptr, nullptr, nullptr);
363  m_oscQueueStarter->wait(1000);
364  if (m_oscQueueStarter->isRunning()) {
365  m_oscQueueStarter->terminate();
366  m_oscQueueStarter->wait(1000);
367  }
368  delete m_oscQueueStarter;
369  delete m_oscQueue;
370  }
371 
372  Profiles::getInstance()->dump();
373 }
374 
375 void
377 {
378  SVDEBUG << "MainWindowBase: Hiding splash screen (if not hidden already)" << endl;
379  emit hideSplash(this);
380 }
381 
382 void
384 {
385  SVDEBUG << "MainWindowBase::finaliseMenus called" << endl;
386 
387  delete m_menuShortcutMapper;
388  m_menuShortcutMapper = nullptr;
389 
390  foreach (QShortcut *sc, m_appShortcuts) {
391  delete sc;
392  }
393  m_appShortcuts.clear();
394 
395  QMenuBar *mb = menuBar();
396 
397  // This used to find all children of QMenu type, and call
398  // finaliseMenu on those. But it seems we are getting hold of some
399  // menus that way that are not actually active in the menu bar and
400  // are not returned in their parent menu's actions() list, and if
401  // we finalise those, we end up with duplicate shortcuts in the
402  // app shortcut mapper. So we should do this by descending the
403  // menu tree through only those menus accessible via actions()
404  // from their parents instead.
405 
406  QList<QMenu *> menus = mb->findChildren<QMenu *>
407  (QString(), Qt::FindDirectChildrenOnly);
408 
409  foreach (QMenu *menu, menus) {
410  if (menu) finaliseMenu(menu);
411  }
412 
413  SVDEBUG << "MainWindowBase::finaliseMenus done" << endl;
414 }
415 
416 void
418 {
419  foreach (QAction *a, menu->actions()) {
420  a->setIconVisibleInMenu(m_iconsVisibleInMenus);
421  }
422 
423 #ifdef Q_OS_MAC
424  // See https://bugreports.qt-project.org/browse/QTBUG-38256 and
425  // our issue #890 http://code.soundsoftware.ac.uk/issues/890 --
426  // single-key shortcuts that are associated only with a menu
427  // action (and not with a toolbar button) do not work with Qt 5.x
428  // under OS/X.
429  //
430  // Apparently Cocoa never handled them as a matter of course, but
431  // earlier versions of Qt picked them up as widget shortcuts and
432  // handled them anyway. That behaviour was removed to fix a crash
433  // when invoking a menu while its window was overridden by a modal
434  // dialog (https://bugreports.qt-project.org/browse/QTBUG-30657).
435  //
436  // This workaround restores the single-key shortcut behaviour by
437  // searching in menus for single-key shortcuts that are associated
438  // only with the menu and not with a toolbar button, and
439  // augmenting them with global application shortcuts that invoke
440  // the relevant actions, testing whether the actions are enabled
441  // on invocation.
442  //
443  // (Previously this acted on all single-key shortcuts in menus,
444  // and it removed the shortcut from the action when it created
445  // each new global one, in order to avoid an "ambiguous shortcut"
446  // error in the case where the action was also associated with a
447  // toolbar button. But that has the unwelcome side-effect of
448  // removing the shortcut hint from the menu entry. So now we leave
449  // the shortcut in the menu action as well as creating a global
450  // one, and we only act on shortcuts that have no toolbar button,
451  // i.e. that will not otherwise work. The downside is that if this
452  // bug is fixed in a future Qt release, we will start getting
453  // "ambiguous shortcut" errors from the menu entry actions and
454  // will need to update the code.)
455 
456  // Update: The bug was fixed in Qt 5.4 for shortcuts with no
457  // modifier, and I believe it is fixed in Qt 5.5 for shortcuts
458  // with Shift modifiers. The below reflects that
459 
460 #if (QT_VERSION < QT_VERSION_CHECK(5, 5, 0))
461 
462  if (!m_menuShortcutMapper) {
463  m_menuShortcutMapper = new QSignalMapper(this);
464  connect(m_menuShortcutMapper, SIGNAL(mapped(QObject *)),
465  this, SLOT(menuActionMapperInvoked(QObject *)));
466  }
467 
468  foreach (QAction *a, menu->actions()) {
469 
470  if (a->isSeparator()) {
471  continue;
472  } else if (a->menu()) {
473  finaliseMenu(a->menu());
474  } else {
475 
476  QWidgetList ww = a->associatedWidgets();
477  bool hasButton = false;
478  foreach (QWidget *w, ww) {
479  if (qobject_cast<QAbstractButton *>(w)) {
480  hasButton = true;
481  break;
482  }
483  }
484  if (hasButton) continue;
485  QKeySequence sc = a->shortcut();
486 
487  // Note that the set of "single-key shortcuts" that aren't
488  // working and that we need to handle here includes those
489  // with the Shift modifier mask as well as those with no
490  // modifier at all
491 #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
492  // Nothing needed
493  if (false) {
494 #elif (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
495  if (sc.count() == 1 &&
496  (sc[0] & Qt::KeyboardModifierMask) == Qt::ShiftModifier) {
497 #else
498  if (sc.count() == 1 &&
499  ((sc[0] & Qt::KeyboardModifierMask) == Qt::NoModifier ||
500  (sc[0] & Qt::KeyboardModifierMask) == Qt::ShiftModifier)) {
501 #endif
502  QShortcut *newSc = new QShortcut(sc, a->parentWidget());
503  QObject::connect(newSc, SIGNAL(activated()),
504  m_menuShortcutMapper, SLOT(map()));
505  m_menuShortcutMapper->setMapping(newSc, a);
506  m_appShortcuts.push_back(newSc);
507  }
508  }
509  }
510 #endif
511 #endif
512 }
513 
514 void
516 {
517  QAction *a = qobject_cast<QAction *>(o);
518  if (a && a->isEnabled()) {
519  a->trigger();
520  }
521 }
522 
523 void
525 {
526  QScreen *screen = QApplication::primaryScreen();
527  QRect available = screen->availableGeometry();
528  QSize actual(std::min(size.width(), available.width()),
529  std::min(size.height(), available.height()));
530  resize(actual);
531 }
532 
533 void
534 MainWindowBase::startOSCQueue(bool withNetworkPort)
535 {
536  m_oscQueueStarter = new OSCQueueStarter(this, withNetworkPort);
537  connect(m_oscQueueStarter, SIGNAL(finished()), this, SLOT(oscReady()));
538  m_oscQueueStarter->start();
539 }
540 
541 void
543 {
544  if (m_oscQueue && m_oscQueue->isOK()) {
545  connect(m_oscQueue, SIGNAL(messagesAvailable()), this, SLOT(pollOSC()));
546  QTimer *oscTimer = new QTimer(this);
547  connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC()));
548  oscTimer->start(2000);
549 
550  if (m_oscQueue->hasPort()) {
551  SVDEBUG << "Finished setting up OSC interface" << endl;
552  } else {
553  SVDEBUG << "Finished setting up internal-only OSC queue" << endl;
554  }
555 
556  if (m_oscScriptFile != QString()) {
557  startOSCScript();
558  }
559  }
560 }
561 
562 void
564 {
566  connect(m_oscScript, SIGNAL(finished()),
567  this, SLOT(oscScriptFinished()));
568  m_oscScriptFile = QString();
569  m_oscScript->start();
570 }
571 
572 void
574 {
575  m_oscScriptFile = fileName;
576  if (m_oscQueue && m_oscQueue->isOK()) {
577  startOSCScript();
578  }
579 }
580 
581 void
583 {
584  delete m_oscScript;
585  m_oscScript = 0;
586 }
587 
588 QString
589 MainWindowBase::getOpenFileName(FileFinder::FileType type)
590 {
591  FileFinder *ff = FileFinder::getInstance();
592 
593  if (type == FileFinder::AnyFile) {
594  if (!getMainModelId().isNone() &&
595  m_paneStack != nullptr &&
596  m_paneStack->getCurrentPane() != nullptr) { // can import a layer
597  return ff->getOpenFileName(FileFinder::AnyFile, m_sessionFile);
598  } else {
599  return ff->getOpenFileName(FileFinder::SessionOrAudioFile,
600  m_sessionFile);
601  }
602  }
603 
604  QString lastPath = m_sessionFile;
605 
606  if (type == FileFinder::AudioFile) {
607  lastPath = m_audioFile;
608  }
609 
610  return ff->getOpenFileName(type, lastPath);
611 }
612 
613 QString
614 MainWindowBase::getSaveFileName(FileFinder::FileType type)
615 {
616  QString lastPath = m_sessionFile;
617 
618  if (type == FileFinder::AudioFile) {
619  lastPath = m_audioFile;
620  }
621 
622  FileFinder *ff = FileFinder::getInstance();
623  return ff->getSaveFileName(type, lastPath);
624 }
625 
626 void
627 MainWindowBase::registerLastOpenedFilePath(FileFinder::FileType type, QString path)
628 {
629  FileFinder *ff = FileFinder::getInstance();
630  ff->registerLastOpenedFilePath(type, path);
631 }
632 
633 QString
635 {
636  QSettings settings;
637  settings.beginGroup("MainWindow");
638  QString templateName = settings.value("sessiontemplate", "").toString();
639  if (templateName == "") templateName = "default";
640  return templateName;
641 }
642 
643 void
645 {
646  QSettings settings;
647  settings.beginGroup("MainWindow");
648  settings.setValue("sessiontemplate", n);
649 }
650 
651 void
653 {
654  Pane *currentPane = nullptr;
655  Layer *currentLayer = nullptr;
656 
657  if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
658  if (currentPane) currentLayer = currentPane->getSelectedLayer();
659 
660  bool havePrevPane = false, haveNextPane = false;
661  bool havePrevLayer = false, haveNextLayer = false;
662 
663  if (currentPane) {
664  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
665  if (m_paneStack->getPane(i) == currentPane) {
666  if (i > 0) havePrevPane = true;
667  if (i < m_paneStack->getPaneCount()-1) haveNextPane = true;
668  break;
669  }
670  }
671  // the prev/next layer commands actually include the pane
672  // itself as one of the selectables -- so we always have a
673  // prev and next layer, as long as we have a pane with at
674  // least one layer in it
675  if (currentPane->getLayerCount() > 0) {
676  havePrevLayer = true;
677  haveNextLayer = true;
678  }
679  }
680 
681  bool haveCurrentPane =
682  (currentPane != nullptr);
683  bool haveCurrentLayer =
684  (haveCurrentPane &&
685  (currentLayer != nullptr));
686  bool haveMainModel =
687  (!getMainModelId().isNone());
688  bool havePlayTarget =
689  (m_playTarget != nullptr || m_audioIO != nullptr);
690  bool haveSelection =
691  (m_viewManager &&
692  !m_viewManager->getSelections().empty());
693  bool haveCurrentEditableLayer =
694  (haveCurrentLayer &&
695  currentLayer->isLayerEditable());
696  bool haveCurrentTimeInstantsLayer =
697  (haveCurrentLayer &&
698  dynamic_cast<TimeInstantLayer *>(currentLayer));
699  bool haveCurrentDurationLayer =
700  (haveCurrentLayer &&
701  (dynamic_cast<NoteLayer *>(currentLayer) ||
702  dynamic_cast<FlexiNoteLayer *>(currentLayer) ||
703  dynamic_cast<RegionLayer *>(currentLayer)));
704  bool haveCurrentColour3DPlot =
705  (haveCurrentLayer &&
706  dynamic_cast<Colour3DPlotLayer *>(currentLayer));
707  bool haveCurrentSpectrogram =
708  (haveCurrentLayer &&
709  dynamic_cast<SpectrogramLayer *>(currentLayer));
710  bool haveClipboardContents =
711  (m_viewManager &&
712  !m_viewManager->getClipboard().empty());
713  bool haveTabularLayer =
714  (haveCurrentLayer &&
715  ModelById::isa<TabularModel>(currentLayer->getModel()));
716 
717  emit canAddPane(haveMainModel);
718  emit canDeleteCurrentPane(haveCurrentPane);
719  emit canZoom(haveMainModel && haveCurrentPane);
720  emit canScroll(haveMainModel && haveCurrentPane);
721  emit canAddLayer(haveMainModel && haveCurrentPane);
722  emit canImportMoreAudio(haveMainModel);
723  emit canReplaceMainAudio(haveMainModel);
724  emit canImportLayer(haveMainModel && haveCurrentPane);
725  emit canExportAudio(haveMainModel);
726  emit canChangeSessionTemplate(haveMainModel);
727  emit canExportLayer(haveMainModel &&
728  (haveCurrentEditableLayer ||
729  haveCurrentColour3DPlot ||
730  haveCurrentSpectrogram));
731  emit canExportImage(haveMainModel && haveCurrentPane);
732  emit canDeleteCurrentLayer(haveCurrentLayer);
733  emit canRenameLayer(haveCurrentLayer);
734  emit canEditLayer(haveCurrentEditableLayer);
735  emit canEditLayerTabular(haveCurrentEditableLayer || haveTabularLayer);
736  emit canMeasureLayer(haveCurrentLayer);
737  emit canSelect(haveMainModel && haveCurrentPane);
738  emit canPlay(haveMainModel && havePlayTarget);
739  emit canFfwd(haveMainModel);
740  emit canRewind(haveMainModel);
741  emit canPaste(haveClipboardContents);
742  emit canInsertInstant(haveCurrentPane);
743  emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection);
744  emit canInsertItemAtSelection(haveCurrentPane && haveSelection && haveCurrentDurationLayer);
745  emit canRenumberInstants(haveCurrentTimeInstantsLayer && haveSelection);
746  emit canSubdivideInstants(haveCurrentTimeInstantsLayer && haveSelection);
747  emit canWinnowInstants(haveCurrentTimeInstantsLayer && haveSelection);
748  emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection);
749  emit canClearSelection(haveSelection);
750  emit canEditSelection(haveSelection && haveCurrentEditableLayer);
752  emit canSaveAs(haveMainModel); // possibly used only in Tony, not SV
753  emit canSelectPreviousPane(havePrevPane);
754  emit canSelectNextPane(haveNextPane);
755  emit canSelectPreviousLayer(havePrevLayer);
756  emit canSelectNextLayer(haveNextLayer);
757 
758  // This is quite subtle -- whereas we can play back only if a
759  // system play target or I/O exists, we can record even if no
760  // record source (i.e. audioIO) exists because we can record into
761  // an empty session before the audio device has been
762  // opened.
763  //
764  // However, if there is no record *target* then recording was
765  // actively disabled via the audio mode setting.
766  //
767  // If we have a play target instead of an audioIO, then if the
768  // audio mode is AUDIO_PLAYBACK_NOW_RECORD_LATER, we are still
769  // expecting to open the IO on demand, but if it is
770  // AUDIO_PLAYBACK_AND_RECORD then we must have tried to open the
771  // device and failed to find any capture source.
772  //
773  bool recordDisabled = (m_recordTarget == nullptr);
774  bool recordDeviceFailed =
776  (m_playTarget != nullptr && m_audioIO == nullptr));
777  emit canRecord(!recordDisabled && !recordDeviceFailed);
778 }
779 
780 void
782 {
783  QString title;
784 
785  if (m_sessionFile != "") {
786  if (m_originalLocation != "" &&
787  m_originalLocation != m_sessionFile) { // session + location
788  title = tr("%1: %2 [%3]")
789  .arg(QApplication::applicationName())
790  .arg(QFileInfo(m_sessionFile).fileName())
791  .arg(m_originalLocation);
792  } else { // session only
793  title = tr("%1: %2")
794  .arg(QApplication::applicationName())
795  .arg(QFileInfo(m_sessionFile).fileName());
796  }
797  } else {
798  if (m_originalLocation != "") { // location only
799  title = tr("%1: %2")
800  .arg(QApplication::applicationName())
801  .arg(m_originalLocation);
802  } else { // neither
803  title = QApplication::applicationName();
804  }
805  }
806 
807  if (m_documentModified) {
808  title = tr("%1 (modified)").arg(title);
809  }
810 
811  setWindowTitle(title);
812 }
813 
814 void
816 {
817 // SVDEBUG << "MainWindowBase::documentModified" << endl;
818 
819  m_documentModified = true;
822 }
823 
824 void
826 {
827 // SVDEBUG << "MainWindowBase::documentRestored" << endl;
828 
829  m_documentModified = false;
832 }
833 
834 void
836 {
837  QAction *action = dynamic_cast<QAction *>(sender());
838 
839  if (action) {
840  m_viewManager->setPlayLoopMode(action->isChecked());
841  } else {
842  m_viewManager->setPlayLoopMode(!m_viewManager->getPlayLoopMode());
843  }
844 }
845 
846 void
848 {
849  QAction *action = dynamic_cast<QAction *>(sender());
850 
851  if (action) {
852  m_viewManager->setPlaySelectionMode(action->isChecked());
853  } else {
854  m_viewManager->setPlaySelectionMode(!m_viewManager->getPlaySelectionMode());
855  }
856 }
857 
858 void
860 {
861  QAction *action = dynamic_cast<QAction *>(sender());
862 
863  if (action) {
864  m_viewManager->setPlaySoloMode(action->isChecked());
865  } else {
866  m_viewManager->setPlaySoloMode(!m_viewManager->getPlaySoloMode());
867  }
868 
869  if (m_viewManager->getPlaySoloMode()) {
870  currentPaneChanged(m_paneStack->getCurrentPane());
871  } else {
872  m_viewManager->setPlaybackModel({});
873  if (m_playSource) {
874  m_playSource->clearSoloModelSet();
875  }
876  }
877 }
878 
879 void
881 {
884 
885  if (!p) return;
886 
887  if (!(m_viewManager &&
888  m_playSource &&
889  m_viewManager->getPlaySoloMode())) {
890  if (m_viewManager) {
891  m_viewManager->setPlaybackModel(ModelId());
892  }
893  return;
894  }
895 
896  ModelId prevPlaybackModel = m_viewManager->getPlaybackModel();
897 
898  // What we want here is not the currently playing frame (unless we
899  // are about to clear out the audio playback buffers -- which may
900  // or may not be possible, depending on the audio driver). What
901  // we want is the frame that was last committed to the soundcard
902  // buffers, as the audio driver will continue playing up to that
903  // frame before switching to whichever one we decide we want to
904  // switch to, regardless of our efforts.
905 
906  sv_frame_t frame = m_playSource->getCurrentBufferedFrame();
907 
908  cerr << "currentPaneChanged: current frame (in ref model) = " << frame << endl;
909 
910  View::ModelSet soloModels = p->getModels();
911 
912  View::ModelSet sources;
913  for (ModelId modelId: sources) {
914  // If a model in this pane is derived from something else,
915  // then we want to play that model as well -- if the model
916  // that's derived from it is not something that is itself
917  // individually playable (e.g. a waveform)
918  if (auto model = ModelById::get(modelId)) {
919  if (!ModelById::isa<RangeSummarisableTimeValueModel>(modelId) &&
920  !model->getSourceModel().isNone()) {
921  sources.insert(model->getSourceModel());
922  }
923  }
924  }
925  for (ModelId modelId: sources) {
926  soloModels.insert(modelId);
927  }
928 
930  //playback model has changed, and changing it on ViewManager --
931  //the play source should be making the setPlaybackModel call to
932  //ViewManager
933 
934  ModelId newPlaybackModel;
935 
936  for (ModelId modelId: soloModels) {
937  if (ModelById::isa<RangeSummarisableTimeValueModel>(modelId)) {
938  m_viewManager->setPlaybackModel(modelId);
939  newPlaybackModel = modelId;
940  }
941  }
942 
943  m_playSource->setSoloModelSet(soloModels);
944 
945  if (!prevPlaybackModel.isNone() && !newPlaybackModel.isNone() &&
946  prevPlaybackModel != newPlaybackModel) {
947  if (m_playSource->isPlaying()) {
948  m_playSource->play(frame);
949  }
950  }
951 }
952 
953 void
955 {
958 }
959 
960 sv_frame_t
962 {
963  sv_frame_t startFrame = 0;
964  if (!m_paneStack) return startFrame;
965  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
966  sv_frame_t thisStart = m_paneStack->getPane(i)->getModelsStartFrame();
967  if (i == 0 || thisStart < startFrame) {
968  startFrame = thisStart;
969  }
970  }
971  return startFrame;
972 }
973 
974 sv_frame_t
976 {
977  sv_frame_t endFrame = 0;
978  if (!m_paneStack) return endFrame;
979  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
980  sv_frame_t thisEnd = m_paneStack->getPane(i)->getModelsEndFrame();
981  if (i == 0 || thisEnd > endFrame) {
982  endFrame = thisEnd;
983  }
984  }
985  return endFrame;
986 }
987 
988 void
990 {
991  m_viewManager->setSelection(Selection(getModelsStartFrame(),
992  getModelsEndFrame()));
993 }
994 
995 void
997 {
998  m_viewManager->setSelection(Selection(getModelsStartFrame(),
999  m_viewManager->getGlobalCentreFrame()));
1000 }
1001 
1002 void
1004 {
1005  m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(),
1006  getModelsEndFrame()));
1007 }
1008 
1009 void
1011 {
1012  auto model = getMainModel();
1013  if (!model) return;
1014 
1015  Pane *currentPane = m_paneStack->getCurrentPane();
1016  if (!currentPane) return;
1017 
1018  sv_frame_t startFrame, endFrame;
1019 
1020  if (currentPane->getStartFrame() < 0) {
1021  startFrame = 0;
1022  } else {
1023  startFrame = currentPane->getStartFrame();
1024  }
1025 
1026  if (currentPane->getEndFrame() > model->getEndFrame()) {
1027  endFrame = model->getEndFrame();
1028  } else {
1029  endFrame = currentPane->getEndFrame();
1030  }
1031 
1032  m_viewManager->setSelection(Selection(startFrame, endFrame));
1033 }
1034 
1035 void
1037 {
1038  m_viewManager->clearSelections();
1039 }
1040 
1041 void
1043 {
1044  Pane *currentPane = m_paneStack->getCurrentPane();
1045  if (!currentPane) return;
1046 
1047  Layer *layer = currentPane->getSelectedLayer();
1048  if (!layer) return;
1049 
1050  Clipboard &clipboard = m_viewManager->getClipboard();
1051  clipboard.clear();
1052 
1053  MultiSelection::SelectionList selections = m_viewManager->getSelections();
1054 
1055  CommandHistory::getInstance()->startCompoundOperation(tr("Cut"), true);
1056 
1057  for (MultiSelection::SelectionList::iterator i = selections.begin();
1058  i != selections.end(); ++i) {
1059  layer->copy(currentPane, *i, clipboard);
1060  layer->deleteSelection(*i);
1061  }
1062 
1063  CommandHistory::getInstance()->endCompoundOperation();
1064 }
1065 
1066 void
1068 {
1069  Pane *currentPane = m_paneStack->getCurrentPane();
1070  if (!currentPane) return;
1071 
1072  Layer *layer = currentPane->getSelectedLayer();
1073  if (!layer) return;
1074 
1075  Clipboard &clipboard = m_viewManager->getClipboard();
1076  clipboard.clear();
1077 
1078  MultiSelection::SelectionList selections = m_viewManager->getSelections();
1079 
1080  for (MultiSelection::SelectionList::iterator i = selections.begin();
1081  i != selections.end(); ++i) {
1082  layer->copy(currentPane, *i, clipboard);
1083  }
1084 }
1085 
1086 void
1088 {
1089  pasteRelative(0);
1090 }
1091 
1092 void
1094 {
1095  sv_frame_t pos = getFrame();
1096  Clipboard &clipboard = m_viewManager->getClipboard();
1097  if (!clipboard.empty()) {
1098  sv_frame_t firstEventFrame = clipboard.getPoints()[0].getFrame();
1099  sv_frame_t offset = 0;
1100  if (firstEventFrame < 0) {
1101  offset = pos - firstEventFrame;
1102  } else if (firstEventFrame < pos) {
1103  offset = pos - firstEventFrame;
1104  } else {
1105  offset = -(firstEventFrame - pos);
1106  }
1107  pasteRelative(offset);
1108  }
1109 }
1110 
1111 void
1113 {
1114  Pane *currentPane = m_paneStack->getCurrentPane();
1115  if (!currentPane) return;
1116 
1117  Layer *layer = currentPane->getSelectedLayer();
1118 
1119  Clipboard &clipboard = m_viewManager->getClipboard();
1120 
1121  bool inCompound = false;
1122 
1123  if (!layer || !layer->isLayerEditable()) {
1124 
1125  CommandHistory::getInstance()->startCompoundOperation
1126  (tr("Paste"), true);
1127 
1128  // no suitable current layer: create one of the most
1129  // appropriate sort
1130  LayerFactory::LayerType type =
1131  LayerFactory::getInstance()->getLayerTypeForClipboardContents(clipboard);
1132  layer = m_document->createEmptyLayer(type);
1133 
1134  if (!layer) {
1135  CommandHistory::getInstance()->endCompoundOperation();
1136  return;
1137  }
1138 
1139  m_document->addLayerToView(currentPane, layer);
1140  m_paneStack->setCurrentLayer(currentPane, layer);
1141 
1142  inCompound = true;
1143  }
1144 
1145  layer->paste(currentPane, clipboard, offset, true);
1146 
1147  if (inCompound) CommandHistory::getInstance()->endCompoundOperation();
1148 }
1149 
1150 void
1152 {
1153  if (m_paneStack->getCurrentPane() &&
1154  m_paneStack->getCurrentPane()->getSelectedLayer()) {
1155 
1156  Layer *layer = m_paneStack->getCurrentPane()->getSelectedLayer();
1157 
1158  if (m_viewManager) {
1159 
1160  if (m_viewManager->getToolMode() == ViewManager::MeasureMode) {
1161 
1162  layer->deleteCurrentMeasureRect();
1163 
1164  } else {
1165 
1166  MultiSelection::SelectionList selections =
1167  m_viewManager->getSelections();
1168 
1169  for (MultiSelection::SelectionList::iterator i = selections.begin();
1170  i != selections.end(); ++i) {
1171  layer->deleteSelection(*i);
1172  }
1173  }
1174  }
1175  }
1176 }
1177 
1178 // FrameTimer method
1179 
1180 sv_frame_t
1182 {
1183  if (m_playSource && m_playSource->isPlaying()) {
1184  return m_playSource->getCurrentPlayingFrame();
1185  } else {
1186  return m_viewManager->getPlaybackFrame();
1187  }
1188 }
1189 
1190 void
1192 {
1194 }
1195 
1196 void
1198 {
1199  MultiSelection::SelectionList selections = m_viewManager->getSelections();
1200  for (MultiSelection::SelectionList::iterator i = selections.begin();
1201  i != selections.end(); ++i) {
1202  sv_frame_t start = i->getStartFrame();
1203  sv_frame_t end = i->getEndFrame();
1204  if (start != end) {
1205  insertInstantAt(start);
1206  insertInstantAt(end);
1207  }
1208  }
1209 }
1210 
1211 void
1213 {
1214  Pane *pane = m_paneStack->getCurrentPane();
1215  if (!pane) {
1216  return;
1217  }
1218 
1219  frame = pane->alignFromReference(frame);
1220 
1221  Layer *layer = dynamic_cast<TimeInstantLayer *>
1222  (pane->getSelectedLayer());
1223 
1224  if (!layer) {
1225  for (int i = pane->getLayerCount(); i > 0; --i) {
1226  layer = dynamic_cast<TimeInstantLayer *>(pane->getLayer(i - 1));
1227  if (layer) break;
1228  }
1229 
1230  if (!layer) {
1231  CommandHistory::getInstance()->startCompoundOperation
1232  (tr("Add Point"), true);
1233  layer = m_document->createEmptyLayer(LayerFactory::TimeInstants);
1234  if (layer) {
1235  m_document->addLayerToView(pane, layer);
1236  m_paneStack->setCurrentLayer(pane, layer);
1237  }
1238  CommandHistory::getInstance()->endCompoundOperation();
1239  }
1240  }
1241 
1242  if (layer) {
1243 
1244  ModelId model = layer->getModel();
1245  auto sodm = ModelById::getAs<SparseOneDimensionalModel>(model);
1246 
1247  if (sodm) {
1248  Event point(frame, "");
1249  Event prevPoint(0);
1250  bool havePrevPoint = false;
1251 
1252  ChangeEventsCommand *command =
1253  new ChangeEventsCommand(model.untyped, tr("Add Point"));
1254 
1255  if (m_labeller) {
1256 
1257  if (m_labeller->requiresPrevPoint()) {
1258 
1259  if (sodm->getNearestEventMatching
1260  (frame,
1261  [](Event) { return true; },
1262  EventSeries::Backward,
1263  prevPoint)) {
1264  havePrevPoint = true;
1265  }
1266  }
1267 
1268  m_labeller->setSampleRate(sodm->getSampleRate());
1269 
1270  Labeller::Relabelling relabelling = m_labeller->label
1271  (point, havePrevPoint ? &prevPoint : nullptr);
1272 
1273  if (relabelling.first == Labeller::AppliesToPreviousEvent) {
1274  command->remove(prevPoint);
1275  command->add(relabelling.second);
1276  } else {
1277  point = relabelling.second;
1278  }
1279  }
1280 
1281  command->add(point);
1282 
1283  command->setName(tr("Add Point at %1 s")
1284  .arg(RealTime::frame2RealTime
1285  (frame,
1286  sodm->getSampleRate())
1287  .toText(false).c_str()));
1288 
1289  Command *c = command->finish();
1290  if (c) {
1291  CommandHistory::getInstance()->addCommand(c, false);
1292  }
1293  }
1294  }
1295 }
1296 
1297 void
1299 {
1300  MultiSelection::SelectionList selections = m_viewManager->getSelections();
1301  for (MultiSelection::SelectionList::iterator i = selections.begin();
1302  i != selections.end(); ++i) {
1303  sv_frame_t start = i->getStartFrame();
1304  sv_frame_t end = i->getEndFrame();
1305  if (start < end) {
1306  insertItemAt(start, end - start);
1307  }
1308  }
1309 }
1310 
1311 void
1312 MainWindowBase::insertItemAt(sv_frame_t frame, sv_frame_t duration)
1313 {
1314  Pane *pane = m_paneStack->getCurrentPane();
1315  if (!pane) {
1316  return;
1317  }
1318 
1319  // ugh!
1320 
1321  sv_frame_t alignedStart = pane->alignFromReference(frame);
1322  sv_frame_t alignedEnd = pane->alignFromReference(frame + duration);
1323  if (alignedStart >= alignedEnd) return;
1324  sv_frame_t alignedDuration = alignedEnd - alignedStart;
1325 
1326  Command *c = nullptr;
1327 
1328  QString name = tr("Add Item at %1 s")
1329  .arg(RealTime::frame2RealTime
1330  (alignedStart,
1331  getMainModel()->getSampleRate())
1332  .toText(false).c_str());
1333 
1334  Layer *layer = pane->getSelectedLayer();
1335  if (!layer) return;
1336 
1337  ModelId modelId = layer->getModel();
1338 
1339  auto rm = ModelById::getAs<RegionModel>(modelId);
1340  if (rm) {
1341  Event point(alignedStart,
1342  rm->getValueMaximum() + 1,
1343  alignedDuration,
1344  "");
1345  ChangeEventsCommand *command = new ChangeEventsCommand
1346  (modelId.untyped, name);
1347  command->add(point);
1348  c = command->finish();
1349  }
1350 
1351  if (c) {
1352  CommandHistory::getInstance()->addCommand(c, false);
1353  return;
1354  }
1355 
1356  auto nm = ModelById::getAs<NoteModel>(modelId);
1357  if (nm) {
1358  Event point(alignedStart,
1359  nm->getValueMinimum(),
1360  alignedDuration,
1361  1.f,
1362  "");
1363  ChangeEventsCommand *command = new ChangeEventsCommand
1364  (modelId.untyped, name);
1365  command->add(point);
1366  c = command->finish();
1367  }
1368 
1369  if (c) {
1370  CommandHistory::getInstance()->addCommand(c, false);
1371  return;
1372  }
1373 }
1374 
1375 void
1377 {
1378  Pane *pane = m_paneStack->getCurrentPane();
1379  if (!pane) return;
1380 
1381  Layer *layer = dynamic_cast<TimeInstantLayer *>(pane->getSelectedLayer());
1382  if (!layer) return;
1383 
1384  MultiSelection ms(m_viewManager->getSelection());
1385 
1386  ModelId modelId = layer->getModel();
1387 
1388  auto sodm = ModelById::getAs<SparseOneDimensionalModel>(modelId);
1389  if (!sodm) return;
1390 
1391  if (!m_labeller) return;
1392 
1393  Labeller labeller(*m_labeller);
1394  labeller.setSampleRate(sodm->getSampleRate());
1395 
1396  Command *c = labeller.labelAll(modelId.untyped, &ms, sodm->getAllEvents());
1397  if (c) CommandHistory::getInstance()->addCommand(c, false);
1398 }
1399 
1400 void
1402 {
1403  Pane *pane = m_paneStack->getCurrentPane();
1404  if (!pane) return;
1405 
1406  Layer *layer = dynamic_cast<TimeInstantLayer *>(pane->getSelectedLayer());
1407  if (!layer) return;
1408 
1409  MultiSelection ms(m_viewManager->getSelection());
1410 
1411  ModelId modelId = layer->getModel();
1412 
1413  auto sodm = ModelById::getAs<SparseOneDimensionalModel>(modelId);
1414  if (!sodm) return;
1415 
1416  if (!m_labeller) return;
1417 
1418  Labeller labeller(*m_labeller);
1419  labeller.setSampleRate(sodm->getSampleRate());
1420 
1421  Command *c = labeller.subdivide(modelId.untyped, &ms, sodm->getAllEvents(), n);
1422  if (c) CommandHistory::getInstance()->addCommand(c, false);
1423 }
1424 
1425 void
1427 {
1428  Pane *pane = m_paneStack->getCurrentPane();
1429  if (!pane) return;
1430 
1431  Layer *layer = dynamic_cast<TimeInstantLayer *>(pane->getSelectedLayer());
1432  if (!layer) return;
1433 
1434  MultiSelection ms(m_viewManager->getSelection());
1435 
1436  ModelId modelId = layer->getModel();
1437 
1438  auto sodm = ModelById::getAs<SparseOneDimensionalModel>(modelId);
1439  if (!sodm) return;
1440 
1441  if (!m_labeller) return;
1442 
1443  Labeller labeller(*m_labeller);
1444  labeller.setSampleRate(sodm->getSampleRate());
1445 
1446  Command *c = labeller.winnow(modelId.untyped, &ms, sodm->getAllEvents(), n);
1447  if (c) CommandHistory::getInstance()->addCommand(c, false);
1448 }
1449 
1452 {
1453  ProgressDialog dialog(tr("Opening file or URL..."), true, 2000, this);
1454  connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash()));
1455  return open(FileSource(fileOrUrl, &dialog), mode);
1456 }
1457 
1459 MainWindowBase::open(FileSource source, AudioFileOpenMode mode)
1460 {
1461  FileOpenStatus status;
1462 
1463  if (!source.isAvailable()) return FileOpenFailed;
1464  source.waitForData();
1465 
1466  bool canImportLayer = (getMainModel() != nullptr &&
1467  m_paneStack != nullptr &&
1468  m_paneStack->getCurrentPane() != nullptr);
1469 
1470  QString extension = source.getExtension().toLower();
1471 
1472  bool rdf = (extension == "rdf" || extension == "n3" || extension == "ttl");
1473  bool audio =
1474  AudioFileReaderFactory::getKnownExtensions().contains(extension);
1475 
1476  bool rdfSession = false;
1477  if (rdf) {
1478  RDFImporter::RDFDocumentType rdfType =
1479  RDFImporter::identifyDocumentType
1480  (QUrl::fromLocalFile(source.getLocalFilename()));
1481  if (rdfType == RDFImporter::AudioRefAndAnnotations ||
1482  rdfType == RDFImporter::AudioRef) {
1483  rdfSession = true;
1484  } else if (rdfType == RDFImporter::NotRDF) {
1485  rdf = false;
1486  }
1487  }
1488 
1489  try {
1490  if (rdf) {
1491  if (rdfSession) {
1492  bool cancel = false;
1493  if (!canImportLayer || shouldCreateNewSessionForRDFAudio(&cancel)) {
1494  return openSession(source);
1495  } else if (cancel) {
1496  return FileOpenCancelled;
1497  } else {
1498  return openLayer(source);
1499  }
1500  } else {
1501  if ((status = openSession(source)) != FileOpenFailed) {
1502  return status;
1503  } else if (!canImportLayer) {
1504  return FileOpenWrongMode;
1505  } else if ((status = openLayer(source)) != FileOpenFailed) {
1506  return status;
1507  } else {
1508  return FileOpenFailed;
1509  }
1510  }
1511  }
1512 
1513  if (audio) {
1514  return openAudio(source, mode);
1515  } else if ((status = openSession(source)) != FileOpenFailed) {
1516  return status;
1517  } else if ((status = openPlaylist(source, mode)) != FileOpenFailed) {
1518  return status;
1519  } else if (!canImportLayer) {
1520  // We already checked whether the file is RDF: we know
1521  // it's not. But if it's another format that might be
1522  // supported as a layer, reply that we can't open a layer
1523  // here - otherwise assume it's an unknown file format
1524  if (ImageLayer::isImageFileSupported(source.getLocation())) {
1525  return FileOpenWrongMode;
1526  }
1527  if (extension == "mid" || extension == "midi") {
1528  return FileOpenWrongMode;
1529  }
1530  if (TextTest::isApparentTextDocument(source)) {
1531  return FileOpenWrongMode;
1532  }
1533  return FileOpenFailed;
1534  } else if ((status = openImage(source)) != FileOpenFailed) {
1535  return status;
1536  } else if ((status = openLayer(source)) != FileOpenFailed) {
1537  return status;
1538  } else {
1539  return FileOpenFailed;
1540  }
1541  } catch (const InsufficientDiscSpace &e) {
1542  emit hideSplash();
1543  m_openingAudioFile = false;
1544  SVCERR << "MainWindowBase: Caught InsufficientDiscSpace in file open" << endl;
1545  QMessageBox::critical
1546  (this, tr("Not enough disc space"),
1547  tr("<b>Not enough disc space</b><p>There doesn't appear to be enough spare disc space to accommodate any necessary temporary files.</p><p>Please clear some space and try again.</p>").arg(e.what()));
1548  return FileOpenFailed;
1549  } catch (const std::bad_alloc &e) { // reader may have rethrown this after cleaning up
1550  emit hideSplash();
1551  m_openingAudioFile = false;
1552  SVCERR << "MainWindowBase: Caught bad_alloc in file open" << endl;
1553  QMessageBox::critical
1554  (this, tr("Not enough memory"),
1555  tr("<b>Not enough memory</b><p>There doesn't appear to be enough memory to accommodate any necessary temporary data.</p>"));
1556  return FileOpenFailed;
1557  }
1558 }
1559 
1561 MainWindowBase::openAudio(FileSource source,
1562  AudioFileOpenMode mode,
1563  QString templateName)
1564 {
1565  SVDEBUG << "MainWindowBase::openAudio(" << source.getLocation() << ") with mode " << mode << " and template " << templateName << endl;
1566 
1567  if (templateName == "") {
1568  templateName = getDefaultSessionTemplate();
1569  SVDEBUG << "(Default template is: \"" << templateName << "\")" << endl;
1570  }
1571 
1572 // cerr << "template is: \"" << templateName << "\"" << endl;
1573 
1574  if (!source.isAvailable()) {
1575  if (source.wasCancelled()) {
1576  return FileOpenCancelled;
1577  } else {
1578  return FileOpenFailed;
1579  }
1580  }
1581 
1582  m_openingAudioFile = true;
1583 
1584  source.waitForData();
1585 
1586  sv_samplerate_t rate = 0;
1587 
1588  SVDEBUG << "Checking whether to preserve incoming audio file's sample rate"
1589  << endl;
1590 
1591  if (Preferences::getInstance()->getFixedSampleRate() != 0) {
1592  rate = Preferences::getInstance()->getFixedSampleRate();
1593  SVDEBUG << "No: preferences specify fixed rate of " << rate << endl;
1594  } else if (Preferences::getInstance()->getResampleOnLoad()) {
1595  if (getMainModel()) {
1596  if (mode == ReplaceSession || mode == ReplaceMainModel) {
1597  SVDEBUG << "Preferences specify resampling additional models to match main model, but we are opening this file to replace the main model according to the open mode: therefore..." << endl;
1598  } else {
1599  rate = getMainModel()->getSampleRate();
1600  SVDEBUG << "No: preferences specify resampling to match main model, whose rate is currently " << rate << endl;
1601  }
1602  }
1603  }
1604 
1605  if (rate == 0) {
1606  SVDEBUG << "Yes, preserving incoming file rate" << endl;
1607  }
1608 
1609  auto newModel = std::make_shared<ReadOnlyWaveFileModel>(source, rate);
1610  if (!newModel->isOK()) {
1611  m_openingAudioFile = false;
1612  if (source.wasCancelled()) {
1613  return FileOpenCancelled;
1614  } else {
1615  return FileOpenFailed;
1616  }
1617  }
1618 
1619  auto newModelId = ModelById::add(newModel);
1620  auto status = addOpenedAudioModel
1621  (source, newModelId, mode, templateName, true);
1622  m_openingAudioFile = false;
1623  return status;
1624 }
1625 
1628  ModelId newModel,
1629  AudioFileOpenMode mode,
1630  QString templateName,
1631  bool registerSource)
1632 {
1633  if (mode == AskUser) {
1634  if (getMainModel()) {
1635 
1636  QSettings settings;
1637  settings.beginGroup("MainWindow");
1638  int lastMode = settings.value("lastaudioopenmode", 0).toBool();
1639  settings.endGroup();
1640  int imode = 0;
1641 
1642  QStringList items;
1643  items << tr("Close the current session and start a new one")
1644  << tr("Replace the main audio file in this session")
1645  << tr("Add the audio file to this session");
1646 
1647  bool ok = false;
1648  QString item = ListInputDialog::getItem
1649  (this, tr("Select target for import"),
1650  tr("<b>Select a target for import</b><p>You already have an audio file loaded.<br>What would you like to do with the new audio file?"),
1651  items, lastMode, &ok);
1652 
1653  if (!ok || item.isEmpty()) {
1654  ModelById::release(newModel);
1655  m_openingAudioFile = false;
1656  return FileOpenCancelled;
1657  }
1658 
1659  for (int i = 0; i < items.size(); ++i) {
1660  if (item == items[i]) imode = i;
1661  }
1662 
1663  settings.beginGroup("MainWindow");
1664  settings.setValue("lastaudioopenmode", imode);
1665  settings.endGroup();
1666 
1667  mode = (AudioFileOpenMode)imode;
1668 
1669  } else {
1670  // no main model: make a new session
1671  mode = ReplaceSession;
1672  }
1673  }
1674 
1675  if (mode == ReplaceCurrentPane) {
1676 
1677  Pane *pane = m_paneStack->getCurrentPane();
1678  if (pane) {
1679  if (getMainModel()) {
1680  View::ModelSet models(pane->getModels());
1681  if (models.find(getMainModelId()) != models.end()) {
1682  // Current pane contains main model: replace that
1683  mode = ReplaceMainModel;
1684  }
1685  // Otherwise the current pane has a non-default model,
1686  // which we will deal with later
1687  } else {
1688  // We have no main model, so start a new session with
1689  // optional template
1690  mode = ReplaceSession;
1691  }
1692  } else {
1693  // We seem to have no current pane! Oh well
1694  mode = CreateAdditionalModel;
1695  }
1696  }
1697 
1698  if (mode != ReplaceSession && !m_document) {
1699  SVDEBUG << "File open mode requested is something other than ReplaceSession, but we have no document at all yet, so we must use ReplaceSession mode" << endl;
1700  mode = ReplaceSession;
1701  }
1702 
1703  if (mode == CreateAdditionalModel && getMainModelId().isNone()) {
1704  SVDEBUG << "File open mode requested is CreateAdditionalModel, but we have no main model, so switching to ReplaceSession mode" << endl;
1705  mode = ReplaceSession;
1706  }
1707 
1708  bool loadedTemplate = false;
1709 
1710  if (mode == ReplaceSession) {
1711 
1712  if (!checkSaveModified()) {
1713  m_openingAudioFile = false;
1714  return FileOpenCancelled;
1715  }
1716 
1717  SVDEBUG << "SV looking for template " << templateName << endl;
1718  if (templateName != "") {
1719  FileOpenStatus tplStatus = openSessionTemplate(templateName);
1720  if (tplStatus == FileOpenCancelled) {
1721  SVDEBUG << "Template load cancelled" << endl;
1722  m_openingAudioFile = false;
1723  return FileOpenCancelled;
1724  }
1725  if (tplStatus != FileOpenFailed) {
1726  SVDEBUG << "Template load succeeded" << endl;
1727  loadedTemplate = true;
1728  }
1729  }
1730 
1731  if (!loadedTemplate) {
1732  SVDEBUG << "No template found: closing session, creating new empty document" << endl;
1733  closeSession();
1734  createDocument();
1735  }
1736 
1737  SVDEBUG << "Now switching to ReplaceMainModel mode" << endl;
1738  mode = ReplaceMainModel;
1739  }
1740 
1741  emit activity(tr("Import audio file \"%1\"").arg(source.getLocation()));
1742 
1743  if (mode == ReplaceMainModel) {
1744 
1745  ModelId prevMain = getMainModelId();
1746  if (!prevMain.isNone()) {
1747  m_playSource->removeModel(prevMain);
1748  }
1749 
1750  SVDEBUG << "SV about to call setMainModel(" << newModel << "): prevMain is " << prevMain << endl;
1751 
1752  m_document->setMainModel(newModel);
1753 
1754  setupMenus();
1755 
1756  m_originalLocation = source.getLocation();
1757 
1758  if (loadedTemplate || (m_sessionFile == "")) {
1759  CommandHistory::getInstance()->clear();
1760  CommandHistory::getInstance()->documentSaved();
1761  m_documentModified = false;
1762  } else {
1763  if (m_documentModified) {
1764  m_documentModified = false;
1765  }
1766  }
1767 
1768  if (!source.isRemote() && registerSource) {
1769  m_audioFile = source.getLocalFilename();
1770  }
1771 
1773 
1774  } else if (mode == CreateAdditionalModel) {
1775 
1776  SVCERR << "Mode is CreateAdditionalModel" << endl;
1777 
1778  CommandHistory::getInstance()->startCompoundOperation
1779  (tr("Import \"%1\"").arg(source.getBasename()), true);
1780 
1781  m_document->addNonDerivedModel(newModel);
1782 
1783  AddPaneCommand *command = new AddPaneCommand(this);
1784  CommandHistory::getInstance()->addCommand(command);
1785 
1786  Pane *pane = command->getPane();
1787 
1788  if (m_timeRulerLayer) {
1789  SVCERR << "Have time ruler, adding it" << endl;
1791  } else {
1792  SVCERR << "Do not have time ruler" << endl;
1793  }
1794 
1795  Layer *newLayer = m_document->createImportedLayer(newModel);
1796 
1797  if (newLayer) {
1798  m_document->addLayerToView(pane, newLayer);
1799  }
1800 
1801  CommandHistory::getInstance()->endCompoundOperation();
1802 
1803  } else if (mode == ReplaceCurrentPane) {
1804 
1805  // We know there is a current pane, otherwise we would have
1806  // reset the mode to CreateAdditionalModel above; and we know
1807  // the current pane does not contain the main model, otherwise
1808  // we would have reset it to ReplaceMainModel. But we don't
1809  // know whether the pane contains a waveform model at all.
1810 
1811  Pane *pane = m_paneStack->getCurrentPane();
1812  Layer *replace = nullptr;
1813 
1814  for (int i = 0; i < pane->getLayerCount(); ++i) {
1815  Layer *layer = pane->getLayer(i);
1816  if (dynamic_cast<WaveformLayer *>(layer)) {
1817  replace = layer;
1818  break;
1819  }
1820  }
1821 
1822  CommandHistory::getInstance()->startCompoundOperation
1823  (tr("Import \"%1\"").arg(source.getBasename()), true);
1824 
1825  m_document->addNonDerivedModel(newModel);
1826 
1827  if (replace) {
1828  m_document->removeLayerFromView(pane, replace);
1829  }
1830 
1831  Layer *newLayer = m_document->createImportedLayer(newModel);
1832 
1833  if (newLayer) {
1834  m_document->addLayerToView(pane, newLayer);
1835  }
1836 
1837  CommandHistory::getInstance()->endCompoundOperation();
1838  }
1839 
1840  updateMenuStates();
1841 
1842  if (registerSource) {
1843  m_recentFiles.addFile(source.getLocation());
1844  }
1845  if (!source.isRemote() && registerSource) {
1846  // for file dialog
1847  registerLastOpenedFilePath(FileFinder::AudioFile,
1848  source.getLocalFilename());
1849  }
1850 
1851  m_openingAudioFile = false;
1852 
1853  currentPaneChanged(m_paneStack->getCurrentPane());
1854 
1855  emit audioFileLoaded();
1856 
1857  return FileOpenSucceeded;
1858 }
1859 
1862 {
1863  SVDEBUG << "MainWindowBase::openPlaylist(" << source.getLocation() << ")" << endl;
1864 
1865  std::set<QString> extensions;
1866  PlaylistFileReader::getSupportedExtensions(extensions);
1867  QString extension = source.getExtension().toLower();
1868  if (extensions.find(extension) == extensions.end()) return FileOpenFailed;
1869 
1870  if (!source.isAvailable()) return FileOpenFailed;
1871  source.waitForData();
1872 
1873  PlaylistFileReader reader(source.getLocalFilename());
1874  if (!reader.isOK()) return FileOpenFailed;
1875 
1876  PlaylistFileReader::Playlist playlist = reader.load();
1877 
1878  bool someSuccess = false;
1879 
1880  for (PlaylistFileReader::Playlist::const_iterator i = playlist.begin();
1881  i != playlist.end(); ++i) {
1882 
1883  ProgressDialog dialog(tr("Opening playlist..."), true, 2000, this);
1884  connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash()));
1885  FileOpenStatus status = openAudio(FileSource(*i, &dialog), mode);
1886 
1887  if (status == FileOpenCancelled) {
1888  return FileOpenCancelled;
1889  }
1890 
1891  if (status == FileOpenSucceeded) {
1892  someSuccess = true;
1893  mode = CreateAdditionalModel;
1894  }
1895  }
1896 
1897  if (someSuccess) return FileOpenSucceeded;
1898  else return FileOpenFailed;
1899 }
1900 
1902 MainWindowBase::openLayer(FileSource source)
1903 {
1904  SVDEBUG << "MainWindowBase::openLayer(" << source.getLocation() << ")" << endl;
1905 
1906  Pane *pane = m_paneStack->getCurrentPane();
1907 
1908  if (!pane) {
1909  // shouldn't happen, as the menu action should have been disabled
1910  cerr << "WARNING: MainWindowBase::openLayer: no current pane" << endl;
1911  return FileOpenWrongMode;
1912  }
1913 
1914  if (!getMainModel()) {
1915  // shouldn't happen, as the menu action should have been disabled
1916  cerr << "WARNING: MainWindowBase::openLayer: No main model -- hence no default sample rate available" << endl;
1917  return FileOpenWrongMode;
1918  }
1919 
1920  if (!source.isAvailable()) return FileOpenFailed;
1921  source.waitForData();
1922 
1923  QString path = source.getLocalFilename();
1924 
1925  RDFImporter::RDFDocumentType rdfType =
1926  RDFImporter::identifyDocumentType(QUrl::fromLocalFile(path));
1927 
1928 // cerr << "RDF type: (in layer) " << (int) rdfType << endl;
1929 
1930  if (rdfType != RDFImporter::NotRDF) {
1931 
1932  return openLayersFromRDF(source);
1933 
1934  } else if (source.getExtension().toLower() == "svl" ||
1935  (source.getExtension().toLower() == "xml" &&
1936  (SVFileReader::identifyXmlFile(source.getLocalFilename())
1938 
1939  PaneCallback callback(this);
1940  QFile file(path);
1941 
1942  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1943  cerr << "ERROR: MainWindowBase::openLayer("
1944  << source.getLocation()
1945  << "): Failed to open file for reading" << endl;
1946  return FileOpenFailed;
1947  }
1948 
1949  SVFileReader reader(m_document, callback, source.getLocation());
1950  connect
1951  (&reader, SIGNAL(modelRegenerationFailed(QString, QString, QString)),
1952  this, SLOT(modelRegenerationFailed(QString, QString, QString)));
1953  connect
1954  (&reader, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
1955  this, SLOT(modelRegenerationWarning(QString, QString, QString)));
1956  reader.setCurrentPane(pane);
1957 
1958  QXmlInputSource inputSource(&file);
1959  reader.parse(inputSource);
1960 
1961  if (!reader.isOK()) {
1962  cerr << "ERROR: MainWindowBase::openLayer("
1963  << source.getLocation()
1964  << "): Failed to read XML file: "
1965  << reader.getErrorString() << endl;
1966  return FileOpenFailed;
1967  }
1968 
1969  emit activity(tr("Import layer XML file \"%1\"").arg(source.getLocation()));
1970 
1971  m_recentFiles.addFile(source.getLocation());
1972 
1973  if (!source.isRemote()) {
1974  registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog
1975  }
1976 
1977  return FileOpenSucceeded;
1978 
1979  } else {
1980 
1981  try {
1982 
1983  MIDIFileImportDialog midiDlg(this);
1984 
1985  Model *newModelPtr = DataFileReaderFactory::loadNonCSV
1986  (path, &midiDlg, getMainModel()->getSampleRate());
1987 
1988  if (!newModelPtr) {
1989  CSVFormatDialog *dialog =
1990  new CSVFormatDialog(this,
1991  path,
1992  getMainModel()->getSampleRate(),
1993  5);
1994  if (dialog->exec() == QDialog::Accepted) {
1995  newModelPtr = DataFileReaderFactory::loadCSV
1996  (path, dialog->getFormat(),
1997  getMainModel()->getSampleRate());
1998  }
1999  delete dialog;
2000  }
2001 
2002  if (newModelPtr) {
2003 
2004  SVDEBUG << "MainWindowBase::openLayer: Have model" << endl;
2005 
2006  emit activity(tr("Import MIDI file \"%1\"").arg(source.getLocation()));
2007 
2008  ModelId modelId =
2009  ModelById::add(std::shared_ptr<Model>(newModelPtr));
2010 
2011  Layer *newLayer = m_document->createImportedLayer(modelId);
2012 
2013  if (newLayer) {
2014 
2015  m_document->addLayerToView(pane, newLayer);
2016  m_paneStack->setCurrentLayer(pane, newLayer);
2017 
2018  m_recentFiles.addFile(source.getLocation());
2019 
2020  if (!source.isRemote()) {
2022  (FileFinder::LayerFile,
2023  path); // for file dialog
2024  }
2025 
2026  return FileOpenSucceeded;
2027  }
2028  }
2029  } catch (DataFileReaderFactory::Exception e) {
2030  if (e == DataFileReaderFactory::ImportCancelled) {
2031  return FileOpenCancelled;
2032  }
2033  }
2034  }
2035 
2036  return FileOpenFailed;
2037 }
2038 
2040 MainWindowBase::openImage(FileSource source)
2041 {
2042  SVDEBUG << "MainWindowBase::openImage(" << source.getLocation() << ")" << endl;
2043 
2044  Pane *pane = m_paneStack->getCurrentPane();
2045 
2046  if (!pane) {
2047  // shouldn't happen, as the menu action should have been disabled
2048  cerr << "WARNING: MainWindowBase::openImage: no current pane" << endl;
2049  return FileOpenWrongMode;
2050  }
2051 
2052  if (!getMainModel()) {
2053  return FileOpenWrongMode;
2054  }
2055 
2056  bool newLayer = false;
2057 
2058  ImageLayer *il = dynamic_cast<ImageLayer *>(pane->getSelectedLayer());
2059  if (!il) {
2060  for (int i = pane->getLayerCount()-1; i >= 0; --i) {
2061  il = dynamic_cast<ImageLayer *>(pane->getLayer(i));
2062  if (il) break;
2063  }
2064  }
2065  if (!il) {
2066  il = dynamic_cast<ImageLayer *>
2067  (m_document->createEmptyLayer(LayerFactory::Image));
2068  if (!il) return FileOpenFailed;
2069  newLayer = true;
2070  }
2071 
2072  // We don't put the image file in Recent Files
2073 
2074  SVCERR << "openImage: trying location \"" << source.getLocation() << "\" in image layer" << endl;
2075 
2076  if (!il->addImage(m_viewManager->getGlobalCentreFrame(), source.getLocation())) {
2077  if (newLayer) {
2078  m_document->deleteLayer(il); // also releases its model
2079  }
2080  return FileOpenFailed;
2081  } else {
2082  if (newLayer) {
2083  m_document->addLayerToView(pane, il);
2084  }
2085  m_paneStack->setCurrentLayer(pane, il);
2086  }
2087 
2088  return FileOpenSucceeded;
2089 }
2090 
2093 {
2094  QDir dir(dirPath);
2095  QStringList files = dir.entryList(QDir::Files | QDir::Readable);
2096  files.sort();
2097 
2098  FileOpenStatus status = FileOpenFailed;
2099  bool first = true;
2100  bool cancelled = false;
2101 
2102  foreach (QString file, files) {
2103 
2104  FileSource source(dir.filePath(file));
2105  if (!source.isAvailable()) {
2106  continue;
2107  }
2108 
2109  if (AudioFileReaderFactory::getKnownExtensions().contains
2110  (source.getExtension().toLower())) {
2111 
2113  if (first) mode = ReplaceSession;
2114 
2115  switch (openAudio(source, mode)) {
2116  case FileOpenSucceeded:
2117  status = FileOpenSucceeded;
2118  first = false;
2119  break;
2120  case FileOpenFailed:
2121  break;
2122  case FileOpenCancelled:
2123  cancelled = true;
2124  break;
2125  case FileOpenWrongMode:
2126  break;
2127  }
2128  }
2129 
2130  if (cancelled) break;
2131  }
2132 
2133  return status;
2134 }
2135 
2138 {
2139  ProgressDialog dialog(tr("Opening session..."), true, 2000, this);
2140  connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash()));
2141  return openSession(FileSource(fileOrUrl, &dialog));
2142 }
2143 
2146 {
2147  SVDEBUG << "MainWindowBase::openSession(" << source.getLocation() << ")" << endl;
2148 
2149  if (!source.isAvailable()) return FileOpenFailed;
2150  source.waitForData();
2151 
2152  QString sessionExt =
2153  InteractiveFileFinder::getInstance()->getApplicationSessionExtension();
2154 
2155  QString extension = source.getExtension().toLower();
2156 
2157  bool rdf = (extension == "rdf" || extension == "n3" || extension == "ttl");
2158 
2159  if (extension != sessionExt) {
2160 
2161  if (rdf) {
2162 
2163  RDFImporter::RDFDocumentType rdfType =
2164  RDFImporter::identifyDocumentType
2165  (QUrl::fromLocalFile(source.getLocalFilename()));
2166 
2167 // cerr << "RDF type: " << (int)rdfType << endl;
2168 
2169  if (rdfType == RDFImporter::AudioRefAndAnnotations ||
2170  rdfType == RDFImporter::AudioRef) {
2171  return openSessionFromRDF(source);
2172  } else if (rdfType != RDFImporter::NotRDF) {
2173  return FileOpenFailed;
2174  }
2175 
2176  } else if (extension == "xml") {
2177 
2178  if (SVFileReader::identifyXmlFile(source.getLocalFilename()) ==
2180  cerr << "This XML file looks like a session file, attempting to open it as a session" << endl;
2181  } else {
2182  return FileOpenFailed;
2183  }
2184  } else {
2185  return FileOpenFailed;
2186  }
2187  }
2188 
2189  QXmlInputSource *inputSource = nullptr;
2190  BZipFileDevice *bzFile = nullptr;
2191  QFile *rawFile = nullptr;
2192 
2193  if (extension == sessionExt) {
2194  bzFile = new BZipFileDevice(source.getLocalFilename());
2195  if (!bzFile->open(QIODevice::ReadOnly)) {
2196  delete bzFile;
2197  return FileOpenFailed;
2198  }
2199  inputSource = new QXmlInputSource(bzFile);
2200  } else {
2201  rawFile = new QFile(source.getLocalFilename());
2202  inputSource = new QXmlInputSource(rawFile);
2203  }
2204 
2205  if (!checkSaveModified()) {
2206  if (bzFile) bzFile->close();
2207  delete inputSource;
2208  delete bzFile;
2209  delete rawFile;
2210  return FileOpenCancelled;
2211  }
2212 
2213  QString error;
2214  closeSession();
2215  createDocument();
2216 
2217  PaneCallback callback(this);
2218  m_viewManager->clearSelections();
2219 
2220  SVFileReader reader(m_document, callback, source.getLocation());
2221  connect
2222  (&reader, SIGNAL(modelRegenerationFailed(QString, QString, QString)),
2223  this, SLOT(modelRegenerationFailed(QString, QString, QString)));
2224  connect
2225  (&reader, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
2226  this, SLOT(modelRegenerationWarning(QString, QString, QString)));
2227 
2228  reader.parse(*inputSource);
2229 
2230  if (!reader.isOK()) {
2231  error = tr("SV XML file read error:\n%1").arg(reader.getErrorString());
2232  }
2233 
2234  if (bzFile) bzFile->close();
2235 
2236  delete inputSource;
2237  delete bzFile;
2238  delete rawFile;
2239 
2240  bool ok = (error == "");
2241 
2242  if (ok) {
2243 
2244  emit activity(tr("Import session file \"%1\"").arg(source.getLocation()));
2245 
2246  if (!source.isRemote() && !m_document->isIncomplete()) {
2247  // Setting the session file path enables the Save (as
2248  // opposed to Save As...) option. We can't do this if we
2249  // don't have a local path to save to, but we also don't
2250  // want to do it if we failed to find an audio file or
2251  // similar on load, as the audio reference would then end
2252  // up being lost from any saved or auto-saved-on-exit copy
2253  m_sessionFile = source.getLocalFilename();
2254  } else {
2255  QMessageBox::warning
2256  (this,
2257  tr("Incomplete session loaded"),
2258  tr("Some of the audio content referred to by the original session file could not be loaded.\nIf you save this session, it will be saved without any reference to that audio, and information may be lost."),
2259  QMessageBox::Ok);
2260  }
2261 
2263  setupMenus();
2265 
2266  CommandHistory::getInstance()->clear();
2267  CommandHistory::getInstance()->documentSaved();
2268  m_documentModified = false;
2269  updateMenuStates();
2270 
2271  m_recentFiles.addFile(source.getLocation());
2272 
2273  if (!source.isRemote()) {
2274  // for file dialog
2275  registerLastOpenedFilePath(FileFinder::SessionFile,
2276  source.getLocalFilename());
2277  }
2278 
2279  m_originalLocation = source.getLocation();
2280 
2281  emit sessionLoaded();
2282 
2284  }
2285 
2286  return ok ? FileOpenSucceeded : FileOpenFailed;
2287 }
2288 
2291 {
2292  // Template in the user's template directory takes
2293  // priority over a bundled one; we don't unbundle, but
2294  // open directly from the bundled file (where applicable)
2295  ResourceFinder rf;
2296  QString tfile = rf.getResourcePath("templates", templateName + ".svt");
2297  if (tfile != "") {
2298  cerr << "SV loading template file " << tfile << endl;
2299  return openSessionTemplate(FileSource("file:" + tfile));
2300  } else {
2301  return FileOpenFailed;
2302  }
2303 }
2304 
2307 {
2308  cerr << "MainWindowBase::openSessionTemplate(" << source.getLocation() << ")" << endl;
2309 
2310  if (!source.isAvailable()) return FileOpenFailed;
2311  source.waitForData();
2312 
2313  QXmlInputSource *inputSource = nullptr;
2314  QFile *file = nullptr;
2315 
2316  file = new QFile(source.getLocalFilename());
2317  inputSource = new QXmlInputSource(file);
2318 
2319  if (!checkSaveModified()) {
2320  delete inputSource;
2321  delete file;
2322  return FileOpenCancelled;
2323  }
2324 
2325  QString error;
2326  closeSession();
2327  createDocument();
2328 
2329  PaneCallback callback(this);
2330  m_viewManager->clearSelections();
2331 
2332  SVFileReader reader(m_document, callback, source.getLocation());
2333  connect
2334  (&reader, SIGNAL(modelRegenerationFailed(QString, QString, QString)),
2335  this, SLOT(modelRegenerationFailed(QString, QString, QString)));
2336  connect
2337  (&reader, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
2338  this, SLOT(modelRegenerationWarning(QString, QString, QString)));
2339 
2340  reader.parse(*inputSource);
2341 
2342  if (!reader.isOK()) {
2343  error = tr("SV XML file read error:\n%1").arg(reader.getErrorString());
2344  }
2345 
2346  delete inputSource;
2347  delete file;
2348 
2349  bool ok = (error == "");
2350 
2351  if (ok) {
2352 
2353  emit activity(tr("Open session template \"%1\"").arg(source.getLocation()));
2354 
2355  setupMenus();
2357 
2358  CommandHistory::getInstance()->clear();
2359  CommandHistory::getInstance()->documentSaved();
2360  m_documentModified = false;
2361  updateMenuStates();
2362 
2363  emit sessionLoaded();
2364  }
2365 
2367 
2368  return ok ? FileOpenSucceeded : FileOpenFailed;
2369 }
2370 
2373 {
2374  SVDEBUG << "MainWindowBase::openSessionFromRDF(" << source.getLocation() << ")" << endl;
2375 
2376  if (!source.isAvailable()) return FileOpenFailed;
2377  source.waitForData();
2378 
2379  if (!checkSaveModified()) {
2380  return FileOpenCancelled;
2381  }
2382 
2383  closeSession();
2384  createDocument();
2385 
2386  FileOpenStatus status = openLayersFromRDF(source);
2387 
2388  setupMenus();
2390 
2391  CommandHistory::getInstance()->clear();
2392  CommandHistory::getInstance()->documentSaved();
2393  m_documentModified = false;
2395 
2396  emit sessionLoaded();
2397 
2398  return status;
2399 }
2400 
2403 {
2404  sv_samplerate_t rate = 0;
2405 
2406  SVDEBUG << "MainWindowBase::openLayersFromRDF" << endl;
2407 
2408  ProgressDialog dialog(tr("Importing from RDF..."), true, 2000, this);
2409  connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash()));
2410 
2411  if (getMainModel()) {
2412  rate = getMainModel()->getSampleRate();
2413  } else if (Preferences::getInstance()->getResampleOnLoad()) {
2414  if (getMainModel()) {
2415  rate = getMainModel()->getSampleRate();
2416  }
2417  }
2418 
2419  RDFImporter importer
2420  (QUrl::fromLocalFile(source.getLocalFilename()).toString(), rate);
2421 
2422  if (!importer.isOK()) {
2423  if (importer.getErrorString() != "") {
2424  QMessageBox::critical
2425  (this, tr("Failed to import RDF"),
2426  tr("<b>Failed to import RDF</b><p>Importing data from RDF document at \"%1\" failed: %2</p>")
2427  .arg(source.getLocation()).arg(importer.getErrorString()));
2428  }
2429  return FileOpenFailed;
2430  }
2431 
2432  std::vector<ModelId> modelIds = importer.getDataModels(&dialog);
2433 
2434  dialog.setMessage(tr("Importing from RDF..."));
2435 
2436  if (modelIds.empty()) {
2437  QMessageBox::critical
2438  (this, tr("Failed to import RDF"),
2439  tr("<b>Failed to import RDF</b><p>No suitable data models found for import from RDF document at \"%1\"</p>").arg(source.getLocation()));
2440  return FileOpenFailed;
2441  }
2442 
2443  emit activity(tr("Import RDF document \"%1\"").arg(source.getLocation()));
2444 
2445  std::set<ModelId> added;
2446 
2447  for (auto modelId: modelIds) {
2448 
2449  if (ModelById::isa<WaveFileModel>(modelId)) {
2450 
2451  Pane *pane = addPaneToStack();
2452  Layer *layer = nullptr;
2453 
2454  if (m_timeRulerLayer) {
2456  }
2457 
2458  if (!getMainModel()) {
2459  m_document->setMainModel(modelId);
2460  layer = m_document->createMainModelLayer(LayerFactory::Waveform);
2461  } else {
2462  layer = m_document->createImportedLayer(modelId);
2463  }
2464 
2465  m_document->addLayerToView(pane, layer);
2466 
2467  added.insert(modelId);
2468 
2469  for (auto otherId: modelIds) {
2470 
2471  if (otherId == modelId) continue;
2472 
2473  bool isDependent = false;
2474  if (auto dm = ModelById::get(otherId)) {
2475  if (dm->getSourceModel() == modelId) {
2476  isDependent = true;
2477  }
2478  }
2479  if (!isDependent) continue;
2480 
2481  layer = m_document->createImportedLayer(otherId);
2482 
2483  if (layer->isLayerOpaque() ||
2484  dynamic_cast<Colour3DPlotLayer *>(layer)) {
2485 
2486  // these always go in a new pane, with nothing
2487  // else going in the same pane
2488 
2489  Pane *singleLayerPane = addPaneToStack();
2490  if (m_timeRulerLayer) {
2491  m_document->addLayerToView(singleLayerPane, m_timeRulerLayer);
2492  }
2493  m_document->addLayerToView(singleLayerPane, layer);
2494 
2495  } else if (layer->getLayerColourSignificance() ==
2496  Layer::ColourHasMeaningfulValue) {
2497 
2498  // these can go in a pane with something else, but
2499  // only if none of the something elses also have
2500  // this quality
2501 
2502  bool needNewPane = false;
2503  for (int i = 0; i < pane->getLayerCount(); ++i) {
2504  Layer *otherLayer = pane->getLayer(i);
2505  if (otherLayer &&
2506  (otherLayer->getLayerColourSignificance() ==
2507  Layer::ColourHasMeaningfulValue)) {
2508  needNewPane = true;
2509  break;
2510  }
2511  }
2512  if (needNewPane) {
2513  pane = addPaneToStack();
2514  }
2515 
2516  m_document->addLayerToView(pane, layer);
2517 
2518  } else {
2519 
2520  if (pane->getLayerCount() > 4) {
2521  pane = addPaneToStack();
2522  }
2523 
2524  m_document->addLayerToView(pane, layer);
2525  }
2526 
2527  added.insert(otherId);
2528  }
2529  }
2530  }
2531 
2532  for (auto modelId : modelIds) {
2533 
2534  if (added.find(modelId) == added.end()) {
2535 
2536  Layer *layer = m_document->createImportedLayer(modelId);
2537  if (!layer) return FileOpenFailed;
2538 
2539  Pane *singleLayerPane = addPaneToStack();
2540  if (m_timeRulerLayer) {
2541  m_document->addLayerToView(singleLayerPane, m_timeRulerLayer);
2542  }
2543  m_document->addLayerToView(singleLayerPane, layer);
2544  }
2545  }
2546 
2547  m_recentFiles.addFile(source.getLocation());
2548  return FileOpenSucceeded;
2549 }
2550 
2551 class AudioLogCallback : public breakfastquay::AudioFactory::LogCallback
2552 {
2553 public:
2554  void log(std::string message) const override {
2555  SVDEBUG << message << endl;
2556  }
2557 };
2558 
2559 void
2561 {
2562  if (m_playTarget || m_audioIO) return;
2563 
2564  static AudioLogCallback audioLogCallback;
2565  breakfastquay::AudioFactory::setLogCallback(&audioLogCallback);
2566 
2567  if (m_audioMode == AUDIO_NONE) return;
2568 
2569  QSettings settings;
2570  settings.beginGroup("Preferences");
2571  QString implementation = settings.value
2572  ("audio-target", "").toString();
2573  QString suffix;
2574  if (implementation != "") suffix = "-" + implementation;
2575  QString recordDevice = settings.value
2576  ("audio-record-device" + suffix, "").toString();
2577  QString playbackDevice = settings.value
2578  ("audio-playback-device" + suffix, "").toString();
2579  settings.endGroup();
2580 
2581  if (implementation == "auto") {
2582  implementation = "";
2583  }
2584 
2585  breakfastquay::AudioFactory::Preference preference;
2586  preference.implementation = implementation.toStdString();
2587  preference.recordDevice = recordDevice.toStdString();
2588  preference.playbackDevice = playbackDevice.toStdString();
2589 
2590  SVCERR << "createAudioIO: Preferred implementation = \""
2591  << preference.implementation << "\"" << endl;
2592  SVCERR << "createAudioIO: Preferred playback device = \""
2593  << preference.playbackDevice << "\"" << endl;
2594  SVCERR << "createAudioIO: Preferred record device = \""
2595  << preference.recordDevice << "\"" << endl;
2596 
2597  breakfastquay::ApplicationPlaybackSource *source =
2598  m_playSource->getApplicationPlaybackSource();
2599 
2600  std::string errorString;
2601 
2603  m_audioIO = breakfastquay::AudioFactory::
2604  createCallbackIO(m_recordTarget, source, preference, errorString);
2605  if (m_audioIO) {
2606  SVDEBUG << "MainWindowBase::createAudioIO: Suspending on creation" << endl;
2607  m_audioIO->suspend(); // start in suspended state
2608  m_playSource->setSystemPlaybackTarget(m_audioIO);
2609  } else {
2610  // Failed to create audio I/O; this may just mean there is
2611  // no record device, so fall through to see what happens
2612  // next. We only report complete failure if we end up with
2613  // neither m_audioIO nor m_playTarget.
2614  }
2615  }
2616 
2617  if (!m_audioIO) {
2618  m_playTarget = breakfastquay::AudioFactory::
2619  createCallbackPlayTarget(source, preference, errorString);
2620  if (m_playTarget) {
2621  SVDEBUG << "MainWindowBase::createAudioIO: Suspending on creation" << endl;
2622  m_playTarget->suspend(); // start in suspended state
2623  m_playSource->setSystemPlaybackTarget(m_playTarget);
2624  }
2625  }
2626 
2627  if (!m_playTarget && !m_audioIO) {
2628  emit hideSplash();
2629  QString message;
2630  QString error = errorString.c_str();
2631  QString firstBit, secondBit;
2632  if (implementation == "") {
2633  if (error == "") {
2634  firstBit = tr("<b>No audio available</b><p>Could not open an audio device.</p>");
2635  } else {
2636  firstBit = tr("<b>No audio available</b><p>Could not open audio device: %1</p>").arg(error);
2637  }
2640  secondBit = tr("<p>Automatic audio device detection failed. Audio playback and recording will not be available during this session.</p>");
2641  } else {
2642  secondBit = tr("<p>Automatic audio device detection failed. Audio playback will not be available during this session.</p>");
2643  }
2644  } else {
2645  QString driverName = breakfastquay::AudioFactory::
2646  getImplementationDescription(implementation.toStdString())
2647  .c_str();
2648  if (error == "") {
2649  firstBit = tr("<b>No audio available</b><p>Failed to open your preferred audio driver (\"%1\").</p>").arg(driverName);
2650  } else {
2651  firstBit = tr("<b>No audio available</b><p>Failed to open your preferred audio driver (\"%1\"): %2.</p>").arg(driverName).arg(error);
2652  }
2655  secondBit = tr("<p>Audio playback and recording will not be available during this session.</p>");
2656  } else {
2657  secondBit = tr("<p>Audio playback will not be available during this session.</p>");
2658  }
2659  }
2660  SVDEBUG << "createAudioIO: ERROR: Failed to open audio device \""
2661  << implementation << "\": error is: " << error << endl;
2662  QMessageBox::warning(this, tr("Couldn't open audio device"),
2663  firstBit + secondBit, QMessageBox::Ok);
2664  }
2665 }
2666 
2667 void
2669 {
2670  // First prevent this trying to call target.
2671  if (m_playSource) {
2672  m_playSource->setSystemPlaybackTarget(nullptr);
2673  }
2674 
2675  // Then delete the breakfastquay::System object.
2676  // Only one of these two exists!
2677  delete m_audioIO;
2678  delete m_playTarget;
2679 
2680  m_audioIO = nullptr;
2681  m_playTarget = nullptr;
2682 }
2683 
2684 void
2686 {
2687  deleteAudioIO();
2688  createAudioIO();
2689 }
2690 
2691 void
2693 {
2694  SVCERR << "MainWindowBase::audioChannelCountIncreased" << endl;
2695  recreateAudioIO();
2696 
2697  if (m_recordTarget &&
2698  m_recordTarget->isRecording() &&
2699  m_audioIO) {
2700  SVCERR << "MainWindowBase::audioChannelCountIncreased: we were recording already, so resuming IO now" << endl;
2701  m_audioIO->resume();
2702  }
2703 }
2704 
2705 ModelId
2707 {
2708  if (!m_document) return {};
2709  return m_document->getMainModel();
2710 }
2711 
2712 std::shared_ptr<WaveFileModel>
2714 {
2715  return ModelById::getAs<WaveFileModel>(getMainModelId());
2716 }
2717 
2718 void
2720 {
2721  m_document = new Document;
2722 
2723  connect(m_document, SIGNAL(layerAdded(Layer *)),
2724  this, SLOT(layerAdded(Layer *)));
2725  connect(m_document, SIGNAL(layerRemoved(Layer *)),
2726  this, SLOT(layerRemoved(Layer *)));
2727  connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)),
2728  this, SLOT(layerAboutToBeDeleted(Layer *)));
2729  connect(m_document, SIGNAL(layerInAView(Layer *, bool)),
2730  this, SLOT(layerInAView(Layer *, bool)));
2731 
2732  connect(m_document, SIGNAL(modelAdded(ModelId )),
2733  this, SLOT(modelAdded(ModelId )));
2734  connect(m_document, SIGNAL(mainModelChanged(ModelId)),
2735  this, SLOT(mainModelChanged(ModelId)));
2736 
2737  connect(m_document, SIGNAL(modelGenerationFailed(QString, QString)),
2738  this, SLOT(modelGenerationFailed(QString, QString)));
2739  connect(m_document, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
2740  this, SLOT(modelRegenerationWarning(QString, QString, QString)));
2741  connect(m_document, SIGNAL(alignmentComplete(ModelId)),
2742  this, SLOT(alignmentComplete(ModelId)));
2743  connect(m_document, SIGNAL(alignmentFailed(ModelId, QString)),
2744  this, SLOT(alignmentFailed(ModelId, QString)));
2745 
2746  m_document->setAutoAlignment(m_viewManager->getAlignMode());
2747 
2748  emit replacedDocument();
2749 }
2750 
2751 bool
2753 {
2754  try {
2755 
2756  TempWriteFile temp(path);
2757 
2758  BZipFileDevice bzFile(temp.getTemporaryFilename());
2759  if (!bzFile.open(QIODevice::WriteOnly)) {
2760  cerr << "Failed to open session file \""
2761  << temp.getTemporaryFilename()
2762  << "\" for writing: "
2763  << bzFile.errorString() << endl;
2764  return false;
2765  }
2766 
2767  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
2768 
2769  QTextStream out(&bzFile);
2770  out.setCodec(QTextCodec::codecForName("UTF-8"));
2771  toXml(out, false);
2772  out.flush();
2773 
2774  QApplication::restoreOverrideCursor();
2775 
2776  if (!bzFile.isOK()) {
2777  QMessageBox::critical(this, tr("Failed to write file"),
2778  tr("<b>Save failed</b><p>Failed to write to file \"%1\": %2")
2779  .arg(path).arg(bzFile.errorString()));
2780  bzFile.close();
2781  return false;
2782  }
2783 
2784  bzFile.close();
2785  temp.moveToTarget();
2786  return true;
2787 
2788  } catch (FileOperationFailed &f) {
2789 
2790  QMessageBox::critical(this, tr("Failed to write file"),
2791  tr("<b>Save failed</b><p>Failed to write to file \"%1\": %2")
2792  .arg(path).arg(f.what()));
2793  return false;
2794  }
2795 }
2796 
2797 bool
2799 {
2800  try {
2801 
2802  TempWriteFile temp(path);
2803 
2804  QFile file(temp.getTemporaryFilename());
2805  if (!file.open(QIODevice::WriteOnly)) {
2806  cerr << "Failed to open session template file \""
2807  << temp.getTemporaryFilename()
2808  << "\" for writing: "
2809  << file.errorString() << endl;
2810  return false;
2811  }
2812 
2813  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
2814 
2815  QTextStream out(&file);
2816  out.setCodec(QTextCodec::codecForName("UTF-8"));
2817  toXml(out, true);
2818  out.flush();
2819 
2820  QApplication::restoreOverrideCursor();
2821 
2822  file.close();
2823  temp.moveToTarget();
2824  return true;
2825 
2826  } catch (FileOperationFailed &f) {
2827 
2828  QMessageBox::critical(this, tr("Failed to write file"),
2829  tr("<b>Save failed</b><p>Failed to write to file \"%1\": %2")
2830  .arg(path).arg(f.what()));
2831  return false;
2832  }
2833 }
2834 
2836 // class? then we can more reasonably query it for things like
2837 // "can we export this layer type to this file format? can we
2838 // export selections, or only the whole layer?"
2839 
2840 bool
2842  QString path, QString &error)
2843 {
2844  if (QFileInfo(path).suffix() == "") path += ".svl";
2845 
2846  QString suffix = QFileInfo(path).suffix().toLower();
2847 
2848  auto model = ModelById::get(layer->getExportModel(nullptr));
2849  if (!model) {
2850  error = tr("Internal error: unknown model");
2851  return false;
2852  }
2853 
2854  QFile file(path);
2855  if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
2856  error = tr("Failed to open file %1 for writing").arg(path);
2857  } else {
2858  QTextStream out(&file);
2859  out.setCodec(QTextCodec::codecForName("UTF-8"));
2860  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2861  << "<!DOCTYPE sonic-visualiser>\n"
2862  << "<sv>\n"
2863  << " <data>\n";
2864 
2865  model->toXml(out, " ");
2866 
2867  out << " </data>\n"
2868  << " <display>\n";
2869 
2870  layer->toXml(out, " ");
2871 
2872  out << " </display>\n"
2873  << "</sv>\n";
2874  }
2875 
2876  return (error == "");
2877 }
2878 
2879 bool
2881  MultiSelection *selectionsToWrite,
2882  QString path, QString &error)
2883 {
2884  if (QFileInfo(path).suffix() == "") path += ".mid";
2885 
2886  QString suffix = QFileInfo(path).suffix().toLower();
2887 
2888  auto model = ModelById::get(layer->getExportModel(nullptr));
2889  if (!model) {
2890  error = tr("Internal error: unknown model");
2891  return false;
2892  }
2893 
2894  auto nm = ModelById::getAs<NoteModel>(layer->getModel());
2895 
2896  if (!nm) {
2897  error = tr("Can't export non-note layers to MIDI");
2898  } else if (!selectionsToWrite) {
2899  MIDIFileWriter writer(path, nm.get(), nm->getSampleRate());
2900  writer.write();
2901  if (!writer.isOK()) {
2902  error = writer.getError();
2903  }
2904  } else {
2905  NoteModel temporary(nm->getSampleRate(),
2906  nm->getResolution(),
2907  nm->getValueMinimum(),
2908  nm->getValueMaximum(),
2909  false);
2910  temporary.setScaleUnits(nm->getScaleUnits());
2911  for (const auto &s: selectionsToWrite->getSelections()) {
2912  EventVector ev(nm->getEventsStartingWithin
2913  (s.getStartFrame(), s.getDuration()));
2914  for (const auto &e: ev) {
2915  temporary.add(e);
2916  }
2917  }
2918  MIDIFileWriter writer(path, &temporary, temporary.getSampleRate());
2919  writer.write();
2920  if (!writer.isOK()) {
2921  error = writer.getError();
2922  }
2923  }
2924 
2925  return (error == "");
2926 }
2927 
2928 bool
2930  QString path, QString &error)
2931 {
2932  if (QFileInfo(path).suffix() == "") path += ".ttl";
2933 
2934  QString suffix = QFileInfo(path).suffix().toLower();
2935 
2936  auto model = ModelById::get(layer->getExportModel(nullptr));
2937  if (!model) {
2938  error = tr("Internal error: unknown model");
2939  return false;
2940  }
2941 
2942  if (!RDFExporter::canExportModel(model.get())) {
2943  error = tr("Sorry, cannot export this layer type to RDF (supported types are: region, note, text, time instants, time values)");
2944  } else {
2945  RDFExporter exporter(path, model.get());
2946  exporter.write();
2947  if (!exporter.isOK()) {
2948  error = exporter.getError();
2949  }
2950  }
2951 
2952  return (error == "");
2953 }
2954 
2955 bool
2956 MainWindowBase::exportLayerToCSV(Layer *layer, LayerGeometryProvider *provider,
2957  MultiSelection *selectionsToWrite,
2958  QString delimiter,
2959  DataExportOptions options,
2960  QString path, QString &error)
2961 {
2962  if (QFileInfo(path).suffix() == "") path += ".csv";
2963 
2964  QString suffix = QFileInfo(path).suffix().toLower();
2965 
2966  auto model = ModelById::get(layer->getExportModel(provider));
2967  if (!model) {
2968  error = tr("Internal error: unknown model");
2969  return false;
2970  }
2971 
2972  ProgressDialog dialog {
2973  QObject::tr("Exporting layer..."), true, 500, this,
2974  Qt::ApplicationModal
2975  };
2976 
2977  CSVFileWriter writer(path, model.get(), &dialog, delimiter, options);
2978 
2979  if (selectionsToWrite) {
2980  writer.writeSelection(*selectionsToWrite);
2981  } else {
2982  writer.write();
2983  }
2984 
2985  if (!writer.isOK()) {
2986  error = writer.getError();
2987  if (error == "") {
2988  error = tr("Failed to export layer for an unknown reason");
2989  }
2990  }
2991 
2992  return (error == "");
2993 }
2994 
2995 bool
2996 MainWindowBase::exportLayerTo(Layer *layer, LayerGeometryProvider *provider,
2997  MultiSelection *selectionsToWrite,
2998  QString path, QString &error)
2999 {
3000  if (QFileInfo(path).suffix() == "") path += ".csv";
3001  QString suffix = QFileInfo(path).suffix().toLower();
3002 
3003  if (suffix == "xml" || suffix == "svl") {
3004  return exportLayerToSVL(layer, path, error);
3005  } else if (suffix == "mid" || suffix == "midi") {
3006  return exportLayerToMIDI(layer, selectionsToWrite, path, error);
3007  } else if (suffix == "ttl" || suffix == "n3") {
3008  return exportLayerToRDF(layer, path, error);
3009  } else {
3010  return exportLayerToCSV(layer, provider, selectionsToWrite,
3011  (suffix == "csv" ? "," : "\t"),
3012  DataExportDefaults, path, error);
3013  }
3014 }
3015 
3016 void
3017 MainWindowBase::toXml(QTextStream &out, bool asTemplate)
3018 {
3019  QString indent(" ");
3020 
3021  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
3022  out << "<!DOCTYPE sonic-visualiser>\n";
3023  out << "<sv>\n";
3024 
3025  if (asTemplate) {
3026  m_document->toXmlAsTemplate(out, "", "");
3027  } else {
3028  m_document->toXml(out, "", "");
3029  }
3030 
3031  out << "<display>\n";
3032 
3033  out << QString(" <window width=\"%1\" height=\"%2\"/>\n")
3034  .arg(width()).arg(height());
3035 
3036  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
3037 
3038  Pane *pane = m_paneStack->getPane(i);
3039 
3040  if (pane) {
3041  pane->toXml(out, indent);
3042  }
3043  }
3044 
3045  out << "</display>\n";
3046 
3047  m_viewManager->getSelection().toXml(out);
3048 
3049  out << "</sv>\n";
3050 }
3051 
3052 Pane *
3054 {
3055  cerr << "MainWindowBase::addPaneToStack()" << endl;
3056  AddPaneCommand *command = new AddPaneCommand(this);
3057  CommandHistory::getInstance()->addCommand(command);
3058  Pane *pane = command->getPane();
3059  return pane;
3060 }
3061 
3062 void
3064 {
3065  Pane *currentPane = m_paneStack->getCurrentPane();
3066  if (currentPane) currentPane->zoom(true);
3067 }
3068 
3069 void
3071 {
3072  Pane *currentPane = m_paneStack->getCurrentPane();
3073  if (currentPane) currentPane->zoom(false);
3074 }
3075 
3076 void
3078 {
3079  Pane *currentPane = m_paneStack->getCurrentPane();
3080  if (!currentPane) return;
3081 
3082  auto model = getMainModel();
3083  if (!model) return;
3084 
3085  sv_frame_t start = model->getStartFrame();
3086  sv_frame_t end = model->getEndFrame();
3087  if (m_playSource) end = std::max(end, m_playSource->getPlayEndFrame());
3088  int pixels = currentPane->width();
3089 
3090  int sw = currentPane->getVerticalScaleWidth();
3091  if (pixels > sw * 2) pixels -= sw * 2;
3092  else pixels = 1;
3093  if (pixels > 4) pixels -= 4;
3094 
3095  ZoomLevel zoomLevel = ZoomLevel::fromRatio(pixels, end - start);
3096  currentPane->setZoomLevel(zoomLevel);
3097  currentPane->setCentreFrame((start + end) / 2);
3098 }
3099 
3100 void
3102 {
3103  Pane *currentPane = m_paneStack->getCurrentPane();
3104  QSettings settings;
3105  settings.beginGroup("MainWindow");
3106  int zoom = settings.value("zoom-default", 1024).toInt();
3107  settings.endGroup();
3108  if (currentPane) {
3109  currentPane->setZoomLevel(ZoomLevel(ZoomLevel::FramesPerPixel, zoom));
3110  }
3111 }
3112 
3113 void
3115 {
3116  Pane *currentPane = m_paneStack->getCurrentPane();
3117  if (currentPane) currentPane->scroll(false, false);
3118 }
3119 
3120 void
3122 {
3123  Pane *currentPane = m_paneStack->getCurrentPane();
3124  if (currentPane) currentPane->scroll(false, true);
3125 }
3126 
3127 void
3129 {
3130  Pane *currentPane = m_paneStack->getCurrentPane();
3131  if (currentPane) currentPane->scroll(false, false, false);
3132 }
3133 
3134 void
3136 {
3137  Pane *currentPane = m_paneStack->getCurrentPane();
3138  if (currentPane) currentPane->scroll(true, false);
3139 }
3140 
3141 void
3143 {
3144  Pane *currentPane = m_paneStack->getCurrentPane();
3145  if (currentPane) currentPane->scroll(true, true);
3146 }
3147 
3148 void
3150 {
3151  Pane *currentPane = m_paneStack->getCurrentPane();
3152  if (currentPane) currentPane->scroll(true, false, false);
3153 }
3154 
3155 void
3157 {
3158  m_viewManager->setOverlayMode(ViewManager::NoOverlays);
3159 }
3160 
3161 void
3163 {
3164  m_viewManager->setOverlayMode(ViewManager::StandardOverlays);
3165 }
3166 
3167 void
3169 {
3170  m_viewManager->setOverlayMode(ViewManager::AllOverlays);
3171 }
3172 
3173 void
3175 {
3176  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
3177  Pane *pane = m_paneStack->getPane(i);
3178  if (!pane) continue;
3179  for (int j = 0; j < pane->getLayerCount(); ++j) {
3180  Layer *layer = pane->getLayer(j);
3181  if (!dynamic_cast<TimeRulerLayer *>(layer)) continue;
3182  m_timeRulerLayer = layer;
3183  return;
3184  }
3185  }
3186  if (m_timeRulerLayer) {
3187  SVCERR << "WARNING: Time ruler layer was not reset to 0 before session template loaded?" << endl;
3188  delete m_timeRulerLayer;
3189  m_timeRulerLayer = nullptr;
3190  }
3191 }
3192 
3193 void
3195 {
3196  bool haveRulers = false;
3197  bool someHidden = false;
3198 
3199  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
3200 
3201  Pane *pane = m_paneStack->getPane(i);
3202  if (!pane) continue;
3203 
3204  for (int j = 0; j < pane->getLayerCount(); ++j) {
3205 
3206  Layer *layer = pane->getLayer(j);
3207  if (!dynamic_cast<TimeRulerLayer *>(layer)) continue;
3208 
3209  haveRulers = true;
3210  if (layer->isLayerDormant(pane)) someHidden = true;
3211  }
3212  }
3213 
3214  if (haveRulers) {
3215 
3216  bool show = someHidden;
3217 
3218  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
3219 
3220  Pane *pane = m_paneStack->getPane(i);
3221  if (!pane) continue;
3222 
3223  for (int j = 0; j < pane->getLayerCount(); ++j) {
3224 
3225  Layer *layer = pane->getLayer(j);
3226  if (!dynamic_cast<TimeRulerLayer *>(layer)) continue;
3227 
3228  layer->showLayer(pane, show);
3229  }
3230  }
3231  }
3232 }
3233 
3234 void
3236 {
3237  if (m_viewManager->getZoomWheelsEnabled()) {
3238  m_viewManager->setZoomWheelsEnabled(false);
3239  } else {
3240  m_viewManager->setZoomWheelsEnabled(true);
3241  }
3242 }
3243 
3244 void
3246 {
3247  if (m_paneStack->getLayoutStyle() == PaneStack::HiddenPropertyStacksLayout) {
3248  if (Preferences::getInstance()->getPropertyBoxLayout() ==
3249  Preferences::VerticallyStacked) {
3250  m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout);
3251  } else {
3252  m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout);
3253  }
3254  } else {
3255  m_paneStack->setLayoutStyle(PaneStack::HiddenPropertyStacksLayout);
3256  }
3257 }
3258 
3259 QLabel *
3261 {
3262  if (!m_statusLabel) {
3263  m_statusLabel = new QLabel();
3264  statusBar()->addWidget(m_statusLabel, 1);
3265  }
3266 
3267  QList<QFrame *> frames = statusBar()->findChildren<QFrame *>();
3268  foreach (QFrame *f, frames) {
3269  f->setFrameStyle(QFrame::NoFrame);
3270  }
3271 
3272  return m_statusLabel;
3273 }
3274 
3275 void
3277 {
3278  QSettings settings;
3279  settings.beginGroup("MainWindow");
3280  bool sb = settings.value("showstatusbar", true).toBool();
3281 
3282  if (sb) {
3283  statusBar()->hide();
3284  } else {
3285  statusBar()->show();
3286  }
3287 
3288  settings.setValue("showstatusbar", !sb);
3289 
3290  settings.endGroup();
3291 }
3292 
3293 void
3295 {
3296  if (m_viewManager->shouldShowCentreLine()) {
3297  m_viewManager->setShowCentreLine(false);
3298  } else {
3299  m_viewManager->setShowCentreLine(true);
3300  }
3301 }
3302 
3303 void
3304 MainWindowBase::preferenceChanged(PropertyContainer::PropertyName name)
3305 {
3306  if (name == "Property Box Layout") {
3307  if (m_paneStack->getLayoutStyle() != PaneStack::HiddenPropertyStacksLayout) {
3308  if (Preferences::getInstance()->getPropertyBoxLayout() ==
3309  Preferences::VerticallyStacked) {
3310  m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout);
3311  } else {
3312  m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout);
3313  }
3314  }
3315  } else if (name == "Background Mode" && m_viewManager) {
3316  Preferences::BackgroundMode mode =
3317  Preferences::getInstance()->getBackgroundMode();
3318  if (mode == Preferences::BackgroundFromTheme) {
3319  m_viewManager->setGlobalDarkBackground(m_initialDarkBackground);
3320  } else if (mode == Preferences::DarkBackground) {
3321  m_viewManager->setGlobalDarkBackground(true);
3322  } else {
3323  m_viewManager->setGlobalDarkBackground(false);
3324  }
3325  }
3326 }
3327 
3328 void
3330 {
3331  if ((m_recordTarget && m_recordTarget->isRecording()) ||
3332  (m_playSource && m_playSource->isPlaying())) {
3333  stop();
3334  QAction *action = qobject_cast<QAction *>(sender());
3335  if (action) action->setChecked(false);
3336  } else {
3337  if (m_audioIO) m_audioIO->resume();
3338  else if (m_playTarget) m_playTarget->resume();
3339  playbackFrameChanged(m_viewManager->getPlaybackFrame());
3340  m_playSource->play(m_viewManager->getPlaybackFrame());
3341  }
3342 }
3343 
3344 void
3346 {
3347  QAction *action = qobject_cast<QAction *>(sender());
3348 
3350  if (action) action->setChecked(false);
3351  return;
3352  }
3353 
3354  if (!m_recordTarget) {
3355  if (action) action->setChecked(false);
3356  return;
3357  }
3358 
3360  SVDEBUG << "MainWindowBase::record: upgrading from "
3361  << "AUDIO_PLAYBACK_NOW_RECORD_LATER to "
3362  << "AUDIO_PLAYBACK_AND_RECORD" << endl;
3364  deleteAudioIO();
3365  }
3366 
3367  if (!m_audioIO) {
3368  SVDEBUG << "MainWindowBase::record: about to create audio IO" << endl;
3369  createAudioIO();
3370  }
3371 
3372  if (!m_audioIO) {
3373  if (!m_playTarget) {
3374  // Don't need to report this, createAudioIO should have
3375  if (action) action->setChecked(false);
3376  return;
3377  } else {
3378  // Need to report this: if the play target exists instead
3379  // of the audio IO, then that means we failed to open a
3380  // capture device. The record control should be disabled
3381  // in that situation, so if it happens here, that must
3382  // mean this is the first time we ever tried to open the
3383  // audio device, hence the need to report the problem here
3384  QMessageBox::critical
3385  (this, tr("No record device available"),
3386  tr("<b>No record device available</b><p>Failed to find or open an audio device for recording. Only playback will be available.</p>"));
3387  if (action) action->setChecked(false);
3388  updateMenuStates();
3389  return;
3390  }
3391  }
3392 
3393  if (m_recordTarget->isRecording()) {
3394  stop();
3395  return;
3396  }
3397 
3399  if (!checkSaveModified()) {
3400  if (action) action->setChecked(false);
3401  return;
3402  }
3403  }
3404 
3405  if (m_viewManager) m_viewManager->setGlobalCentreFrame(0);
3406 
3407  SVCERR << "MainWindowBase::record: about to resume" << endl;
3408  m_audioIO->resume();
3409 
3410  WritableWaveFileModel *modelPtr = m_recordTarget->startRecording();
3411  if (!modelPtr) {
3412  SVCERR << "ERROR: MainWindowBase::record: Recording failed" << endl;
3413  QMessageBox::critical
3414  (this, tr("Recording failed"),
3415  tr("<b>Recording failed</b><p>Failed to switch to record mode (some internal problem?)</p>"));
3416  if (action) action->setChecked(false);
3417  return;
3418  }
3419 
3420  if (!modelPtr->isOK()) {
3421  SVCERR << "MainWindowBase::record: Model not OK, stopping and suspending" << endl;
3422  m_recordTarget->stopRecording();
3423  m_audioIO->suspend();
3424  if (action) action->setChecked(false);
3425  delete modelPtr;
3426  return;
3427  }
3428 
3429  SVCERR << "MainWindowBase::record: Model is OK, continuing..." << endl;
3430 
3431  QString location = modelPtr->getLocation();
3432 
3433  auto modelId = ModelById::add(std::shared_ptr<Model>(modelPtr));
3434 
3436 
3438 
3439  QString templateName = getDefaultSessionTemplate();
3440  bool loadedTemplate = false;
3441 
3442  if (templateName != "") {
3443  FileOpenStatus tplStatus = openSessionTemplate(templateName);
3444  if (tplStatus == FileOpenCancelled) {
3445  SVCERR << "MainWindowBase::record: Session template open cancelled, stopping and suspending" << endl;
3446  m_recordTarget->stopRecording();
3447  m_audioIO->suspend();
3448  ModelById::release(modelId);
3449  return;
3450  }
3451  if (tplStatus != FileOpenFailed) {
3452  loadedTemplate = true;
3453  }
3454  }
3455 
3456  if (!loadedTemplate) {
3457  closeSession();
3458  createDocument();
3459  }
3460 
3461  ModelId prevMain = getMainModelId();
3462  if (!prevMain.isNone()) {
3463  m_playSource->removeModel(prevMain);
3464  }
3465 
3466  m_document->setMainModel(modelId);
3467  setupMenus();
3469 
3470  m_originalLocation = location;
3471 
3472  if (loadedTemplate || (m_sessionFile == "")) {
3473  CommandHistory::getInstance()->clear();
3474  CommandHistory::getInstance()->documentSaved();
3475  }
3476 
3477  m_documentModified = false;
3479 
3480  } else {
3481 
3482  CommandHistory::getInstance()->startCompoundOperation
3483  (tr("Import Recorded Audio"), true);
3484 
3485  m_document->addNonDerivedModel(modelId);
3486 
3487  AddPaneCommand *command = new AddPaneCommand(this);
3488  CommandHistory::getInstance()->addCommand(command);
3489 
3490  Pane *pane = command->getPane();
3491 
3492  if (m_timeRulerLayer) {
3494  }
3495 
3496  Layer *newLayer = m_document->createImportedLayer(modelId);
3497 
3498  if (newLayer) {
3499  m_document->addLayerToView(pane, newLayer);
3500  }
3501 
3502  CommandHistory::getInstance()->endCompoundOperation();
3503  }
3504 
3505  updateMenuStates();
3506  m_recentFiles.addFile(location);
3507  currentPaneChanged(m_paneStack->getCurrentPane());
3508 
3509  emit audioFileLoaded();
3510 }
3511 
3512 void
3514 {
3515  if (!getMainModel()) return;
3516 
3517  sv_frame_t playbackFrame = m_viewManager->getPlaybackFrame();
3518  sv_frame_t frame = playbackFrame + 1;
3519 
3520  Pane *pane = m_paneStack->getCurrentPane();
3521  Layer *layer = getSnapLayer();
3522  sv_samplerate_t sr = getMainModel()->getSampleRate();
3523 
3524  if (!pane || !layer) {
3525 
3526  frame = RealTime::realTime2Frame
3527  (RealTime::frame2RealTime(frame, sr) + m_defaultFfwdRwdStep, sr);
3528  if (frame > getMainModel()->getEndFrame()) {
3529  frame = getMainModel()->getEndFrame();
3530  }
3531 
3532  } else {
3533 
3534  sv_frame_t pframe = pane->alignFromReference(frame);
3535  int resolution = 0;
3536  bool success = false;
3537 
3538  while (layer->snapToFeatureFrame(pane, pframe, resolution,
3539  Layer::SnapRight, -1)) {
3540  if (pane->alignToReference(pframe) > playbackFrame) {
3541  success = true;
3542  break;
3543  } else {
3544  ++pframe;
3545  }
3546  }
3547 
3548  if (success) {
3549  frame = pane->alignToReference(pframe);
3550  } else {
3551  frame = getMainModel()->getEndFrame();
3552  }
3553  }
3554 
3555  if (frame < 0) frame = 0;
3556 
3557  if (m_viewManager->getPlaySelectionMode()) {
3558  frame = m_viewManager->constrainFrameToSelection(frame);
3559  }
3560 
3561  m_viewManager->setPlaybackFrame(frame);
3562 
3563  if (frame >= getMainModel()->getEndFrame() &&
3564  m_playSource &&
3565  m_playSource->isPlaying() &&
3566  !m_viewManager->getPlayLoopMode()) {
3567  stop();
3568  }
3569 }
3570 
3571 void
3573 {
3574  if (!getMainModel()) return;
3575 
3576  if (m_playSource &&
3577  m_playSource->isPlaying() &&
3578  !m_viewManager->getPlayLoopMode()) {
3579  stop();
3580  }
3581 
3582  sv_frame_t frame = getMainModel()->getEndFrame();
3583 
3584  if (m_viewManager->getPlaySelectionMode()) {
3585  frame = m_viewManager->constrainFrameToSelection(frame);
3586  }
3587 
3588  m_viewManager->setPlaybackFrame(frame);
3589 }
3590 
3591 void
3593 {
3594  if (!getMainModel()) return;
3595 
3596  Layer *layer = getSnapLayer();
3597  if (!layer) { ffwd(); return; }
3598 
3599  Pane *pane = m_paneStack->getCurrentPane();
3600  sv_frame_t frame = m_viewManager->getPlaybackFrame();
3601 
3602  int resolution = 0;
3603  if (pane) frame = pane->alignFromReference(frame);
3604  if (layer->snapToSimilarFeature(m_paneStack->getCurrentPane(),
3605  frame, resolution, Layer::SnapRight)) {
3606  if (pane) frame = pane->alignToReference(frame);
3607  } else {
3608  frame = getMainModel()->getEndFrame();
3609  }
3610 
3611  if (frame < 0) frame = 0;
3612 
3613  if (m_viewManager->getPlaySelectionMode()) {
3614  frame = m_viewManager->constrainFrameToSelection(frame);
3615  }
3616 
3617  m_viewManager->setPlaybackFrame(frame);
3618 
3619  if (frame == getMainModel()->getEndFrame() &&
3620  m_playSource &&
3621  m_playSource->isPlaying() &&
3622  !m_viewManager->getPlayLoopMode()) {
3623  stop();
3624  }
3625 }
3626 
3627 void
3629 {
3630  if (!getMainModel()) return;
3631 
3632  sv_frame_t playbackFrame = m_viewManager->getPlaybackFrame();
3633  sv_frame_t frame = playbackFrame;
3634  if (frame > 0) --frame;
3635 
3636  Pane *pane = m_paneStack->getCurrentPane();
3637  Layer *layer = getSnapLayer();
3638  sv_samplerate_t sr = getMainModel()->getSampleRate();
3639 
3640  // when rewinding during playback, we want to allow a period
3641  // following a rewind target point at which the rewind will go to
3642  // the prior point instead of the immediately neighbouring one
3643  if (m_playSource && m_playSource->isPlaying()) {
3644  RealTime ct = RealTime::frame2RealTime(frame, sr);
3645  ct = ct - RealTime::fromSeconds(0.15);
3646  if (ct < RealTime::zeroTime) ct = RealTime::zeroTime;
3647  frame = RealTime::realTime2Frame(ct, sr);
3648  }
3649 
3650  if (!pane || !layer) {
3651 
3652  frame = RealTime::realTime2Frame
3653  (RealTime::frame2RealTime(frame, sr) - m_defaultFfwdRwdStep, sr);
3654  if (frame < getMainModel()->getStartFrame()) {
3655  frame = getMainModel()->getStartFrame();
3656  }
3657 
3658  } else {
3659 
3660  sv_frame_t pframe = pane->alignFromReference(frame);
3661  int resolution = 0;
3662  bool success = false;
3663 
3664  while (layer->snapToFeatureFrame(pane, pframe, resolution,
3665  Layer::SnapLeft, -1)) {
3666  if (pane->alignToReference(pframe) < playbackFrame ||
3667  pframe <= 0) {
3668  success = true;
3669  break;
3670  } else {
3671  --pframe;
3672  }
3673  }
3674 
3675  if (success) {
3676  frame = pane->alignToReference(pframe);
3677  } else {
3678  frame = getMainModel()->getStartFrame();
3679  }
3680  }
3681 
3682  if (frame < 0) frame = 0;
3683 
3684  if (m_viewManager->getPlaySelectionMode()) {
3685  frame = m_viewManager->constrainFrameToSelection(frame);
3686  }
3687 
3688  m_viewManager->setPlaybackFrame(frame);
3689 }
3690 
3691 void
3693 {
3694  if (!getMainModel()) return;
3695 
3696  sv_frame_t frame = getMainModel()->getStartFrame();
3697 
3698  if (m_viewManager->getPlaySelectionMode()) {
3699  frame = m_viewManager->constrainFrameToSelection(frame);
3700  }
3701 
3702  m_viewManager->setPlaybackFrame(frame);
3703 }
3704 
3705 void
3707 {
3708  if (!getMainModel()) return;
3709 
3710  Layer *layer = getSnapLayer();
3711  if (!layer) { rewind(); return; }
3712 
3713  Pane *pane = m_paneStack->getCurrentPane();
3714  sv_frame_t frame = m_viewManager->getPlaybackFrame();
3715 
3716  int resolution = 0;
3717  if (pane) frame = pane->alignFromReference(frame);
3718  if (layer->snapToSimilarFeature(m_paneStack->getCurrentPane(),
3719  frame, resolution, Layer::SnapLeft)) {
3720  if (pane) frame = pane->alignToReference(frame);
3721  } else {
3722  frame = getMainModel()->getStartFrame();
3723  }
3724 
3725  if (frame < 0) frame = 0;
3726 
3727  if (m_viewManager->getPlaySelectionMode()) {
3728  frame = m_viewManager->constrainFrameToSelection(frame);
3729  }
3730 
3731  m_viewManager->setPlaybackFrame(frame);
3732 }
3733 
3734 Layer *
3736 {
3737  Pane *pane = m_paneStack->getCurrentPane();
3738  if (!pane) return nullptr;
3739 
3740  Layer *layer = pane->getSelectedLayer();
3741 
3742  if (!dynamic_cast<TimeInstantLayer *>(layer) &&
3743  !dynamic_cast<TimeValueLayer *>(layer) &&
3744  !dynamic_cast<RegionLayer *>(layer) &&
3745  !dynamic_cast<TimeRulerLayer *>(layer)) {
3746 
3747  layer = nullptr;
3748 
3749  for (int i = pane->getLayerCount(); i > 0; --i) {
3750  Layer *l = pane->getLayer(i-1);
3751  if (dynamic_cast<TimeRulerLayer *>(l)) {
3752  layer = l;
3753  break;
3754  }
3755  }
3756  }
3757 
3758  return layer;
3759 }
3760 
3761 void
3763 {
3764  if (m_recordTarget &&
3765  m_recordTarget->isRecording()) {
3766  m_recordTarget->stopRecording();
3767  }
3768 
3769  if (!m_playSource) return;
3770 
3771  m_playSource->stop();
3772 
3773  SVDEBUG << "MainWindowBase::stop: suspending" << endl;
3774 
3775  if (m_audioIO) m_audioIO->suspend();
3776  else if (m_playTarget) m_playTarget->suspend();
3777 
3778  if (m_paneStack && m_paneStack->getCurrentPane()) {
3779  updateVisibleRangeDisplay(m_paneStack->getCurrentPane());
3780  } else {
3781  m_myStatusMessage = "";
3782  getStatusLabel()->setText("");
3783  }
3784 }
3785 
3787  m_mw(mw),
3788  m_pane(nullptr),
3789  m_prevCurrentPane(nullptr),
3790  m_added(false)
3791 {
3792 }
3793 
3795 {
3796  if (m_pane && !m_added) {
3797  m_mw->m_paneStack->deletePane(m_pane);
3798  }
3799 }
3800 
3801 QString
3803 {
3804  return tr("Add Pane");
3805 }
3806 
3807 void
3809 {
3810  if (!m_pane) {
3811  m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane();
3812  m_pane = m_mw->m_paneStack->addPane();
3813 
3814  connect(m_pane, SIGNAL(contextHelpChanged(const QString &)),
3815  m_mw, SLOT(contextHelpChanged(const QString &)));
3816  } else {
3817  m_mw->m_paneStack->showPane(m_pane);
3818  }
3819 
3820  m_mw->m_paneStack->setCurrentPane(m_pane);
3821  m_added = true;
3822 }
3823 
3824 void
3826 {
3827  m_mw->m_paneStack->hidePane(m_pane);
3828  m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane);
3829  m_added = false;
3830 }
3831 
3833  m_mw(mw),
3834  m_pane(pane),
3835  m_prevCurrentPane(nullptr),
3836  m_added(true)
3837 {
3838 }
3839 
3841 {
3842  if (m_pane && !m_added) {
3843  m_mw->m_paneStack->deletePane(m_pane);
3844  }
3845 }
3846 
3847 QString
3849 {
3850  return tr("Remove Pane");
3851 }
3852 
3853 void
3855 {
3856  m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane();
3857  m_mw->m_paneStack->hidePane(m_pane);
3858  m_added = false;
3859 }
3860 
3861 void
3863 {
3864  m_mw->m_paneStack->showPane(m_pane);
3865  m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane);
3866  m_added = true;
3867 }
3868 
3869 void
3871 {
3872  CommandHistory::getInstance()->startCompoundOperation
3873  (tr("Delete Pane"), true);
3874 
3875  Pane *pane = m_paneStack->getCurrentPane();
3876  if (pane) {
3877  while (pane->getLayerCount() > 0) {
3878  Layer *layer = pane->getLayer(0);
3879  if (layer) {
3880  m_document->removeLayerFromView(pane, layer);
3881  } else {
3882  break;
3883  }
3884  }
3885 
3886  RemovePaneCommand *command = new RemovePaneCommand(this, pane);
3887  CommandHistory::getInstance()->addCommand(command);
3888  }
3889 
3890  CommandHistory::getInstance()->endCompoundOperation();
3891 
3892  updateMenuStates();
3893 }
3894 
3895 void
3897 {
3898  Pane *pane = m_paneStack->getCurrentPane();
3899  if (pane) {
3900  Layer *layer = pane->getSelectedLayer();
3901  if (layer) {
3902  m_document->removeLayerFromView(pane, layer);
3903  }
3904  }
3905  updateMenuStates();
3906 }
3907 
3908 void
3910 {
3911  Layer *layer = nullptr;
3912  Pane *pane = m_paneStack->getCurrentPane();
3913  if (pane) layer = pane->getSelectedLayer();
3914  if (!layer) return;
3915 
3916  auto tabular = ModelById::getAs<TabularModel>(layer->getModel());
3917  if (!tabular) {
3919  //appropriate model type? or will we ultimately support
3920  //tabular display for all editable models?
3921  SVDEBUG << "NOTE: Not a tabular model" << endl;
3922  return;
3923  }
3924 
3925  if (m_layerDataDialogMap.find(layer) != m_layerDataDialogMap.end()) {
3926  if (!m_layerDataDialogMap[layer].isNull()) {
3927  m_layerDataDialogMap[layer]->show();
3928  m_layerDataDialogMap[layer]->raise();
3929  return;
3930  }
3931  }
3932 
3933  QString title = layer->getLayerPresentationName();
3934 
3935  ModelDataTableDialog *dialog = new ModelDataTableDialog
3936  (layer->getModel(), title, this);
3937  dialog->setAttribute(Qt::WA_DeleteOnClose);
3938 
3939  connectLayerEditDialog(dialog);
3940 
3941  m_layerDataDialogMap[layer] = dialog;
3942  m_viewDataDialogMap[pane].insert(dialog);
3943 
3944  dialog->show();
3945 }
3946 
3947 void
3948 MainWindowBase::connectLayerEditDialog(ModelDataTableDialog *dialog)
3949 {
3950  connect(m_viewManager,
3951  SIGNAL(globalCentreFrameChanged(sv_frame_t)),
3952  dialog,
3953  SLOT(userScrolledToFrame(sv_frame_t)));
3954 
3955  connect(m_viewManager,
3956  SIGNAL(playbackFrameChanged(sv_frame_t)),
3957  dialog,
3958  SLOT(playbackScrolledToFrame(sv_frame_t)));
3959 
3960  connect(dialog,
3961  SIGNAL(scrollToFrame(sv_frame_t)),
3962  m_viewManager,
3963  SLOT(setGlobalCentreFrame(sv_frame_t)));
3964 
3965  connect(dialog,
3966  SIGNAL(scrollToFrame(sv_frame_t)),
3967  m_viewManager,
3968  SLOT(setPlaybackFrame(sv_frame_t)));
3969 }
3970 
3971 void
3973 {
3974  if (!m_paneStack) return;
3975 
3976  Pane *currentPane = m_paneStack->getCurrentPane();
3977  if (!currentPane) return;
3978 
3979  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
3980  if (m_paneStack->getPane(i) == currentPane) {
3981  if (i == 0) return;
3982  m_paneStack->setCurrentPane(m_paneStack->getPane(i-1));
3983  updateMenuStates();
3984  return;
3985  }
3986  }
3987 }
3988 
3989 void
3991 {
3992  if (!m_paneStack) return;
3993 
3994  Pane *currentPane = m_paneStack->getCurrentPane();
3995  if (!currentPane) return;
3996 
3997  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
3998  if (m_paneStack->getPane(i) == currentPane) {
3999  if (i == m_paneStack->getPaneCount()-1) return;
4000  m_paneStack->setCurrentPane(m_paneStack->getPane(i+1));
4001  updateMenuStates();
4002  return;
4003  }
4004  }
4005 }
4006 
4007 void
4009 {
4010  if (!m_paneStack) return;
4011 
4012  Pane *currentPane = m_paneStack->getCurrentPane();
4013  if (!currentPane) return;
4014 
4015  int count = currentPane->getLayerCount();
4016  if (count == 0) return;
4017 
4018  Layer *currentLayer = currentPane->getSelectedLayer();
4019 
4020  if (!currentLayer) {
4021  // The pane itself is current
4022  m_paneStack->setCurrentLayer
4023  (currentPane, currentPane->getFixedOrderLayer(count-1));
4024  } else {
4025  for (int i = 0; i < count; ++i) {
4026  if (currentPane->getFixedOrderLayer(i) == currentLayer) {
4027  if (i == 0) {
4028  m_paneStack->setCurrentLayer
4029  (currentPane, nullptr); // pane
4030  } else {
4031  m_paneStack->setCurrentLayer
4032  (currentPane, currentPane->getFixedOrderLayer(i-1));
4033  }
4034  break;
4035  }
4036  }
4037  }
4038 
4039  updateMenuStates();
4040 }
4041 
4042 void
4044 {
4045  if (!m_paneStack) return;
4046 
4047  Pane *currentPane = m_paneStack->getCurrentPane();
4048  if (!currentPane) return;
4049 
4050  int count = currentPane->getLayerCount();
4051  if (count == 0) return;
4052 
4053  Layer *currentLayer = currentPane->getSelectedLayer();
4054 
4055  if (!currentLayer) {
4056  // The pane itself is current
4057  m_paneStack->setCurrentLayer
4058  (currentPane, currentPane->getFixedOrderLayer(0));
4059  } else {
4060  for (int i = 0; i < count; ++i) {
4061  if (currentPane->getFixedOrderLayer(i) == currentLayer) {
4062  if (i == currentPane->getLayerCount()-1) {
4063  m_paneStack->setCurrentLayer
4064  (currentPane, nullptr); // pane
4065  } else {
4066  m_paneStack->setCurrentLayer
4067  (currentPane, currentPane->getFixedOrderLayer(i+1));
4068  }
4069  break;
4070  }
4071  }
4072  }
4073 
4074  updateMenuStates();
4075 }
4076 
4077 void
4079 {
4080  if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
4081 
4083 
4084  RealTime now = RealTime::frame2RealTime
4085  (frame, getMainModel()->getSampleRate());
4086 
4087  if (now.sec == m_lastPlayStatusSec) return;
4088 
4089  RealTime then = RealTime::frame2RealTime
4090  (m_playSource->getPlayEndFrame(), getMainModel()->getSampleRate());
4091 
4092  QString nowStr;
4093  QString thenStr;
4094  QString remainingStr;
4095 
4096  if (then.sec > 10) {
4097  nowStr = now.toSecText().c_str();
4098  thenStr = then.toSecText().c_str();
4099  remainingStr = (then - now).toSecText().c_str();
4100  m_lastPlayStatusSec = now.sec;
4101  } else {
4102  nowStr = now.toText(true).c_str();
4103  thenStr = then.toText(true).c_str();
4104  remainingStr = (then - now).toText(true).c_str();
4105  }
4106 
4107  m_myStatusMessage = tr("Playing: %1 of %2 (%3 remaining)")
4108  .arg(nowStr).arg(thenStr).arg(remainingStr);
4109 
4110  getStatusLabel()->setText(m_myStatusMessage);
4111 }
4112 
4113 void
4114 MainWindowBase::recordDurationChanged(sv_frame_t frame, sv_samplerate_t rate)
4115 {
4116  RealTime duration = RealTime::frame2RealTime(frame, rate);
4117  QString durStr = duration.toSecText().c_str();
4118 
4119  m_myStatusMessage = tr("Recording: %1").arg(durStr);
4120 
4121  getStatusLabel()->setText(m_myStatusMessage);
4122 }
4123 
4124 void
4126 {
4127  if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
4128  Pane *p = nullptr;
4129  if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
4130  if (!p->getFollowGlobalPan()) return;
4132 }
4133 
4134 void
4135 MainWindowBase::viewCentreFrameChanged(View *v, sv_frame_t frame)
4136 {
4137 // SVDEBUG << "MainWindowBase::viewCentreFrameChanged(" << v << "," << frame << ")" << endl;
4138 
4139  if (m_viewDataDialogMap.find(v) != m_viewDataDialogMap.end()) {
4140  for (DataDialogSet::iterator i = m_viewDataDialogMap[v].begin();
4141  i != m_viewDataDialogMap[v].end(); ++i) {
4142  (*i)->userScrolledToFrame(frame);
4143  }
4144  }
4145  if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
4146  Pane *p = nullptr;
4147  if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
4148  if (v == p) updateVisibleRangeDisplay(p);
4149 }
4150 
4151 void
4152 MainWindowBase::viewZoomLevelChanged(View *v, ZoomLevel, bool )
4153 {
4154  if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
4155  Pane *p = nullptr;
4156  if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
4157  if (v == p) updateVisibleRangeDisplay(p);
4158 }
4159 
4160 void
4162 {
4163 // SVDEBUG << "MainWindowBase::layerAdded(" << layer << ")" << endl;
4164  updateMenuStates();
4165 }
4166 
4167 void
4169 {
4170  Profiler profiler("MainWindowBase::layerRemoved");
4171 // SVDEBUG << "MainWindowBase::layerRemoved(" << layer << ")" << endl;
4172  updateMenuStates();
4173 }
4174 
4175 void
4177 {
4178 // SVDEBUG << "MainWindowBase::layerAboutToBeDeleted(" << layer << ")" << endl;
4179 
4180  removeLayerEditDialog(layer);
4181 
4182  if (m_timeRulerLayer && (layer == m_timeRulerLayer)) {
4183 // cerr << "(this is the time ruler layer)" << endl;
4184  m_timeRulerLayer = nullptr;
4185  }
4186 }
4187 
4188 void
4189 MainWindowBase::layerInAView(Layer *layer, bool inAView)
4190 {
4191 // SVDEBUG << "MainWindowBase::layerInAView(" << layer << "," << inAView << ")" << endl;
4192 
4193  if (!inAView) removeLayerEditDialog(layer);
4194 
4195  // Check whether we need to add or remove model from play source
4196  ModelId modelId = layer->getModel();
4197  if (!modelId.isNone()) {
4198  if (inAView) {
4199  m_playSource->addModel(modelId);
4200  } else {
4201  bool found = false;
4202  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
4203  Pane *pane = m_paneStack->getPane(i);
4204  if (!pane) continue;
4205  for (int j = 0; j < pane->getLayerCount(); ++j) {
4206  Layer *pl = pane->getLayer(j);
4207  if (pl &&
4208  !dynamic_cast<TimeRulerLayer *>(pl) &&
4209  (pl->getModel() == modelId)) {
4210  found = true;
4211  break;
4212  }
4213  }
4214  if (found) break;
4215  }
4216  if (!found) {
4217  m_playSource->removeModel(modelId);
4218  }
4219  }
4220  }
4221 
4222  updateMenuStates();
4223 }
4224 
4225 void
4227 {
4228  if (m_layerDataDialogMap.find(layer) != m_layerDataDialogMap.end()) {
4229 
4230  ModelDataTableDialog *dialog = m_layerDataDialogMap[layer];
4231 
4232  for (ViewDataDialogMap::iterator vi = m_viewDataDialogMap.begin();
4233  vi != m_viewDataDialogMap.end(); ++vi) {
4234  vi->second.erase(dialog);
4235  }
4236 
4237  m_layerDataDialogMap.erase(layer);
4238  delete dialog;
4239  }
4240 }
4241 
4242 void
4244 {
4245 // SVDEBUG << "MainWindowBase::modelAdded(" << model << ")" << endl;
4246  std::cerr << "\nAdding model " << model << " to playsource " << std::endl;
4247  m_playSource->addModel(model);
4248 }
4249 
4250 void
4252 {
4253 // SVDEBUG << "MainWindowBase::mainModelChanged(" << model << ")" << endl;
4255  auto model = ModelById::getAs<WaveFileModel>(modelId);
4256  if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate());
4257  if (model && !(m_playTarget || m_audioIO) && (m_audioMode != AUDIO_NONE)) {
4258  createAudioIO();
4259  }
4260 }
4261 
4262 void
4264 {
4265  bool found = false;
4266  for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
4267  if (m_paneStack->getPane(i) == pane) {
4268  found = true;
4269  break;
4270  }
4271  }
4272  if (!found) {
4273  SVDEBUG << "MainWindowBase::paneDeleteButtonClicked: Unknown pane "
4274  << pane << endl;
4275  return;
4276  }
4277 
4278  CommandHistory::getInstance()->startCompoundOperation
4279  (tr("Delete Pane"), true);
4280 
4281  while (pane->getLayerCount() > 0) {
4282  Layer *layer = pane->getLayer(pane->getLayerCount() - 1);
4283  if (layer) {
4284  m_document->removeLayerFromView(pane, layer);
4285  } else {
4286  break;
4287  }
4288  }
4289 
4290  RemovePaneCommand *command = new RemovePaneCommand(this, pane);
4291  CommandHistory::getInstance()->addCommand(command);
4292 
4293  CommandHistory::getInstance()->endCompoundOperation();
4294 
4295  updateMenuStates();
4296 }
4297 
4298 void
4299 MainWindowBase::alignmentComplete(ModelId alignmentModelId)
4300 {
4301  cerr << "MainWindowBase::alignmentComplete(" << alignmentModelId << ")" << endl;
4302 }
4303 
4304 void
4306 {
4307  if (!m_oscQueue || m_oscQueue->isEmpty()) return;
4308 
4309  if (m_handlingOSC) {
4310  SVDEBUG << "MainWindowBase::pollOSC: "
4311  << "not making nested invocations, waiting"
4312  << endl;
4313  return;
4314  }
4315 
4316  m_handlingOSC = true;
4317 
4318  while (!m_oscQueue->isEmpty()) {
4319 
4320  if (m_openingAudioFile) {
4321  SVDEBUG << "MainWindowBase::pollOSC: "
4322  << "waiting for audio to finish loading"
4323  << endl;
4324  m_handlingOSC = false;
4325  return;
4326  }
4327 
4328  if (ModelTransformerFactory::getInstance()->haveRunningTransformers()) {
4329  SVDEBUG << "MainWindowBase::pollOSC: "
4330  << "waiting for running transforms to complete"
4331  << endl;
4332  m_handlingOSC = false;
4333  return;
4334  }
4335 
4336  SVDEBUG << "MainWindowBase::pollOSC: have "
4337  << m_oscQueue->getMessagesAvailable()
4338  << " messages" << endl;
4339 
4340  OSCMessage message = m_oscQueue->readMessage();
4341 
4342  if (message.getTarget() != 0) {
4343  SVCERR << "MainWindowBase::pollOSC: ignoring message with target "
4344  << message.getTarget() << " (we are target 0)" << endl;
4345  m_handlingOSC = false;
4346  continue;
4347  }
4348 
4349  handleOSCMessage(message);
4350 
4351  disconnect(m_oscQueue, SIGNAL(messagesAvailable()),
4352  this, SLOT(pollOSC()));
4353 
4354  QApplication::processEvents(QEventLoop::ExcludeUserInputEvents |
4355  QEventLoop::ExcludeSocketNotifiers);
4356  }
4357 
4358  m_handlingOSC = false;
4359 
4360  connect(m_oscQueue, SIGNAL(messagesAvailable()),
4361  this, SLOT(pollOSC()));
4362 }
4363 
4364 void
4366 {
4367  Pane *currentPane = nullptr;
4368  if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
4369  if (currentPane) {
4370  //cerr << "JTEST: mouse event on selection pane" << endl;
4371  updateVisibleRangeDisplay(currentPane);
4372  }
4373 }
4374 
4375 void
4377 {
4378  QLabel *lab = getStatusLabel();
4379 
4380  if (s == "" && m_myStatusMessage != "") {
4381  if (lab->text() != m_myStatusMessage) {
4382  lab->setText(m_myStatusMessage);
4383  }
4384  return;
4385  }
4386 
4387  lab->setText(s);
4388 }
4389 
4390 void
4392 {
4393  // This method mostly lifted from Qt Assistant source code
4394 
4395  QProcess *process = new QProcess(this);
4396  connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
4397 
4398  QStringList args;
4399 
4400 #ifdef Q_OS_MAC
4401  args.append(url);
4402  process->start("open", args);
4403 #else
4404 #ifdef Q_OS_WIN32
4405  std::string pfiles;
4406  (void)getEnvUtf8("ProgramFiles", pfiles);
4407  QString command =
4408  QString::fromStdString(pfiles) +
4409  QString("\\Internet Explorer\\IEXPLORE.EXE");
4410 
4411  args.append(url);
4412  process->start(command, args);
4413 #else
4414  if (!qgetenv("KDE_FULL_SESSION").isEmpty()) {
4415  args.append("exec");
4416  args.append(url);
4417  process->start("kfmclient", args);
4418  } else if (!qgetenv("BROWSER").isEmpty()) {
4419  args.append(url);
4420  process->start(qgetenv("BROWSER"), args);
4421  } else {
4422  args.append(url);
4423  process->start("firefox", args);
4424  }
4425 #endif
4426 #endif
4427 }
4428 
4429 void
4431 {
4432  QDir d(path);
4433  if (d.exists()) {
4434  QStringList args;
4435  QString path = d.canonicalPath();
4436 #if defined Q_OS_WIN32
4437  // Although the Win32 API is quite happy to have
4438  // forward slashes as directory separators, Windows
4439  // Explorer is not
4440  path = path.replace('/', '\\');
4441  args << path;
4442  QProcess::execute("c:/windows/explorer.exe", args);
4443 #else
4444  args << path;
4445  QProcess process;
4446  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
4447  env.insert("LD_LIBRARY_PATH", "");
4448  process.setProcessEnvironment(env);
4449  process.start(
4450 #if defined Q_OS_MAC
4451  "/usr/bin/open",
4452 #else
4453  "/usr/bin/xdg-open",
4454 #endif
4455  args);
4456  process.waitForFinished();
4457 #endif
4458  }
4459 }
4460 
AudioCallbackRecordTarget * m_recordTarget
virtual void playSoloToggled()
void setMainModel(ModelId)
Set the main model (the source for playback sample rate, etc) to the given wave file model...
Definition: Document.cpp:421
void canSelectNextPane(bool)
AudioMode m_audioMode
void canMeasureLayer(bool)
virtual void toXml(QTextStream &stream, bool asTemplate)
virtual QString getSaveFileName(FileFinder::FileType type)
void cueOSCScript(QString filename)
virtual void documentModified()
Open a MIDI device and listen for MIDI input.
virtual void registerLastOpenedFilePath(FileFinder::FileType type, QString path)
virtual void stop()
virtual void rewind()
QLabel * m_statusLabel
virtual bool shouldCreateNewSessionForRDFAudio(bool *)
OSCQueueStarter * m_oscQueueStarter
virtual bool exportLayerToCSV(Layer *layer, LayerGeometryProvider *provider, MultiSelection *selectionsToWrite, QString delimiter, DataExportOptions options, QString toPath, QString &error)
MIDIMode
Determine whether to open a MIDI input device.
virtual void clearSelection()
virtual void monitoringLevelsChanged(float, float)=0
QLabel * getStatusLabel() const
RecentFiles m_recentFiles
virtual void paneRightButtonMenuRequested(Pane *, QPoint point)=0
RealTime m_defaultFfwdRwdStep
virtual void jumpRight()
virtual void zoomDefault()
virtual void previousPane()
virtual void peekLeft()
void canRewind(bool)
virtual void editCurrentLayer()
virtual void rewindSimilar()
virtual void preferenceChanged(PropertyContainer::PropertyName)
virtual void insertInstantAt(sv_frame_t)
void canSelectNextLayer(bool)
virtual void showNoOverlays()
void canAddPane(bool)
virtual void currentPaneChanged(Pane *)
The base class for the SV main window.
virtual bool saveSessionFile(QString path)
void canPlay(bool)
virtual void panePropertiesRightButtonMenuRequested(Pane *, QPoint point)=0
virtual void documentRestored()
QString m_oscScriptFile
void canEditLayer(bool)
breakfastquay::SystemPlaybackTarget * m_playTarget
virtual void findTimeRulerLayer()
void canPaste(bool)
void canExportImage(bool)
virtual void layerInAView(Layer *, bool)
virtual void record()
virtual void winnowInstantsBy(int)
void canSelectPreviousLayer(bool)
virtual void alignmentFailed(ModelId, QString)=0
sv_frame_t getFrame() const override
Implementation of FrameTimer interface method.
virtual void peekRight()
void canZoom(bool)
virtual void sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)=0
void canSelectPreviousPane(bool)
virtual void layerPropertiesRightButtonMenuRequested(Pane *, Layer *, QPoint point)=0
virtual void zoomOut()
virtual void playLoopToggled()
void addNonDerivedModel(ModelId)
Add an imported model, i.e.
Definition: Document.cpp:645
void audioFileLoaded()
Layer * createMainModelLayer(LayerFactory::LayerType)
Create and return a new layer of the given type, associated with the current main model (if appropria...
Definition: Document.cpp:140
void canRecord(bool)
virtual void contextHelpChanged(const QString &)
SVFileReader loads Sonic Visualiser XML files.
Definition: SVFileReader.h:166
virtual void modelAdded(ModelId)
virtual void play()
virtual void audioChannelCountIncreased(int count)
virtual void zoomToFit()
virtual void paneDropAccepted(Pane *, QStringList)=0
sv_frame_t getModelsStartFrame() const
QList< QShortcut * > m_appShortcuts
void removeLayerEditDialog(Layer *)
QString getName() const override
bool m_initialDarkBackground
QString m_sessionFile
virtual void updateDescriptionLabel()=0
void canImportLayer(bool)
virtual void layerAdded(Layer *)
void canEditSelection(bool)
virtual bool exportLayerToMIDI(Layer *layer, MultiSelection *selectionsToWrite, QString toPath, QString &error)
virtual void deleteCurrentPane()
virtual void viewZoomLevelChanged(View *, ZoomLevel, bool)
virtual FileOpenStatus openAudio(FileSource source, AudioFileOpenMode=AskUser, QString templateName="")
virtual void scrollRight()
void canImportMoreAudio(bool)
virtual void alignmentComplete(ModelId)
virtual QString getOpenFileName(FileFinder::FileType type)
virtual FileOpenStatus openPath(QString fileOrUrl, AudioFileOpenMode=AskUser)
virtual void insertInstant()
void canSubdivideInstants(bool)
breakfastquay::SystemAudioIO * m_audioIO
void canRenameLayer(bool)
void log(std::string message) const override
Open for playback, never for recording.
ModelId getMainModel()
Get the main model (the source for playback sample rate, etc).
Definition: Document.h:195
virtual void deleteCurrentLayer()
virtual void paste()
virtual void selectVisible()
void canChangeSessionTemplate(bool)
void canSelect(bool)
void canSave(bool)
virtual void ffwd()
virtual void togglePropertyBoxes()
virtual void updateVisibleRangeDisplay(Pane *p) const =0
Open for playback when model loaded, switch to I/O if record called.
virtual void emitHideSplash()
virtual bool checkSaveModified()=0
virtual void recordDurationChanged(sv_frame_t, sv_samplerate_t)
virtual void audioOverloadPluginDisabled()=0
virtual void paneDeleteButtonClicked(Pane *)
virtual void updatePositionStatusDisplays() const =0
AudioMode
Determine what kind of audio device to open when the first model is loaded or record() is called...
QString getName() const override
virtual void oscScriptFinished()
void toXmlAsTemplate(QTextStream &, QString indent, QString extraAttributes) const
Definition: Document.cpp:1351
virtual void modelGenerationFailed(QString, QString)=0
ModelId getMainModelId() const
Document * m_document
static FileType identifyXmlFile(QString path)
FileOpenStatus addOpenedAudioModel(FileSource source, ModelId model, AudioFileOpenMode mode, QString templateName, bool registerSource)
virtual void ffwdSimilar()
MIDIMode m_midiMode
void canEditLayerTabular(bool)
void canDeleteCurrentPane(bool)
void removeLayerFromView(View *, Layer *)
Remove the given layer from the given view.
Definition: Document.cpp:1018
void canScroll(bool)
virtual void ffwdEnd()
virtual void scrollLeft()
virtual void updateWindowTitle()
virtual void toggleStatusBar()
Layer * createEmptyLayer(LayerFactory::LayerType)
Create and return a new layer of the given type, with an appropriate empty model. ...
Definition: Document.cpp:189
virtual void paneAboutToBeDeleted(Pane *)=0
virtual bool exportLayerToRDF(Layer *layer, QString toPath, QString &error)
virtual void inProgressSelectionChanged()
Layer * getSnapLayer() const
QSignalMapper * m_menuShortcutMapper
virtual void layerRemoved(Layer *)
virtual void paneAdded(Pane *)=0
QString m_myStatusMessage
virtual void connectLayerEditDialog(ModelDataTableDialog *dialog)
std::shared_ptr< WaveFileModel > getMainModel() const
virtual void showMinimalOverlays()
virtual FileOpenStatus openLayer(FileSource source)
void canClearSelection(bool)
void sessionLoaded()
virtual void selectAll()
virtual void modelRegenerationFailed(QString, QString, QString)=0
virtual void updateMenuStates()
RemovePaneCommand(MainWindowBase *mw, Pane *pane)
virtual FileOpenStatus openSessionPath(QString fileOrUrl)
void toXml(QTextStream &, QString indent, QString extraAttributes) const override
Definition: Document.cpp:1345
virtual ~MainWindowBase()
virtual void modelRegenerationWarning(QString, QString, QString)=0
virtual QString getDefaultSessionTemplate() const
virtual void pasteAtPlaybackPosition()
virtual void showAllOverlays()
virtual void jumpLeft()
Open for I/O as soon as model loaded or record called.
virtual void currentLayerChanged(Pane *, Layer *)
virtual void viewCentreFrameChanged(View *, sv_frame_t)
void canReplaceMainAudio(bool)
virtual void setDefaultSessionTemplate(QString)
A Sonic Visualiser document consists of a set of data models, and also the visualisation layers used ...
Definition: Document.h:71
bool isIncomplete() const
Return true if any external files (most obviously audio) failed to be found on load, so that the document is incomplete compared to its saved description.
Definition: Document.h:307
void activity(QString)
virtual void paneHidden(Pane *)=0
virtual void mainModelChanged(ModelId)
virtual void pollOSC()
void addLayerToView(View *, Layer *)
Add the given layer to the given view.
Definition: Document.cpp:993
virtual void playSelectionToggled()
virtual void selectToStart()
void abandon()
Definition: OSCScript.h:120
virtual FileOpenStatus openPlaylist(FileSource source, AudioFileOpenMode=AskUser)
virtual void closeSession()=0
void startOSCQueue(bool withNetworkPort)
ViewDataDialogMap m_viewDataDialogMap
MIDIInput * m_midiInput
virtual FileOpenStatus openLayersFromRDF(FileSource source)
virtual void globalCentreFrameChanged(sv_frame_t)
virtual void previousLayer()
sv_frame_t getModelsEndFrame() const
void setAutoAlignment(bool on)
Specify whether models added via addImportedModel should be automatically aligned against the main mo...
Definition: Document.h:287
virtual void finaliseMenu(QMenu *)
virtual void openLocalFolder(QString path)
virtual FileOpenStatus openDirOfAudio(QString dirPath)
void canSaveAs(bool)
void canFfwd(bool)
virtual void zoomIn()
virtual void copy()
virtual void insertInstantsAtBoundaries()
virtual void layerAboutToBeDeleted(Layer *)
Open no audio device, ever.
virtual void recreateAudioIO()
virtual FileOpenStatus openSessionFromRDF(FileSource source)
virtual void menuActionMapperInvoked(QObject *)
virtual void toggleTimeRulers()
virtual void deleteSelected()
virtual FileOpenStatus openImage(FileSource source)
AddPaneCommand(MainWindowBase *mw)
virtual void createAudioIO()
void canExportLayer(bool)
virtual void playbackFrameChanged(sv_frame_t)
virtual void oscReady()
void canInsertItemAtSelection(bool)
AudioRecordMode m_audioRecordMode
Layer * m_timeRulerLayer
virtual bool exportLayerTo(Layer *layer, LayerGeometryProvider *provider, MultiSelection *selectionsToWrite, QString toPath, QString &error)
void canInsertInstantsAtBoundaries(bool)
void replacedDocument()
virtual void resizeConstrained(QSize)
virtual void openHelpUrl(QString url)
void deleteLayer(Layer *, bool force=false)
Delete the given layer, and also its associated model if no longer used by any other layer...
Definition: Document.cpp:889
OSCQueue * m_oscQueue
virtual void toggleZoomWheels()
void canInsertInstant(bool)
virtual FileOpenStatus openSessionTemplate(QString templateName)
virtual void pasteRelative(sv_frame_t offset)
virtual void selectToEnd()
virtual void finaliseMenus()
void canRenumberInstants(bool)
void canAddLayer(bool)
virtual void nextLayer()
MainWindowBase(AudioMode audioMode, MIDIMode midiMode, PaneStack::Options paneStackOptions)
OSCScript * m_oscScript
bool m_iconsVisibleInMenus
virtual bool exportLayerToSVL(Layer *layer, QString toPath, QString &error)
!! should we pull out the whole export logic into another
void canWinnowInstants(bool)
LayerDataDialogMap m_layerDataDialogMap
QString m_originalLocation
void canPlaySelection(bool)
virtual void nextPane()
virtual void insertItemAt(sv_frame_t, sv_frame_t)
virtual bool saveSessionTemplate(QString path)
virtual void deleteAudioIO()
PaneStack * m_paneStack
virtual void cut()
virtual void insertItemAtSelection()
void canExportAudio(bool)
Layer * createImportedLayer(ModelId)
Create and return a new layer associated with the given model, and register the model as an imported ...
Definition: Document.cpp:151
virtual void setupMenus()=0
virtual void renumberInstants()
virtual void rewindStart()
virtual FileOpenStatus openSession(FileSource source)
virtual void toggleCentreLine()
void canDeleteCurrentLayer(bool)
Labeller * m_labeller
AudioCallbackPlaySource * m_playSource
virtual void subdivideInstantsBy(int)
ViewManager * m_viewManager
virtual FileOpenStatus open(FileSource source, AudioFileOpenMode=AskUser)