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