annotate framework/MainWindowBase.cpp @ 746:771ec060c1d2

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