annotate framework/MainWindowBase.cpp @ 758:5b6655449ba6

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