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