annotate framework/MainWindowBase.cpp @ 759:5f8fc01e01c7

Never blunder ahead without a document at this point! (Could happen if first model was opened using OSC)
author Chris Cannam
date Wed, 29 Apr 2020 17:48:20 +0100
parents 5b6655449ba6
children 3a63f1f61bd6
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@759 1685 if (mode != ReplaceSession && !m_document) {
Chris@759 1686 SVDEBUG << "File open mode requested is something other than ReplaceSession, but we have no document at all yet, so we must use ReplaceSession mode" << endl;
Chris@759 1687 mode = ReplaceSession;
Chris@759 1688 }
Chris@759 1689
Chris@684 1690 if (mode == CreateAdditionalModel && getMainModelId().isNone()) {
Chris@759 1691 SVDEBUG << "File open mode requested is CreateAdditionalModel, but we have no main model, so switching to ReplaceSession mode" << endl;
Chris@221 1692 mode = ReplaceSession;
Chris@221 1693 }
Chris@221 1694
Chris@221 1695 bool loadedTemplate = false;
Chris@221 1696
Chris@221 1697 if (mode == ReplaceSession) {
Chris@258 1698
Chris@720 1699 if (!checkSaveModified()) {
Chris@720 1700 m_openingAudioFile = false;
Chris@720 1701 return FileOpenCancelled;
Chris@720 1702 }
Chris@258 1703
Chris@386 1704 SVDEBUG << "SV looking for template " << templateName << endl;
Chris@230 1705 if (templateName != "") {
Chris@230 1706 FileOpenStatus tplStatus = openSessionTemplate(templateName);
Chris@258 1707 if (tplStatus == FileOpenCancelled) {
Chris@577 1708 SVDEBUG << "Template load cancelled" << endl;
Chris@720 1709 m_openingAudioFile = false;
Chris@258 1710 return FileOpenCancelled;
Chris@258 1711 }
Chris@230 1712 if (tplStatus != FileOpenFailed) {
Chris@577 1713 SVDEBUG << "Template load succeeded" << endl;
Chris@230 1714 loadedTemplate = true;
Chris@221 1715 }
Chris@221 1716 }
Chris@221 1717
Chris@221 1718 if (!loadedTemplate) {
Chris@386 1719 SVDEBUG << "No template found: closing session, creating new empty document" << endl;
Chris@221 1720 closeSession();
Chris@221 1721 createDocument();
Chris@221 1722 }
Chris@221 1723
Chris@386 1724 SVDEBUG << "Now switching to ReplaceMainModel mode" << endl;
Chris@45 1725 mode = ReplaceMainModel;
Chris@45 1726 }
Chris@45 1727
Chris@164 1728 emit activity(tr("Import audio file \"%1\"").arg(source.getLocation()));
Chris@669 1729
Chris@45 1730 if (mode == ReplaceMainModel) {
Chris@45 1731
Chris@684 1732 ModelId prevMain = getMainModelId();
Chris@684 1733 if (!prevMain.isNone()) {
Chris@45 1734 m_playSource->removeModel(prevMain);
Chris@45 1735 }
Chris@45 1736
Chris@248 1737 SVDEBUG << "SV about to call setMainModel(" << newModel << "): prevMain is " << prevMain << endl;
Chris@248 1738
Chris@595 1739 m_document->setMainModel(newModel);
Chris@595 1740
Chris@595 1741 setupMenus();
Chris@595 1742
Chris@669 1743 m_originalLocation = source.getLocation();
Chris@669 1744
Chris@595 1745 if (loadedTemplate || (m_sessionFile == "")) {
Chris@595 1746 CommandHistory::getInstance()->clear();
Chris@595 1747 CommandHistory::getInstance()->documentSaved();
Chris@595 1748 m_documentModified = false;
Chris@595 1749 } else {
Chris@595 1750 if (m_documentModified) {
Chris@595 1751 m_documentModified = false;
Chris@595 1752 }
Chris@595 1753 }
Chris@45 1754
Chris@604 1755 if (!source.isRemote() && registerSource) {
Chris@604 1756 m_audioFile = source.getLocalFilename();
Chris@604 1757 }
Chris@45 1758
Chris@669 1759 updateWindowTitle();
Chris@669 1760
Chris@45 1761 } else if (mode == CreateAdditionalModel) {
Chris@45 1762
Chris@577 1763 SVCERR << "Mode is CreateAdditionalModel" << endl;
Chris@577 1764
Chris@595 1765 CommandHistory::getInstance()->startCompoundOperation
Chris@595 1766 (tr("Import \"%1\"").arg(source.getBasename()), true);
Chris@595 1767
Chris@691 1768 m_document->addNonDerivedModel(newModel);
Chris@595 1769
Chris@595 1770 AddPaneCommand *command = new AddPaneCommand(this);
Chris@595 1771 CommandHistory::getInstance()->addCommand(command);
Chris@595 1772
Chris@595 1773 Pane *pane = command->getPane();
Chris@45 1774
Chris@47 1775 if (m_timeRulerLayer) {
Chris@577 1776 SVCERR << "Have time ruler, adding it" << endl;
Chris@47 1777 m_document->addLayerToView(pane, m_timeRulerLayer);
Chris@577 1778 } else {
Chris@577 1779 SVCERR << "Do not have time ruler" << endl;
Chris@47 1780 }
Chris@45 1781
Chris@595 1782 Layer *newLayer = m_document->createImportedLayer(newModel);
Chris@595 1783
Chris@595 1784 if (newLayer) {
Chris@595 1785 m_document->addLayerToView(pane, newLayer);
Chris@595 1786 }
Chris@595 1787
Chris@595 1788 CommandHistory::getInstance()->endCompoundOperation();
Chris@45 1789
Chris@45 1790 } else if (mode == ReplaceCurrentPane) {
Chris@45 1791
Chris@45 1792 // We know there is a current pane, otherwise we would have
Chris@45 1793 // reset the mode to CreateAdditionalModel above; and we know
Chris@45 1794 // the current pane does not contain the main model, otherwise
Chris@45 1795 // we would have reset it to ReplaceMainModel. But we don't
Chris@45 1796 // know whether the pane contains a waveform model at all.
Chris@45 1797
Chris@45 1798 Pane *pane = m_paneStack->getCurrentPane();
Chris@636 1799 Layer *replace = nullptr;
Chris@45 1800
Chris@45 1801 for (int i = 0; i < pane->getLayerCount(); ++i) {
Chris@45 1802 Layer *layer = pane->getLayer(i);
Chris@45 1803 if (dynamic_cast<WaveformLayer *>(layer)) {
Chris@45 1804 replace = layer;
Chris@45 1805 break;
Chris@45 1806 }
Chris@45 1807 }
Chris@45 1808
Chris@595 1809 CommandHistory::getInstance()->startCompoundOperation
Chris@595 1810 (tr("Import \"%1\"").arg(source.getBasename()), true);
Chris@595 1811
Chris@691 1812 m_document->addNonDerivedModel(newModel);
Chris@45 1813
Chris@45 1814 if (replace) {
Chris@45 1815 m_document->removeLayerFromView(pane, replace);
Chris@45 1816 }
Chris@45 1817
Chris@595 1818 Layer *newLayer = m_document->createImportedLayer(newModel);
Chris@595 1819
Chris@595 1820 if (newLayer) {
Chris@595 1821 m_document->addLayerToView(pane, newLayer);
Chris@595 1822 }
Chris@595 1823
Chris@595 1824 CommandHistory::getInstance()->endCompoundOperation();
Chris@45 1825 }
Chris@45 1826
Chris@45 1827 updateMenuStates();
Chris@622 1828
Chris@622 1829 if (registerSource) {
Chris@622 1830 m_recentFiles.addFile(source.getLocation());
Chris@622 1831 }
Chris@604 1832 if (!source.isRemote() && registerSource) {
Chris@45 1833 // for file dialog
Chris@45 1834 registerLastOpenedFilePath(FileFinder::AudioFile,
Chris@45 1835 source.getLocalFilename());
Chris@45 1836 }
Chris@622 1837
Chris@45 1838 m_openingAudioFile = false;
Chris@45 1839
Chris@45 1840 currentPaneChanged(m_paneStack->getCurrentPane());
Chris@45 1841
Chris@342 1842 emit audioFileLoaded();
Chris@342 1843
Chris@45 1844 return FileOpenSucceeded;
Chris@45 1845 }
Chris@45 1846
Chris@45 1847 MainWindowBase::FileOpenStatus
Chris@45 1848 MainWindowBase::openPlaylist(FileSource source, AudioFileOpenMode mode)
Chris@45 1849 {
Chris@233 1850 SVDEBUG << "MainWindowBase::openPlaylist(" << source.getLocation() << ")" << endl;
Chris@135 1851
Chris@45 1852 std::set<QString> extensions;
Chris@45 1853 PlaylistFileReader::getSupportedExtensions(extensions);
Chris@152 1854 QString extension = source.getExtension().toLower();
Chris@45 1855 if (extensions.find(extension) == extensions.end()) return FileOpenFailed;
Chris@45 1856
Chris@45 1857 if (!source.isAvailable()) return FileOpenFailed;
Chris@45 1858 source.waitForData();
Chris@45 1859
Chris@45 1860 PlaylistFileReader reader(source.getLocalFilename());
Chris@45 1861 if (!reader.isOK()) return FileOpenFailed;
Chris@45 1862
Chris@45 1863 PlaylistFileReader::Playlist playlist = reader.load();
Chris@45 1864
Chris@45 1865 bool someSuccess = false;
Chris@45 1866
Chris@45 1867 for (PlaylistFileReader::Playlist::const_iterator i = playlist.begin();
Chris@45 1868 i != playlist.end(); ++i) {
Chris@45 1869
Chris@134 1870 ProgressDialog dialog(tr("Opening playlist..."), true, 2000, this);
Chris@134 1871 connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash()));
Chris@109 1872 FileOpenStatus status = openAudio(FileSource(*i, &dialog), mode);
Chris@45 1873
Chris@45 1874 if (status == FileOpenCancelled) {
Chris@45 1875 return FileOpenCancelled;
Chris@45 1876 }
Chris@45 1877
Chris@45 1878 if (status == FileOpenSucceeded) {
Chris@45 1879 someSuccess = true;
Chris@45 1880 mode = CreateAdditionalModel;
Chris@45 1881 }
Chris@45 1882 }
Chris@45 1883
Chris@45 1884 if (someSuccess) return FileOpenSucceeded;
Chris@45 1885 else return FileOpenFailed;
Chris@45 1886 }
Chris@45 1887
Chris@45 1888 MainWindowBase::FileOpenStatus
Chris@45 1889 MainWindowBase::openLayer(FileSource source)
Chris@45 1890 {
Chris@233 1891 SVDEBUG << "MainWindowBase::openLayer(" << source.getLocation() << ")" << endl;
Chris@135 1892
Chris@45 1893 Pane *pane = m_paneStack->getCurrentPane();
Chris@45 1894
Chris@45 1895 if (!pane) {
Chris@595 1896 // shouldn't happen, as the menu action should have been disabled
Chris@595 1897 cerr << "WARNING: MainWindowBase::openLayer: no current pane" << endl;
Chris@595 1898 return FileOpenWrongMode;
Chris@45 1899 }
Chris@45 1900
Chris@45 1901 if (!getMainModel()) {
Chris@595 1902 // shouldn't happen, as the menu action should have been disabled
Chris@595 1903 cerr << "WARNING: MainWindowBase::openLayer: No main model -- hence no default sample rate available" << endl;
Chris@595 1904 return FileOpenWrongMode;
Chris@45 1905 }
Chris@45 1906
Chris@45 1907 if (!source.isAvailable()) return FileOpenFailed;
Chris@45 1908 source.waitForData();
Chris@45 1909
Chris@45 1910 QString path = source.getLocalFilename();
Chris@45 1911
Chris@145 1912 RDFImporter::RDFDocumentType rdfType =
Chris@145 1913 RDFImporter::identifyDocumentType(QUrl::fromLocalFile(path).toString());
Chris@145 1914
Chris@293 1915 // cerr << "RDF type: (in layer) " << (int) rdfType << endl;
Chris@148 1916
Chris@145 1917 if (rdfType != RDFImporter::NotRDF) {
Chris@145 1918
Chris@145 1919 return openLayersFromRDF(source);
Chris@134 1920
Chris@152 1921 } else if (source.getExtension().toLower() == "svl" ||
Chris@152 1922 (source.getExtension().toLower() == "xml" &&
Chris@140 1923 (SVFileReader::identifyXmlFile(source.getLocalFilename())
Chris@140 1924 == SVFileReader::SVLayerFile))) {
Chris@45 1925
Chris@45 1926 PaneCallback callback(this);
Chris@45 1927 QFile file(path);
Chris@45 1928
Chris@45 1929 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
Chris@293 1930 cerr << "ERROR: MainWindowBase::openLayer("
Chris@294 1931 << source.getLocation()
Chris@293 1932 << "): Failed to open file for reading" << endl;
Chris@45 1933 return FileOpenFailed;
Chris@45 1934 }
Chris@45 1935
Chris@45 1936 SVFileReader reader(m_document, callback, source.getLocation());
Chris@79 1937 connect
Chris@79 1938 (&reader, SIGNAL(modelRegenerationFailed(QString, QString, QString)),
Chris@79 1939 this, SLOT(modelRegenerationFailed(QString, QString, QString)));
Chris@79 1940 connect
Chris@79 1941 (&reader, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
Chris@79 1942 this, SLOT(modelRegenerationWarning(QString, QString, QString)));
Chris@45 1943 reader.setCurrentPane(pane);
Chris@45 1944
Chris@45 1945 QXmlInputSource inputSource(&file);
Chris@45 1946 reader.parse(inputSource);
Chris@45 1947
Chris@45 1948 if (!reader.isOK()) {
Chris@293 1949 cerr << "ERROR: MainWindowBase::openLayer("
Chris@294 1950 << source.getLocation()
Chris@45 1951 << "): Failed to read XML file: "
Chris@293 1952 << reader.getErrorString() << endl;
Chris@45 1953 return FileOpenFailed;
Chris@45 1954 }
Chris@45 1955
Chris@164 1956 emit activity(tr("Import layer XML file \"%1\"").arg(source.getLocation()));
Chris@164 1957
Chris@45 1958 m_recentFiles.addFile(source.getLocation());
Chris@45 1959
Chris@45 1960 if (!source.isRemote()) {
Chris@45 1961 registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog
Chris@45 1962 }
Chris@45 1963
Chris@75 1964 return FileOpenSucceeded;
Chris@75 1965
Chris@45 1966 } else {
Chris@45 1967
Chris@45 1968 try {
Chris@45 1969
Chris@109 1970 MIDIFileImportDialog midiDlg(this);
Chris@109 1971
Chris@684 1972 Model *newModelPtr = DataFileReaderFactory::loadNonCSV
Chris@109 1973 (path, &midiDlg, getMainModel()->getSampleRate());
Chris@45 1974
Chris@684 1975 if (!newModelPtr) {
Chris@643 1976 CSVFormatDialog *dialog =
Chris@643 1977 new CSVFormatDialog(this,
Chris@643 1978 path,
Chris@643 1979 getMainModel()->getSampleRate(),
Chris@643 1980 5);
Chris@109 1981 if (dialog->exec() == QDialog::Accepted) {
Chris@684 1982 newModelPtr = DataFileReaderFactory::loadCSV
Chris@109 1983 (path, dialog->getFormat(),
Chris@109 1984 getMainModel()->getSampleRate());
Chris@109 1985 }
Chris@619 1986 delete dialog;
Chris@109 1987 }
Chris@109 1988
Chris@684 1989 if (newModelPtr) {
Chris@45 1990
Chris@233 1991 SVDEBUG << "MainWindowBase::openLayer: Have model" << endl;
Chris@45 1992
Chris@164 1993 emit activity(tr("Import MIDI file \"%1\"").arg(source.getLocation()));
Chris@164 1994
Chris@687 1995 ModelId modelId =
Chris@687 1996 ModelById::add(std::shared_ptr<Model>(newModelPtr));
Chris@684 1997
Chris@684 1998 Layer *newLayer = m_document->createImportedLayer(modelId);
Chris@45 1999
Chris@45 2000 if (newLayer) {
Chris@45 2001
Chris@45 2002 m_document->addLayerToView(pane, newLayer);
Chris@88 2003 m_paneStack->setCurrentLayer(pane, newLayer);
Chris@88 2004
Chris@45 2005 m_recentFiles.addFile(source.getLocation());
Chris@45 2006
Chris@45 2007 if (!source.isRemote()) {
Chris@45 2008 registerLastOpenedFilePath
Chris@45 2009 (FileFinder::LayerFile,
Chris@45 2010 path); // for file dialog
Chris@45 2011 }
Chris@88 2012
Chris@45 2013 return FileOpenSucceeded;
Chris@45 2014 }
Chris@45 2015 }
Chris@45 2016 } catch (DataFileReaderFactory::Exception e) {
Chris@45 2017 if (e == DataFileReaderFactory::ImportCancelled) {
Chris@45 2018 return FileOpenCancelled;
Chris@45 2019 }
Chris@45 2020 }
Chris@45 2021 }
Chris@45 2022
Chris@45 2023 return FileOpenFailed;
Chris@45 2024 }
Chris@45 2025
Chris@45 2026 MainWindowBase::FileOpenStatus
Chris@45 2027 MainWindowBase::openImage(FileSource source)
Chris@45 2028 {
Chris@233 2029 SVDEBUG << "MainWindowBase::openImage(" << source.getLocation() << ")" << endl;
Chris@135 2030
Chris@45 2031 Pane *pane = m_paneStack->getCurrentPane();
Chris@45 2032
Chris@45 2033 if (!pane) {
Chris@595 2034 // shouldn't happen, as the menu action should have been disabled
Chris@595 2035 cerr << "WARNING: MainWindowBase::openImage: no current pane" << endl;
Chris@595 2036 return FileOpenWrongMode;
Chris@45 2037 }
Chris@45 2038
Chris@684 2039 if (!getMainModel()) {
Chris@45 2040 return FileOpenWrongMode;
Chris@45 2041 }
Chris@45 2042
Chris@45 2043 bool newLayer = false;
Chris@45 2044
Chris@45 2045 ImageLayer *il = dynamic_cast<ImageLayer *>(pane->getSelectedLayer());
Chris@45 2046 if (!il) {
Chris@45 2047 for (int i = pane->getLayerCount()-1; i >= 0; --i) {
Chris@45 2048 il = dynamic_cast<ImageLayer *>(pane->getLayer(i));
Chris@45 2049 if (il) break;
Chris@45 2050 }
Chris@45 2051 }
Chris@45 2052 if (!il) {
Chris@45 2053 il = dynamic_cast<ImageLayer *>
Chris@45 2054 (m_document->createEmptyLayer(LayerFactory::Image));
Chris@45 2055 if (!il) return FileOpenFailed;
Chris@45 2056 newLayer = true;
Chris@45 2057 }
Chris@45 2058
Chris@45 2059 // We don't put the image file in Recent Files
Chris@45 2060
Chris@293 2061 cerr << "openImage: trying location \"" << source.getLocation() << "\" in image layer" << endl;
Chris@45 2062
Chris@45 2063 if (!il->addImage(m_viewManager->getGlobalCentreFrame(), source.getLocation())) {
Chris@45 2064 if (newLayer) {
Chris@52 2065 m_document->deleteLayer(il); // also releases its model
Chris@45 2066 }
Chris@45 2067 return FileOpenFailed;
Chris@45 2068 } else {
Chris@45 2069 if (newLayer) {
Chris@45 2070 m_document->addLayerToView(pane, il);
Chris@45 2071 }
Chris@45 2072 m_paneStack->setCurrentLayer(pane, il);
Chris@45 2073 }
Chris@45 2074
Chris@45 2075 return FileOpenSucceeded;
Chris@45 2076 }
Chris@45 2077
Chris@45 2078 MainWindowBase::FileOpenStatus
Chris@427 2079 MainWindowBase::openDirOfAudio(QString dirPath)
Chris@427 2080 {
Chris@427 2081 QDir dir(dirPath);
Chris@427 2082 QStringList files = dir.entryList(QDir::Files | QDir::Readable);
Chris@427 2083 files.sort();
Chris@427 2084
Chris@427 2085 FileOpenStatus status = FileOpenFailed;
Chris@427 2086 bool first = true;
Chris@427 2087 bool cancelled = false;
Chris@427 2088
Chris@427 2089 foreach (QString file, files) {
Chris@427 2090
Chris@427 2091 FileSource source(dir.filePath(file));
Chris@427 2092 if (!source.isAvailable()) {
Chris@427 2093 continue;
Chris@427 2094 }
Chris@427 2095
Chris@427 2096 if (AudioFileReaderFactory::getKnownExtensions().contains
Chris@427 2097 (source.getExtension().toLower())) {
Chris@427 2098
Chris@427 2099 AudioFileOpenMode mode = CreateAdditionalModel;
Chris@427 2100 if (first) mode = ReplaceSession;
Chris@427 2101
Chris@427 2102 switch (openAudio(source, mode)) {
Chris@427 2103 case FileOpenSucceeded:
Chris@427 2104 status = FileOpenSucceeded;
Chris@427 2105 first = false;
Chris@427 2106 break;
Chris@427 2107 case FileOpenFailed:
Chris@427 2108 break;
Chris@427 2109 case FileOpenCancelled:
Chris@427 2110 cancelled = true;
Chris@427 2111 break;
Chris@427 2112 case FileOpenWrongMode:
Chris@427 2113 break;
Chris@427 2114 }
Chris@427 2115 }
Chris@427 2116
Chris@427 2117 if (cancelled) break;
Chris@427 2118 }
Chris@427 2119
Chris@427 2120 return status;
Chris@427 2121 }
Chris@427 2122
Chris@427 2123 MainWindowBase::FileOpenStatus
Chris@373 2124 MainWindowBase::openSessionPath(QString fileOrUrl)
Chris@45 2125 {
Chris@134 2126 ProgressDialog dialog(tr("Opening session..."), true, 2000, this);
Chris@134 2127 connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash()));
Chris@109 2128 return openSession(FileSource(fileOrUrl, &dialog));
Chris@45 2129 }
Chris@45 2130
Chris@45 2131 MainWindowBase::FileOpenStatus
Chris@45 2132 MainWindowBase::openSession(FileSource source)
Chris@45 2133 {
Chris@233 2134 SVDEBUG << "MainWindowBase::openSession(" << source.getLocation() << ")" << endl;
Chris@135 2135
Chris@45 2136 if (!source.isAvailable()) return FileOpenFailed;
Chris@145 2137 source.waitForData();
Chris@141 2138
Chris@341 2139 QString sessionExt =
Chris@341 2140 InteractiveFileFinder::getInstance()->getApplicationSessionExtension();
Chris@341 2141
Chris@341 2142 if (source.getExtension().toLower() != sessionExt) {
Chris@145 2143
Chris@145 2144 RDFImporter::RDFDocumentType rdfType =
Chris@145 2145 RDFImporter::identifyDocumentType
Chris@145 2146 (QUrl::fromLocalFile(source.getLocalFilename()).toString());
Chris@145 2147
Chris@293 2148 // cerr << "RDF type: " << (int)rdfType << endl;
Chris@148 2149
Chris@145 2150 if (rdfType == RDFImporter::AudioRefAndAnnotations ||
Chris@145 2151 rdfType == RDFImporter::AudioRef) {
Chris@145 2152 return openSessionFromRDF(source);
Chris@145 2153 } else if (rdfType != RDFImporter::NotRDF) {
Chris@145 2154 return FileOpenFailed;
Chris@145 2155 }
Chris@145 2156
Chris@152 2157 if (source.getExtension().toLower() == "xml") {
Chris@140 2158 if (SVFileReader::identifyXmlFile(source.getLocalFilename()) ==
Chris@140 2159 SVFileReader::SVSessionFile) {
Chris@293 2160 cerr << "This XML file looks like a session file, attempting to open it as a session" << endl;
Chris@140 2161 } else {
Chris@140 2162 return FileOpenFailed;
Chris@140 2163 }
Chris@140 2164 } else {
Chris@140 2165 return FileOpenFailed;
Chris@140 2166 }
Chris@140 2167 }
Chris@45 2168
Chris@636 2169 QXmlInputSource *inputSource = nullptr;
Chris@636 2170 BZipFileDevice *bzFile = nullptr;
Chris@636 2171 QFile *rawFile = nullptr;
Chris@140 2172
Chris@341 2173 if (source.getExtension().toLower() == sessionExt) {
Chris@140 2174 bzFile = new BZipFileDevice(source.getLocalFilename());
Chris@140 2175 if (!bzFile->open(QIODevice::ReadOnly)) {
Chris@140 2176 delete bzFile;
Chris@140 2177 return FileOpenFailed;
Chris@140 2178 }
Chris@140 2179 inputSource = new QXmlInputSource(bzFile);
Chris@140 2180 } else {
Chris@140 2181 rawFile = new QFile(source.getLocalFilename());
Chris@140 2182 inputSource = new QXmlInputSource(rawFile);
Chris@140 2183 }
Chris@140 2184
Chris@140 2185 if (!checkSaveModified()) {
Chris@140 2186 if (bzFile) bzFile->close();
Chris@140 2187 delete inputSource;
Chris@140 2188 delete bzFile;
Chris@140 2189 delete rawFile;
Chris@140 2190 return FileOpenCancelled;
Chris@140 2191 }
Chris@45 2192
Chris@45 2193 QString error;
Chris@45 2194 closeSession();
Chris@45 2195 createDocument();
Chris@45 2196
Chris@45 2197 PaneCallback callback(this);
Chris@45 2198 m_viewManager->clearSelections();
Chris@45 2199
Chris@45 2200 SVFileReader reader(m_document, callback, source.getLocation());
Chris@79 2201 connect
Chris@79 2202 (&reader, SIGNAL(modelRegenerationFailed(QString, QString, QString)),
Chris@79 2203 this, SLOT(modelRegenerationFailed(QString, QString, QString)));
Chris@79 2204 connect
Chris@79 2205 (&reader, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
Chris@79 2206 this, SLOT(modelRegenerationWarning(QString, QString, QString)));
Chris@140 2207
Chris@140 2208 reader.parse(*inputSource);
Chris@45 2209
Chris@45 2210 if (!reader.isOK()) {
Chris@45 2211 error = tr("SV XML file read error:\n%1").arg(reader.getErrorString());
Chris@45 2212 }
Chris@45 2213
Chris@140 2214 if (bzFile) bzFile->close();
Chris@140 2215
Chris@140 2216 delete inputSource;
Chris@140 2217 delete bzFile;
Chris@140 2218 delete rawFile;
Chris@45 2219
Chris@45 2220 bool ok = (error == "");
Chris@45 2221
Chris@45 2222 if (ok) {
Chris@45 2223
Chris@164 2224 emit activity(tr("Import session file \"%1\"").arg(source.getLocation()));
Chris@164 2225
Chris@601 2226 if (!source.isRemote() && !m_document->isIncomplete()) {
Chris@601 2227 // Setting the session file path enables the Save (as
Chris@601 2228 // opposed to Save As...) option. We can't do this if we
Chris@601 2229 // don't have a local path to save to, but we also don't
Chris@601 2230 // want to do it if we failed to find an audio file or
Chris@601 2231 // similar on load, as the audio reference would then end
Chris@601 2232 // up being lost from any saved or auto-saved-on-exit copy
Chris@601 2233 m_sessionFile = source.getLocalFilename();
Chris@602 2234 } else {
Chris@602 2235 QMessageBox::warning
Chris@602 2236 (this,
Chris@602 2237 tr("Incomplete session loaded"),
Chris@603 2238 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 2239 QMessageBox::Ok);
Chris@601 2240 }
Chris@595 2241
Chris@669 2242 updateWindowTitle();
Chris@595 2243 setupMenus();
Chris@577 2244 findTimeRulerLayer();
Chris@45 2245
Chris@595 2246 CommandHistory::getInstance()->clear();
Chris@595 2247 CommandHistory::getInstance()->documentSaved();
Chris@595 2248 m_documentModified = false;
Chris@595 2249 updateMenuStates();
Chris@45 2250
Chris@227 2251 m_recentFiles.addFile(source.getLocation());
Chris@45 2252
Chris@45 2253 if (!source.isRemote()) {
Chris@45 2254 // for file dialog
Chris@45 2255 registerLastOpenedFilePath(FileFinder::SessionFile,
Chris@227 2256 source.getLocalFilename());
Chris@45 2257 }
Chris@45 2258
Chris@669 2259 m_originalLocation = source.getLocation();
Chris@669 2260
Chris@342 2261 emit sessionLoaded();
Chris@342 2262
Chris@669 2263 updateWindowTitle();
Chris@45 2264 }
Chris@669 2265
Chris@45 2266 return ok ? FileOpenSucceeded : FileOpenFailed;
Chris@45 2267 }
Chris@45 2268
Chris@141 2269 MainWindowBase::FileOpenStatus
Chris@230 2270 MainWindowBase::openSessionTemplate(QString templateName)
Chris@230 2271 {
Chris@230 2272 // Template in the user's template directory takes
Chris@230 2273 // priority over a bundled one; we don't unbundle, but
Chris@230 2274 // open directly from the bundled file (where applicable)
Chris@230 2275 ResourceFinder rf;
Chris@230 2276 QString tfile = rf.getResourcePath("templates", templateName + ".svt");
Chris@230 2277 if (tfile != "") {
Chris@294 2278 cerr << "SV loading template file " << tfile << endl;
Chris@230 2279 return openSessionTemplate(FileSource("file:" + tfile));
Chris@230 2280 } else {
Chris@230 2281 return FileOpenFailed;
Chris@230 2282 }
Chris@230 2283 }
Chris@230 2284
Chris@230 2285 MainWindowBase::FileOpenStatus
Chris@227 2286 MainWindowBase::openSessionTemplate(FileSource source)
Chris@227 2287 {
Chris@294 2288 cerr << "MainWindowBase::openSessionTemplate(" << source.getLocation() << ")" << endl;
Chris@227 2289
Chris@227 2290 if (!source.isAvailable()) return FileOpenFailed;
Chris@227 2291 source.waitForData();
Chris@227 2292
Chris@636 2293 QXmlInputSource *inputSource = nullptr;
Chris@636 2294 QFile *file = nullptr;
Chris@227 2295
Chris@227 2296 file = new QFile(source.getLocalFilename());
Chris@227 2297 inputSource = new QXmlInputSource(file);
Chris@227 2298
Chris@227 2299 if (!checkSaveModified()) {
Chris@227 2300 delete inputSource;
Chris@227 2301 delete file;
Chris@227 2302 return FileOpenCancelled;
Chris@227 2303 }
Chris@227 2304
Chris@227 2305 QString error;
Chris@227 2306 closeSession();
Chris@227 2307 createDocument();
Chris@227 2308
Chris@227 2309 PaneCallback callback(this);
Chris@227 2310 m_viewManager->clearSelections();
Chris@227 2311
Chris@227 2312 SVFileReader reader(m_document, callback, source.getLocation());
Chris@227 2313 connect
Chris@227 2314 (&reader, SIGNAL(modelRegenerationFailed(QString, QString, QString)),
Chris@227 2315 this, SLOT(modelRegenerationFailed(QString, QString, QString)));
Chris@227 2316 connect
Chris@227 2317 (&reader, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
Chris@227 2318 this, SLOT(modelRegenerationWarning(QString, QString, QString)));
Chris@227 2319
Chris@227 2320 reader.parse(*inputSource);
Chris@227 2321
Chris@227 2322 if (!reader.isOK()) {
Chris@227 2323 error = tr("SV XML file read error:\n%1").arg(reader.getErrorString());
Chris@227 2324 }
Chris@227 2325
Chris@227 2326 delete inputSource;
Chris@227 2327 delete file;
Chris@227 2328
Chris@227 2329 bool ok = (error == "");
Chris@227 2330
Chris@227 2331 if (ok) {
Chris@227 2332
Chris@227 2333 emit activity(tr("Open session template \"%1\"").arg(source.getLocation()));
Chris@227 2334
Chris@595 2335 setupMenus();
Chris@577 2336 findTimeRulerLayer();
Chris@227 2337
Chris@595 2338 CommandHistory::getInstance()->clear();
Chris@595 2339 CommandHistory::getInstance()->documentSaved();
Chris@595 2340 m_documentModified = false;
Chris@595 2341 updateMenuStates();
Chris@342 2342
Chris@342 2343 emit sessionLoaded();
Chris@227 2344 }
Chris@227 2345
Chris@669 2346 updateWindowTitle();
Chris@669 2347
Chris@227 2348 return ok ? FileOpenSucceeded : FileOpenFailed;
Chris@227 2349 }
Chris@227 2350
Chris@227 2351 MainWindowBase::FileOpenStatus
Chris@141 2352 MainWindowBase::openSessionFromRDF(FileSource source)
Chris@141 2353 {
Chris@233 2354 SVDEBUG << "MainWindowBase::openSessionFromRDF(" << source.getLocation() << ")" << endl;
Chris@141 2355
Chris@141 2356 if (!source.isAvailable()) return FileOpenFailed;
Chris@141 2357 source.waitForData();
Chris@141 2358
Chris@145 2359 if (!checkSaveModified()) {
Chris@145 2360 return FileOpenCancelled;
Chris@141 2361 }
Chris@143 2362
Chris@145 2363 closeSession();
Chris@145 2364 createDocument();
Chris@145 2365
Chris@145 2366 FileOpenStatus status = openLayersFromRDF(source);
Chris@141 2367
Chris@141 2368 setupMenus();
Chris@577 2369 findTimeRulerLayer();
Chris@669 2370
Chris@141 2371 CommandHistory::getInstance()->clear();
Chris@141 2372 CommandHistory::getInstance()->documentSaved();
Chris@141 2373 m_documentModified = false;
Chris@669 2374 updateWindowTitle();
Chris@145 2375
Chris@342 2376 emit sessionLoaded();
Chris@342 2377
Chris@145 2378 return status;
Chris@145 2379 }
Chris@145 2380
Chris@145 2381 MainWindowBase::FileOpenStatus
Chris@145 2382 MainWindowBase::openLayersFromRDF(FileSource source)
Chris@145 2383 {
Chris@435 2384 sv_samplerate_t rate = 0;
Chris@145 2385
Chris@233 2386 SVDEBUG << "MainWindowBase::openLayersFromRDF" << endl;
Chris@186 2387
Chris@145 2388 ProgressDialog dialog(tr("Importing from RDF..."), true, 2000, this);
Chris@145 2389 connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash()));
Chris@145 2390
Chris@145 2391 if (getMainModel()) {
Chris@145 2392 rate = getMainModel()->getSampleRate();
Chris@145 2393 } else if (Preferences::getInstance()->getResampleOnLoad()) {
Chris@552 2394 if (getMainModel()) {
Chris@552 2395 rate = getMainModel()->getSampleRate();
Chris@552 2396 }
Chris@145 2397 }
Chris@145 2398
Chris@145 2399 RDFImporter importer
Chris@145 2400 (QUrl::fromLocalFile(source.getLocalFilename()).toString(), rate);
Chris@145 2401
Chris@145 2402 if (!importer.isOK()) {
Chris@147 2403 if (importer.getErrorString() != "") {
Chris@147 2404 QMessageBox::critical
Chris@147 2405 (this, tr("Failed to import RDF"),
Chris@147 2406 tr("<b>Failed to import RDF</b><p>Importing data from RDF document at \"%1\" failed: %2</p>")
Chris@147 2407 .arg(source.getLocation()).arg(importer.getErrorString()));
Chris@147 2408 }
Chris@145 2409 return FileOpenFailed;
Chris@145 2410 }
Chris@145 2411
Chris@687 2412 std::vector<ModelId> modelIds = importer.getDataModels(&dialog);
Chris@145 2413
Chris@145 2414 dialog.setMessage(tr("Importing from RDF..."));
Chris@145 2415
Chris@687 2416 if (modelIds.empty()) {
Chris@186 2417 QMessageBox::critical
Chris@186 2418 (this, tr("Failed to import RDF"),
Chris@186 2419 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 2420 return FileOpenFailed;
Chris@145 2421 }
Chris@145 2422
Chris@164 2423 emit activity(tr("Import RDF document \"%1\"").arg(source.getLocation()));
Chris@684 2424
Chris@684 2425 std::set<ModelId> added;
Chris@684 2426
Chris@684 2427 for (auto modelId: modelIds) {
Chris@684 2428
Chris@684 2429 if (ModelById::isa<WaveFileModel>(modelId)) {
Chris@145 2430
Chris@145 2431 Pane *pane = addPaneToStack();
Chris@636 2432 Layer *layer = nullptr;
Chris@145 2433
Chris@145 2434 if (m_timeRulerLayer) {
Chris@145 2435 m_document->addLayerToView(pane, m_timeRulerLayer);
Chris@145 2436 }
Chris@145 2437
Chris@145 2438 if (!getMainModel()) {
Chris@684 2439 m_document->setMainModel(modelId);
Chris@145 2440 layer = m_document->createMainModelLayer(LayerFactory::Waveform);
Chris@145 2441 } else {
Chris@684 2442 layer = m_document->createImportedLayer(modelId);
Chris@145 2443 }
Chris@145 2444
Chris@145 2445 m_document->addLayerToView(pane, layer);
Chris@145 2446
Chris@684 2447 added.insert(modelId);
Chris@684 2448
Chris@684 2449 for (auto otherId: modelIds) {
Chris@684 2450
Chris@684 2451 if (otherId == modelId) continue;
Chris@684 2452
Chris@684 2453 bool isDependent = false;
Chris@684 2454 if (auto dm = ModelById::get(otherId)) {
Chris@684 2455 if (dm->getSourceModel() == modelId) {
Chris@684 2456 isDependent = true;
Chris@684 2457 }
Chris@684 2458 }
Chris@684 2459 if (!isDependent) continue;
Chris@684 2460
Chris@684 2461 layer = m_document->createImportedLayer(otherId);
Chris@145 2462
Chris@145 2463 if (layer->isLayerOpaque() ||
Chris@145 2464 dynamic_cast<Colour3DPlotLayer *>(layer)) {
Chris@145 2465
Chris@156 2466 // these always go in a new pane, with nothing
Chris@156 2467 // else going in the same pane
Chris@156 2468
Chris@145 2469 Pane *singleLayerPane = addPaneToStack();
Chris@145 2470 if (m_timeRulerLayer) {
Chris@145 2471 m_document->addLayerToView(singleLayerPane, m_timeRulerLayer);
Chris@145 2472 }
Chris@145 2473 m_document->addLayerToView(singleLayerPane, layer);
Chris@145 2474
Chris@156 2475 } else if (layer->getLayerColourSignificance() ==
Chris@156 2476 Layer::ColourHasMeaningfulValue) {
Chris@156 2477
Chris@156 2478 // these can go in a pane with something else, but
Chris@156 2479 // only if none of the something elses also have
Chris@156 2480 // this quality
Chris@156 2481
Chris@156 2482 bool needNewPane = false;
Chris@156 2483 for (int i = 0; i < pane->getLayerCount(); ++i) {
Chris@156 2484 Layer *otherLayer = pane->getLayer(i);
Chris@156 2485 if (otherLayer &&
Chris@156 2486 (otherLayer->getLayerColourSignificance() ==
Chris@156 2487 Layer::ColourHasMeaningfulValue)) {
Chris@156 2488 needNewPane = true;
Chris@156 2489 break;
Chris@156 2490 }
Chris@156 2491 }
Chris@156 2492 if (needNewPane) {
Chris@156 2493 pane = addPaneToStack();
Chris@156 2494 }
Chris@156 2495
Chris@156 2496 m_document->addLayerToView(pane, layer);
Chris@156 2497
Chris@145 2498 } else {
Chris@145 2499
Chris@145 2500 if (pane->getLayerCount() > 4) {
Chris@145 2501 pane = addPaneToStack();
Chris@145 2502 }
Chris@145 2503
Chris@145 2504 m_document->addLayerToView(pane, layer);
Chris@145 2505 }
Chris@145 2506
Chris@684 2507 added.insert(otherId);
Chris@145 2508 }
Chris@145 2509 }
Chris@145 2510 }
Chris@145 2511
Chris@684 2512 for (auto modelId : modelIds) {
Chris@684 2513
Chris@684 2514 if (added.find(modelId) == added.end()) {
Chris@145 2515
Chris@684 2516 Layer *layer = m_document->createImportedLayer(modelId);
Chris@145 2517 if (!layer) return FileOpenFailed;
Chris@145 2518
Chris@145 2519 Pane *singleLayerPane = addPaneToStack();
Chris@145 2520 if (m_timeRulerLayer) {
Chris@145 2521 m_document->addLayerToView(singleLayerPane, m_timeRulerLayer);
Chris@145 2522 }
Chris@145 2523 m_document->addLayerToView(singleLayerPane, layer);
Chris@145 2524 }
Chris@145 2525 }
Chris@145 2526
Chris@145 2527 m_recentFiles.addFile(source.getLocation());
Chris@145 2528 return FileOpenSucceeded;
Chris@141 2529 }
Chris@141 2530
Chris@584 2531 class AudioLogCallback : public breakfastquay::AudioFactory::LogCallback
Chris@584 2532 {
Chris@584 2533 public:
Chris@584 2534 void log(std::string message) const override {
Chris@584 2535 SVDEBUG << message << endl;
Chris@584 2536 }
Chris@584 2537 };
Chris@584 2538
Chris@45 2539 void
Chris@475 2540 MainWindowBase::createAudioIO()
Chris@45 2541 {
Chris@475 2542 if (m_playTarget || m_audioIO) return;
Chris@475 2543
Chris@584 2544 static AudioLogCallback audioLogCallback;
Chris@584 2545 breakfastquay::AudioFactory::setLogCallback(&audioLogCallback);
Chris@714 2546
Chris@714 2547 if (m_audioMode == AUDIO_NONE) return;
Chris@45 2548
Chris@126 2549 QSettings settings;
Chris@126 2550 settings.beginGroup("Preferences");
Chris@547 2551 QString implementation = settings.value
Chris@547 2552 ("audio-target", "").toString();
Chris@547 2553 QString suffix;
Chris@547 2554 if (implementation != "") suffix = "-" + implementation;
Chris@547 2555 QString recordDevice = settings.value
Chris@547 2556 ("audio-record-device" + suffix, "").toString();
Chris@547 2557 QString playbackDevice = settings.value
Chris@547 2558 ("audio-playback-device" + suffix, "").toString();
Chris@126 2559 settings.endGroup();
Chris@547 2560
Chris@547 2561 if (implementation == "auto") {
Chris@547 2562 implementation = "";
Chris@547 2563 }
Chris@468 2564
Chris@547 2565 breakfastquay::AudioFactory::Preference preference;
Chris@547 2566 preference.implementation = implementation.toStdString();
Chris@547 2567 preference.recordDevice = recordDevice.toStdString();
Chris@547 2568 preference.playbackDevice = playbackDevice.toStdString();
Chris@547 2569
Chris@547 2570 SVCERR << "createAudioIO: Preferred implementation = \""
Chris@547 2571 << preference.implementation << "\"" << endl;
Chris@547 2572 SVCERR << "createAudioIO: Preferred playback device = \""
Chris@547 2573 << preference.playbackDevice << "\"" << endl;
Chris@547 2574 SVCERR << "createAudioIO: Preferred record device = \""
Chris@547 2575 << preference.recordDevice << "\"" << endl;
Chris@475 2576
Chris@738 2577 breakfastquay::ApplicationPlaybackSource *source =
Chris@738 2578 m_playSource->getApplicationPlaybackSource();
Chris@569 2579
Chris@569 2580 std::string errorString;
Chris@551 2581
Chris@714 2582 if (m_audioMode == AUDIO_PLAYBACK_AND_RECORD) {
Chris@475 2583 m_audioIO = breakfastquay::AudioFactory::
Chris@738 2584 createCallbackIO(m_recordTarget, source, preference, errorString);
Chris@525 2585 if (m_audioIO) {
Chris@611 2586 SVCERR << "MainWindowBase::createAudioIO: Suspending on creation" << endl;
Chris@525 2587 m_audioIO->suspend(); // start in suspended state
Chris@525 2588 m_playSource->setSystemPlaybackTarget(m_audioIO);
Chris@586 2589 } else {
Chris@586 2590 // Failed to create audio I/O; this may just mean there is
Chris@586 2591 // no record device, so fall through to see what happens
Chris@586 2592 // next. We only report complete failure if we end up with
Chris@586 2593 // neither m_audioIO nor m_playTarget.
Chris@525 2594 }
Chris@586 2595 }
Chris@586 2596
Chris@586 2597 if (!m_audioIO) {
Chris@475 2598 m_playTarget = breakfastquay::AudioFactory::
Chris@738 2599 createCallbackPlayTarget(source, preference, errorString);
Chris@525 2600 if (m_playTarget) {
Chris@611 2601 SVCERR << "MainWindowBase::createAudioIO: Suspending on creation" << endl;
Chris@525 2602 m_playTarget->suspend(); // start in suspended state
Chris@525 2603 m_playSource->setSystemPlaybackTarget(m_playTarget);
Chris@525 2604 }
Chris@475 2605 }
Chris@475 2606
Chris@475 2607 if (!m_playTarget && !m_audioIO) {
Chris@104 2608 emit hideSplash();
Chris@569 2609 QString message;
Chris@569 2610 QString error = errorString.c_str();
Chris@569 2611 QString firstBit, secondBit;
Chris@547 2612 if (implementation == "") {
Chris@569 2613 if (error == "") {
Chris@569 2614 firstBit = tr("<b>No audio available</b><p>Could not open an audio device.</p>");
Chris@569 2615 } else {
Chris@569 2616 firstBit = tr("<b>No audio available</b><p>Could not open audio device: %1</p>").arg(error);
Chris@569 2617 }
Chris@714 2618 if (m_audioMode == AUDIO_PLAYBACK_NOW_RECORD_LATER ||
Chris@714 2619 m_audioMode == AUDIO_PLAYBACK_AND_RECORD) {
Chris@569 2620 secondBit = tr("<p>Automatic audio device detection failed. Audio playback and recording will not be available during this session.</p>");
Chris@569 2621 } else {
Chris@569 2622 secondBit = tr("<p>Automatic audio device detection failed. Audio playback will not be available during this session.</p>");
Chris@569 2623 }
Chris@126 2624 } else {
Chris@569 2625 QString driverName = breakfastquay::AudioFactory::
Chris@569 2626 getImplementationDescription(implementation.toStdString())
Chris@569 2627 .c_str();
Chris@569 2628 if (error == "") {
Chris@569 2629 firstBit = tr("<b>No audio available</b><p>Failed to open your preferred audio driver (\"%1\").</p>").arg(driverName);
Chris@569 2630 } else {
Chris@569 2631 firstBit = tr("<b>No audio available</b><p>Failed to open your preferred audio driver (\"%1\"): %2.</p>").arg(driverName).arg(error);
Chris@569 2632 }
Chris@714 2633 if (m_audioMode == AUDIO_PLAYBACK_NOW_RECORD_LATER ||
Chris@714 2634 m_audioMode == AUDIO_PLAYBACK_AND_RECORD) {
Chris@569 2635 secondBit = tr("<p>Audio playback and recording will not be available during this session.</p>");
Chris@569 2636 } else {
Chris@569 2637 secondBit = tr("<p>Audio playback will not be available during this session.</p>");
Chris@569 2638 }
Chris@126 2639 }
Chris@570 2640 SVDEBUG << "createAudioIO: ERROR: Failed to open audio device \""
Chris@570 2641 << implementation << "\": error is: " << error << endl;
Chris@569 2642 QMessageBox::warning(this, tr("Couldn't open audio device"),
Chris@569 2643 firstBit + secondBit, QMessageBox::Ok);
Chris@45 2644 }
Chris@45 2645 }
Chris@45 2646
Chris@556 2647 void
Chris@556 2648 MainWindowBase::deleteAudioIO()
Chris@556 2649 {
Chris@556 2650 // First prevent this trying to call target.
Chris@559 2651 if (m_playSource) {
Chris@636 2652 m_playSource->setSystemPlaybackTarget(nullptr);
Chris@559 2653 }
Chris@556 2654
Chris@556 2655 // Then delete the breakfastquay::System object.
Chris@556 2656 // Only one of these two exists!
Chris@556 2657 delete m_audioIO;
Chris@556 2658 delete m_playTarget;
Chris@556 2659
Chris@636 2660 m_audioIO = nullptr;
Chris@636 2661 m_playTarget = nullptr;
Chris@556 2662 }
Chris@556 2663
Chris@556 2664 void
Chris@556 2665 MainWindowBase::recreateAudioIO()
Chris@556 2666 {
Chris@556 2667 deleteAudioIO();
Chris@556 2668 createAudioIO();
Chris@556 2669 }
Chris@556 2670
Chris@570 2671 void
Chris@570 2672 MainWindowBase::audioChannelCountIncreased(int)
Chris@570 2673 {
Chris@611 2674 SVCERR << "MainWindowBase::audioChannelCountIncreased" << endl;
Chris@570 2675 recreateAudioIO();
Chris@610 2676
Chris@610 2677 if (m_recordTarget &&
Chris@610 2678 m_recordTarget->isRecording() &&
Chris@610 2679 m_audioIO) {
Chris@610 2680 SVCERR << "MainWindowBase::audioChannelCountIncreased: we were recording already, so resuming IO now" << endl;
Chris@610 2681 m_audioIO->resume();
Chris@610 2682 }
Chris@570 2683 }
Chris@570 2684
Chris@684 2685 ModelId
Chris@685 2686 MainWindowBase::getMainModelId() const
Chris@684 2687 {
Chris@684 2688 if (!m_document) return {};
Chris@684 2689 return m_document->getMainModel();
Chris@684 2690 }
Chris@684 2691
Chris@684 2692 std::shared_ptr<WaveFileModel>
Chris@685 2693 MainWindowBase::getMainModel() const
Chris@45 2694 {
Chris@684 2695 return ModelById::getAs<WaveFileModel>(getMainModelId());
Chris@45 2696 }
Chris@45 2697
Chris@45 2698 void
Chris@45 2699 MainWindowBase::createDocument()
Chris@45 2700 {
Chris@45 2701 m_document = new Document;
Chris@45 2702
Chris@45 2703 connect(m_document, SIGNAL(layerAdded(Layer *)),
Chris@595 2704 this, SLOT(layerAdded(Layer *)));
Chris@45 2705 connect(m_document, SIGNAL(layerRemoved(Layer *)),
Chris@595 2706 this, SLOT(layerRemoved(Layer *)));
Chris@45 2707 connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)),
Chris@595 2708 this, SLOT(layerAboutToBeDeleted(Layer *)));
Chris@45 2709 connect(m_document, SIGNAL(layerInAView(Layer *, bool)),
Chris@595 2710 this, SLOT(layerInAView(Layer *, bool)));
Chris@45 2711
Chris@684 2712 connect(m_document, SIGNAL(modelAdded(ModelId )),
Chris@684 2713 this, SLOT(modelAdded(ModelId )));
Chris@687 2714 connect(m_document, SIGNAL(mainModelChanged(ModelId)),
Chris@687 2715 this, SLOT(mainModelChanged(ModelId)));
Chris@45 2716
Chris@78 2717 connect(m_document, SIGNAL(modelGenerationFailed(QString, QString)),
Chris@78 2718 this, SLOT(modelGenerationFailed(QString, QString)));
Chris@78 2719 connect(m_document, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
Chris@78 2720 this, SLOT(modelRegenerationWarning(QString, QString, QString)));
Chris@687 2721 connect(m_document, SIGNAL(alignmentComplete(ModelId)),
Chris@687 2722 this, SLOT(alignmentComplete(ModelId)));
Chris@423 2723 connect(m_document, SIGNAL(alignmentFailed(QString)),
Chris@423 2724 this, SLOT(alignmentFailed(QString)));
Chris@160 2725
Chris@667 2726 m_document->setAutoAlignment(m_viewManager->getAlignMode());
Chris@667 2727
Chris@160 2728 emit replacedDocument();
Chris@45 2729 }
Chris@45 2730
Chris@45 2731 bool
Chris@45 2732 MainWindowBase::saveSessionFile(QString path)
Chris@45 2733 {
Chris@217 2734 try {
Chris@217 2735
Chris@217 2736 TempWriteFile temp(path);
Chris@217 2737
Chris@217 2738 BZipFileDevice bzFile(temp.getTemporaryFilename());
Chris@217 2739 if (!bzFile.open(QIODevice::WriteOnly)) {
Chris@293 2740 cerr << "Failed to open session file \""
Chris@294 2741 << temp.getTemporaryFilename()
Chris@217 2742 << "\" for writing: "
Chris@293 2743 << bzFile.errorString() << endl;
Chris@217 2744 return false;
Chris@217 2745 }
Chris@217 2746
Chris@217 2747 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
Chris@217 2748
Chris@217 2749 QTextStream out(&bzFile);
Chris@432 2750 out.setCodec(QTextCodec::codecForName("UTF-8"));
Chris@226 2751 toXml(out, false);
Chris@217 2752 out.flush();
Chris@217 2753
Chris@217 2754 QApplication::restoreOverrideCursor();
Chris@217 2755
Chris@217 2756 if (!bzFile.isOK()) {
Chris@217 2757 QMessageBox::critical(this, tr("Failed to write file"),
Chris@217 2758 tr("<b>Save failed</b><p>Failed to write to file \"%1\": %2")
Chris@217 2759 .arg(path).arg(bzFile.errorString()));
Chris@217 2760 bzFile.close();
Chris@217 2761 return false;
Chris@217 2762 }
Chris@217 2763
Chris@217 2764 bzFile.close();
Chris@217 2765 temp.moveToTarget();
Chris@217 2766 return true;
Chris@217 2767
Chris@217 2768 } catch (FileOperationFailed &f) {
Chris@217 2769
Chris@217 2770 QMessageBox::critical(this, tr("Failed to write file"),
Chris@217 2771 tr("<b>Save failed</b><p>Failed to write to file \"%1\": %2")
Chris@217 2772 .arg(path).arg(f.what()));
Chris@45 2773 return false;
Chris@45 2774 }
Chris@45 2775 }
Chris@45 2776
Chris@224 2777 bool
Chris@224 2778 MainWindowBase::saveSessionTemplate(QString path)
Chris@224 2779 {
Chris@224 2780 try {
Chris@224 2781
Chris@224 2782 TempWriteFile temp(path);
Chris@224 2783
Chris@224 2784 QFile file(temp.getTemporaryFilename());
Chris@224 2785 if (!file.open(QIODevice::WriteOnly)) {
Chris@293 2786 cerr << "Failed to open session template file \""
Chris@294 2787 << temp.getTemporaryFilename()
Chris@224 2788 << "\" for writing: "
Chris@294 2789 << file.errorString() << endl;
Chris@224 2790 return false;
Chris@224 2791 }
Chris@224 2792
Chris@224 2793 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
Chris@224 2794
Chris@224 2795 QTextStream out(&file);
Chris@432 2796 out.setCodec(QTextCodec::codecForName("UTF-8"));
Chris@226 2797 toXml(out, true);
Chris@224 2798 out.flush();
Chris@224 2799
Chris@224 2800 QApplication::restoreOverrideCursor();
Chris@224 2801
Chris@224 2802 file.close();
Chris@224 2803 temp.moveToTarget();
Chris@224 2804 return true;
Chris@224 2805
Chris@224 2806 } catch (FileOperationFailed &f) {
Chris@224 2807
Chris@224 2808 QMessageBox::critical(this, tr("Failed to write file"),
Chris@224 2809 tr("<b>Save failed</b><p>Failed to write to file \"%1\": %2")
Chris@224 2810 .arg(path).arg(f.what()));
Chris@224 2811 return false;
Chris@224 2812 }
Chris@224 2813 }
Chris@224 2814
Chris@729 2815 //!!! should we pull out the whole export logic into another
Chris@729 2816 // class? then we can more reasonably query it for things like
Chris@729 2817 // "can we export this layer type to this file format? can we
Chris@729 2818 // export selections, or only the whole layer?"
Chris@729 2819
Chris@659 2820 bool
Chris@729 2821 MainWindowBase::exportLayerToSVL(Layer *layer,
Chris@729 2822 QString path, QString &error)
Chris@659 2823 {
Chris@659 2824 if (QFileInfo(path).suffix() == "") path += ".svl";
Chris@659 2825
Chris@659 2826 QString suffix = QFileInfo(path).suffix().toLower();
Chris@659 2827
Chris@729 2828 auto model = ModelById::get(layer->getExportModel(nullptr));
Chris@684 2829 if (!model) {
Chris@684 2830 error = tr("Internal error: unknown model");
Chris@684 2831 return false;
Chris@684 2832 }
Chris@659 2833
Chris@729 2834 QFile file(path);
Chris@729 2835 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
Chris@729 2836 error = tr("Failed to open file %1 for writing").arg(path);
Chris@729 2837 } else {
Chris@729 2838 QTextStream out(&file);
Chris@729 2839 out.setCodec(QTextCodec::codecForName("UTF-8"));
Chris@729 2840 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
Chris@729 2841 << "<!DOCTYPE sonic-visualiser>\n"
Chris@729 2842 << "<sv>\n"
Chris@729 2843 << " <data>\n";
Chris@729 2844
Chris@729 2845 model->toXml(out, " ");
Chris@729 2846
Chris@729 2847 out << " </data>\n"
Chris@729 2848 << " <display>\n";
Chris@729 2849
Chris@729 2850 layer->toXml(out, " ");
Chris@729 2851
Chris@729 2852 out << " </display>\n"
Chris@729 2853 << "</sv>\n";
Chris@729 2854 }
Chris@729 2855
Chris@729 2856 return (error == "");
Chris@729 2857 }
Chris@729 2858
Chris@729 2859 bool
Chris@729 2860 MainWindowBase::exportLayerToMIDI(Layer *layer,
Chris@729 2861 MultiSelection *selectionsToWrite,
Chris@729 2862 QString path, QString &error)
Chris@729 2863 {
Chris@729 2864 if (QFileInfo(path).suffix() == "") path += ".mid";
Chris@729 2865
Chris@729 2866 QString suffix = QFileInfo(path).suffix().toLower();
Chris@729 2867
Chris@729 2868 auto model = ModelById::get(layer->getExportModel(nullptr));
Chris@729 2869 if (!model) {
Chris@729 2870 error = tr("Internal error: unknown model");
Chris@729 2871 return false;
Chris@729 2872 }
Chris@729 2873
Chris@729 2874 auto nm = ModelById::getAs<NoteModel>(layer->getModel());
Chris@729 2875
Chris@729 2876 if (!nm) {
Chris@729 2877 error = tr("Can't export non-note layers to MIDI");
Chris@729 2878 } else if (!selectionsToWrite) {
Chris@729 2879 MIDIFileWriter writer(path, nm.get(), nm->getSampleRate());
Chris@729 2880 writer.write();
Chris@729 2881 if (!writer.isOK()) {
Chris@729 2882 error = writer.getError();
Chris@659 2883 }
Chris@729 2884 } else {
Chris@729 2885 NoteModel temporary(nm->getSampleRate(),
Chris@729 2886 nm->getResolution(),
Chris@729 2887 nm->getValueMinimum(),
Chris@729 2888 nm->getValueMaximum(),
Chris@729 2889 false);
Chris@729 2890 temporary.setScaleUnits(nm->getScaleUnits());
Chris@729 2891 for (const auto &s: selectionsToWrite->getSelections()) {
Chris@729 2892 EventVector ev(nm->getEventsStartingWithin
Chris@729 2893 (s.getStartFrame(), s.getDuration()));
Chris@729 2894 for (const auto &e: ev) {
Chris@729 2895 temporary.add(e);
Chris@724 2896 }
Chris@659 2897 }
Chris@729 2898 MIDIFileWriter writer(path, &temporary, temporary.getSampleRate());
Chris@729 2899 writer.write();
Chris@659 2900 if (!writer.isOK()) {
Chris@659 2901 error = writer.getError();
Chris@659 2902 }
Chris@659 2903 }
Chris@729 2904
Chris@659 2905 return (error == "");
Chris@659 2906 }
Chris@659 2907
Chris@729 2908 bool
Chris@729 2909 MainWindowBase::exportLayerToRDF(Layer *layer,
Chris@729 2910 QString path, QString &error)
Chris@729 2911 {
Chris@729 2912 if (QFileInfo(path).suffix() == "") path += ".ttl";
Chris@729 2913
Chris@729 2914 QString suffix = QFileInfo(path).suffix().toLower();
Chris@729 2915
Chris@729 2916 auto model = ModelById::get(layer->getExportModel(nullptr));
Chris@729 2917 if (!model) {
Chris@729 2918 error = tr("Internal error: unknown model");
Chris@729 2919 return false;
Chris@729 2920 }
Chris@729 2921
Chris@729 2922 if (!RDFExporter::canExportModel(model.get())) {
Chris@729 2923 error = tr("Sorry, cannot export this layer type to RDF (supported types are: region, note, text, time instants, time values)");
Chris@729 2924 } else {
Chris@729 2925 RDFExporter exporter(path, model.get());
Chris@729 2926 exporter.write();
Chris@729 2927 if (!exporter.isOK()) {
Chris@729 2928 error = exporter.getError();
Chris@729 2929 }
Chris@729 2930 }
Chris@729 2931
Chris@729 2932 return (error == "");
Chris@729 2933 }
Chris@729 2934
Chris@729 2935 bool
Chris@729 2936 MainWindowBase::exportLayerToCSV(Layer *layer, LayerGeometryProvider *provider,
Chris@729 2937 MultiSelection *selectionsToWrite,
Chris@729 2938 QString delimiter,
Chris@729 2939 DataExportOptions options,
Chris@729 2940 QString path, QString &error)
Chris@729 2941 {
Chris@729 2942 if (QFileInfo(path).suffix() == "") path += ".csv";
Chris@729 2943
Chris@729 2944 QString suffix = QFileInfo(path).suffix().toLower();
Chris@729 2945
Chris@729 2946 auto model = ModelById::get(layer->getExportModel(provider));
Chris@729 2947 if (!model) {
Chris@729 2948 error = tr("Internal error: unknown model");
Chris@729 2949 return false;
Chris@729 2950 }
Chris@729 2951
Chris@729 2952 ProgressDialog dialog {
Chris@729 2953 QObject::tr("Exporting layer..."), true, 500, this,
Chris@729 2954 Qt::ApplicationModal
Chris@729 2955 };
Chris@729 2956
Chris@729 2957 CSVFileWriter writer(path, model.get(), &dialog, delimiter, options);
Chris@729 2958
Chris@729 2959 if (selectionsToWrite) {
Chris@729 2960 writer.writeSelection(*selectionsToWrite);
Chris@729 2961 } else {
Chris@729 2962 writer.write();
Chris@729 2963 }
Chris@729 2964
Chris@729 2965 if (!writer.isOK()) {
Chris@729 2966 error = writer.getError();
Chris@729 2967 if (error == "") {
Chris@729 2968 error = tr("Failed to export layer for an unknown reason");
Chris@729 2969 }
Chris@729 2970 }
Chris@729 2971
Chris@729 2972 return (error == "");
Chris@729 2973 }
Chris@729 2974
Chris@729 2975 bool
Chris@729 2976 MainWindowBase::exportLayerTo(Layer *layer, LayerGeometryProvider *provider,
Chris@729 2977 MultiSelection *selectionsToWrite,
Chris@729 2978 QString path, QString &error)
Chris@729 2979 {
Chris@731 2980 if (QFileInfo(path).suffix() == "") path += ".csv";
Chris@729 2981 QString suffix = QFileInfo(path).suffix().toLower();
Chris@729 2982
Chris@729 2983 if (suffix == "xml" || suffix == "svl") {
Chris@729 2984 return exportLayerToSVL(layer, path, error);
Chris@729 2985 } else if (suffix == "mid" || suffix == "midi") {
Chris@729 2986 return exportLayerToMIDI(layer, selectionsToWrite, path, error);
Chris@729 2987 } else if (suffix == "ttl" || suffix == "n3") {
Chris@729 2988 return exportLayerToRDF(layer, path, error);
Chris@729 2989 } else {
Chris@729 2990 return exportLayerToCSV(layer, provider, selectionsToWrite,
Chris@729 2991 (suffix == "csv" ? "," : "\t"),
Chris@729 2992 DataExportDefaults, path, error);
Chris@729 2993 }
Chris@729 2994 }
Chris@729 2995
Chris@45 2996 void
Chris@226 2997 MainWindowBase::toXml(QTextStream &out, bool asTemplate)
Chris@45 2998 {
Chris@45 2999 QString indent(" ");
Chris@45 3000
Chris@45 3001 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
Chris@45 3002 out << "<!DOCTYPE sonic-visualiser>\n";
Chris@45 3003 out << "<sv>\n";
Chris@45 3004
Chris@226 3005 if (asTemplate) {
Chris@226 3006 m_document->toXmlAsTemplate(out, "", "");
Chris@226 3007 } else {
Chris@226 3008 m_document->toXml(out, "", "");
Chris@226 3009 }
Chris@45 3010
Chris@45 3011 out << "<display>\n";
Chris@45 3012
Chris@45 3013 out << QString(" <window width=\"%1\" height=\"%2\"/>\n")
Chris@595 3014 .arg(width()).arg(height());
Chris@45 3015
Chris@45 3016 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@45 3017
Chris@595 3018 Pane *pane = m_paneStack->getPane(i);
Chris@595 3019
Chris@595 3020 if (pane) {
Chris@45 3021 pane->toXml(out, indent);
Chris@595 3022 }
Chris@45 3023 }
Chris@45 3024
Chris@45 3025 out << "</display>\n";
Chris@45 3026
Chris@45 3027 m_viewManager->getSelection().toXml(out);
Chris@45 3028
Chris@45 3029 out << "</sv>\n";
Chris@45 3030 }
Chris@45 3031
Chris@45 3032 Pane *
Chris@45 3033 MainWindowBase::addPaneToStack()
Chris@45 3034 {
Chris@342 3035 cerr << "MainWindowBase::addPaneToStack()" << endl;
Chris@45 3036 AddPaneCommand *command = new AddPaneCommand(this);
Chris@45 3037 CommandHistory::getInstance()->addCommand(command);
Chris@57 3038 Pane *pane = command->getPane();
Chris@57 3039 return pane;
Chris@45 3040 }
Chris@45 3041
Chris@45 3042 void
Chris@45 3043 MainWindowBase::zoomIn()
Chris@45 3044 {
Chris@45 3045 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@45 3046 if (currentPane) currentPane->zoom(true);
Chris@45 3047 }
Chris@45 3048
Chris@45 3049 void
Chris@45 3050 MainWindowBase::zoomOut()
Chris@45 3051 {
Chris@45 3052 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@45 3053 if (currentPane) currentPane->zoom(false);
Chris@45 3054 }
Chris@45 3055
Chris@45 3056 void
Chris@45 3057 MainWindowBase::zoomToFit()
Chris@45 3058 {
Chris@45 3059 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@45 3060 if (!currentPane) return;
Chris@45 3061
Chris@684 3062 auto model = getMainModel();
Chris@45 3063 if (!model) return;
Chris@45 3064
Chris@434 3065 sv_frame_t start = model->getStartFrame();
Chris@434 3066 sv_frame_t end = model->getEndFrame();
Chris@60 3067 if (m_playSource) end = std::max(end, m_playSource->getPlayEndFrame());
Chris@366 3068 int pixels = currentPane->width();
Chris@366 3069
Chris@366 3070 int sw = currentPane->getVerticalScaleWidth();
Chris@45 3071 if (pixels > sw * 2) pixels -= sw * 2;
Chris@45 3072 else pixels = 1;
Chris@45 3073 if (pixels > 4) pixels -= 4;
Chris@45 3074
Chris@624 3075 ZoomLevel zoomLevel = ZoomLevel::fromRatio(pixels, end - start);
Chris@45 3076 currentPane->setZoomLevel(zoomLevel);
Chris@45 3077 currentPane->setCentreFrame((start + end) / 2);
Chris@45 3078 }
Chris@45 3079
Chris@45 3080 void
Chris@45 3081 MainWindowBase::zoomDefault()
Chris@45 3082 {
Chris@45 3083 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@302 3084 QSettings settings;
Chris@302 3085 settings.beginGroup("MainWindow");
Chris@302 3086 int zoom = settings.value("zoom-default", 1024).toInt();
Chris@302 3087 settings.endGroup();
Chris@624 3088 if (currentPane) {
Chris@624 3089 currentPane->setZoomLevel(ZoomLevel(ZoomLevel::FramesPerPixel, zoom));
Chris@624 3090 }
Chris@45 3091 }
Chris@45 3092
Chris@45 3093 void
Chris@45 3094 MainWindowBase::scrollLeft()
Chris@45 3095 {
Chris@45 3096 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@45 3097 if (currentPane) currentPane->scroll(false, false);
Chris@45 3098 }
Chris@45 3099
Chris@45 3100 void
Chris@45 3101 MainWindowBase::jumpLeft()
Chris@45 3102 {
Chris@45 3103 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@45 3104 if (currentPane) currentPane->scroll(false, true);
Chris@45 3105 }
Chris@45 3106
Chris@45 3107 void
Chris@162 3108 MainWindowBase::peekLeft()
Chris@162 3109 {
Chris@162 3110 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@162 3111 if (currentPane) currentPane->scroll(false, false, false);
Chris@162 3112 }
Chris@162 3113
Chris@162 3114 void
Chris@45 3115 MainWindowBase::scrollRight()
Chris@45 3116 {
Chris@45 3117 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@45 3118 if (currentPane) currentPane->scroll(true, false);
Chris@45 3119 }
Chris@45 3120
Chris@45 3121 void
Chris@45 3122 MainWindowBase::jumpRight()
Chris@45 3123 {
Chris@45 3124 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@45 3125 if (currentPane) currentPane->scroll(true, true);
Chris@45 3126 }
Chris@45 3127
Chris@45 3128 void
Chris@162 3129 MainWindowBase::peekRight()
Chris@162 3130 {
Chris@162 3131 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@162 3132 if (currentPane) currentPane->scroll(true, false, false);
Chris@162 3133 }
Chris@162 3134
Chris@162 3135 void
Chris@45 3136 MainWindowBase::showNoOverlays()
Chris@45 3137 {
Chris@45 3138 m_viewManager->setOverlayMode(ViewManager::NoOverlays);
Chris@45 3139 }
Chris@45 3140
Chris@45 3141 void
Chris@45 3142 MainWindowBase::showMinimalOverlays()
Chris@45 3143 {
Chris@335 3144 m_viewManager->setOverlayMode(ViewManager::StandardOverlays);
Chris@45 3145 }
Chris@45 3146
Chris@45 3147 void
Chris@45 3148 MainWindowBase::showAllOverlays()
Chris@45 3149 {
Chris@45 3150 m_viewManager->setOverlayMode(ViewManager::AllOverlays);
Chris@45 3151 }
Chris@45 3152
Chris@45 3153 void
Chris@577 3154 MainWindowBase::findTimeRulerLayer()
Chris@577 3155 {
Chris@577 3156 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@577 3157 Pane *pane = m_paneStack->getPane(i);
Chris@577 3158 if (!pane) continue;
Chris@577 3159 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@577 3160 Layer *layer = pane->getLayer(j);
Chris@577 3161 if (!dynamic_cast<TimeRulerLayer *>(layer)) continue;
Chris@577 3162 m_timeRulerLayer = layer;
Chris@577 3163 return;
Chris@577 3164 }
Chris@577 3165 }
Chris@577 3166 if (m_timeRulerLayer) {
Chris@577 3167 SVCERR << "WARNING: Time ruler layer was not reset to 0 before session template loaded?" << endl;
Chris@577 3168 delete m_timeRulerLayer;
Chris@636 3169 m_timeRulerLayer = nullptr;
Chris@577 3170 }
Chris@577 3171 }
Chris@577 3172
Chris@577 3173 void
Chris@211 3174 MainWindowBase::toggleTimeRulers()
Chris@211 3175 {
Chris@211 3176 bool haveRulers = false;
Chris@211 3177 bool someHidden = false;
Chris@211 3178
Chris@211 3179 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@211 3180
Chris@211 3181 Pane *pane = m_paneStack->getPane(i);
Chris@211 3182 if (!pane) continue;
Chris@211 3183
Chris@211 3184 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@211 3185
Chris@211 3186 Layer *layer = pane->getLayer(j);
Chris@211 3187 if (!dynamic_cast<TimeRulerLayer *>(layer)) continue;
Chris@211 3188
Chris@211 3189 haveRulers = true;
Chris@211 3190 if (layer->isLayerDormant(pane)) someHidden = true;
Chris@211 3191 }
Chris@211 3192 }
Chris@211 3193
Chris@211 3194 if (haveRulers) {
Chris@211 3195
Chris@211 3196 bool show = someHidden;
Chris@211 3197
Chris@211 3198 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@211 3199
Chris@211 3200 Pane *pane = m_paneStack->getPane(i);
Chris@211 3201 if (!pane) continue;
Chris@211 3202
Chris@211 3203 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@211 3204
Chris@211 3205 Layer *layer = pane->getLayer(j);
Chris@211 3206 if (!dynamic_cast<TimeRulerLayer *>(layer)) continue;
Chris@211 3207
Chris@211 3208 layer->showLayer(pane, show);
Chris@211 3209 }
Chris@211 3210 }
Chris@211 3211 }
Chris@211 3212 }
Chris@211 3213
Chris@211 3214 void
Chris@45 3215 MainWindowBase::toggleZoomWheels()
Chris@45 3216 {
Chris@45 3217 if (m_viewManager->getZoomWheelsEnabled()) {
Chris@45 3218 m_viewManager->setZoomWheelsEnabled(false);
Chris@45 3219 } else {
Chris@45 3220 m_viewManager->setZoomWheelsEnabled(true);
Chris@45 3221 }
Chris@45 3222 }
Chris@45 3223
Chris@45 3224 void
Chris@45 3225 MainWindowBase::togglePropertyBoxes()
Chris@45 3226 {
Chris@712 3227 if (m_paneStack->getLayoutStyle() == PaneStack::HiddenPropertyStacksLayout) {
Chris@45 3228 if (Preferences::getInstance()->getPropertyBoxLayout() ==
Chris@45 3229 Preferences::VerticallyStacked) {
Chris@45 3230 m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout);
Chris@45 3231 } else {
Chris@45 3232 m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout);
Chris@45 3233 }
Chris@45 3234 } else {
Chris@712 3235 m_paneStack->setLayoutStyle(PaneStack::HiddenPropertyStacksLayout);
Chris@45 3236 }
Chris@45 3237 }
Chris@45 3238
Chris@378 3239 QLabel *
Chris@378 3240 MainWindowBase::getStatusLabel() const
Chris@378 3241 {
Chris@378 3242 if (!m_statusLabel) {
Chris@378 3243 m_statusLabel = new QLabel();
Chris@378 3244 statusBar()->addWidget(m_statusLabel, 1);
Chris@378 3245 }
Chris@379 3246
Chris@379 3247 QList<QFrame *> frames = statusBar()->findChildren<QFrame *>();
Chris@379 3248 foreach (QFrame *f, frames) {
Chris@379 3249 f->setFrameStyle(QFrame::NoFrame);
Chris@379 3250 }
Chris@379 3251
Chris@378 3252 return m_statusLabel;
Chris@378 3253 }
Chris@378 3254
Chris@45 3255 void
Chris@45 3256 MainWindowBase::toggleStatusBar()
Chris@45 3257 {
Chris@45 3258 QSettings settings;
Chris@45 3259 settings.beginGroup("MainWindow");
Chris@45 3260 bool sb = settings.value("showstatusbar", true).toBool();
Chris@45 3261
Chris@45 3262 if (sb) {
Chris@45 3263 statusBar()->hide();
Chris@45 3264 } else {
Chris@45 3265 statusBar()->show();
Chris@45 3266 }
Chris@45 3267
Chris@45 3268 settings.setValue("showstatusbar", !sb);
Chris@45 3269
Chris@45 3270 settings.endGroup();
Chris@45 3271 }
Chris@45 3272
Chris@45 3273 void
Chris@256 3274 MainWindowBase::toggleCentreLine()
Chris@256 3275 {
Chris@256 3276 if (m_viewManager->shouldShowCentreLine()) {
Chris@256 3277 m_viewManager->setShowCentreLine(false);
Chris@256 3278 } else {
Chris@256 3279 m_viewManager->setShowCentreLine(true);
Chris@256 3280 }
Chris@256 3281 }
Chris@256 3282
Chris@256 3283 void
Chris@45 3284 MainWindowBase::preferenceChanged(PropertyContainer::PropertyName name)
Chris@45 3285 {
Chris@45 3286 if (name == "Property Box Layout") {
Chris@712 3287 if (m_paneStack->getLayoutStyle() != PaneStack::HiddenPropertyStacksLayout) {
Chris@45 3288 if (Preferences::getInstance()->getPropertyBoxLayout() ==
Chris@45 3289 Preferences::VerticallyStacked) {
Chris@45 3290 m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout);
Chris@45 3291 } else {
Chris@45 3292 m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout);
Chris@45 3293 }
Chris@45 3294 }
Chris@45 3295 } else if (name == "Background Mode" && m_viewManager) {
Chris@45 3296 Preferences::BackgroundMode mode =
Chris@45 3297 Preferences::getInstance()->getBackgroundMode();
Chris@45 3298 if (mode == Preferences::BackgroundFromTheme) {
Chris@45 3299 m_viewManager->setGlobalDarkBackground(m_initialDarkBackground);
Chris@45 3300 } else if (mode == Preferences::DarkBackground) {
Chris@45 3301 m_viewManager->setGlobalDarkBackground(true);
Chris@45 3302 } else {
Chris@45 3303 m_viewManager->setGlobalDarkBackground(false);
Chris@45 3304 }
Chris@45 3305 }
Chris@45 3306 }
Chris@45 3307
Chris@45 3308 void
Chris@45 3309 MainWindowBase::play()
Chris@45 3310 {
Chris@516 3311 if ((m_recordTarget && m_recordTarget->isRecording()) ||
Chris@516 3312 (m_playSource && m_playSource->isPlaying())) {
Chris@45 3313 stop();
Chris@479 3314 QAction *action = qobject_cast<QAction *>(sender());
Chris@479 3315 if (action) action->setChecked(false);
Chris@45 3316 } else {
Chris@487 3317 if (m_audioIO) m_audioIO->resume();
Chris@509 3318 else if (m_playTarget) m_playTarget->resume();
Chris@45 3319 playbackFrameChanged(m_viewManager->getPlaybackFrame());
Chris@595 3320 m_playSource->play(m_viewManager->getPlaybackFrame());
Chris@45 3321 }
Chris@45 3322 }
Chris@45 3323
Chris@45 3324 void
Chris@477 3325 MainWindowBase::record()
Chris@477 3326 {
Chris@586 3327 QAction *action = qobject_cast<QAction *>(sender());
Chris@586 3328
Chris@714 3329 if (m_audioMode == AUDIO_NONE || m_audioMode == AUDIO_PLAYBACK_ONLY) {
Chris@586 3330 if (action) action->setChecked(false);
Chris@478 3331 return;
Chris@478 3332 }
Chris@478 3333
Chris@477 3334 if (!m_recordTarget) {
Chris@586 3335 if (action) action->setChecked(false);
Chris@477 3336 return;
Chris@477 3337 }
Chris@477 3338
Chris@714 3339 if (m_audioMode == AUDIO_PLAYBACK_NOW_RECORD_LATER) {
Chris@714 3340 SVDEBUG << "MainWindowBase::record: upgrading from "
Chris@714 3341 << "AUDIO_PLAYBACK_NOW_RECORD_LATER to "
Chris@714 3342 << "AUDIO_PLAYBACK_AND_RECORD" << endl;
Chris@714 3343 m_audioMode = AUDIO_PLAYBACK_AND_RECORD;
Chris@714 3344 deleteAudioIO();
Chris@714 3345 }
Chris@714 3346
Chris@478 3347 if (!m_audioIO) {
Chris@611 3348 SVDEBUG << "MainWindowBase::record: about to create audio IO" << endl;
Chris@478 3349 createAudioIO();
Chris@478 3350 }
Chris@492 3351
Chris@492 3352 if (!m_audioIO) {
Chris@586 3353 if (!m_playTarget) {
Chris@586 3354 // Don't need to report this, createAudioIO should have
Chris@586 3355 if (action) action->setChecked(false);
Chris@586 3356 return;
Chris@586 3357 } else {
Chris@586 3358 // Need to report this: if the play target exists instead
Chris@586 3359 // of the audio IO, then that means we failed to open a
Chris@586 3360 // capture device. The record control should be disabled
Chris@586 3361 // in that situation, so if it happens here, that must
Chris@586 3362 // mean this is the first time we ever tried to open the
Chris@586 3363 // audio device, hence the need to report the problem here
Chris@586 3364 QMessageBox::critical
Chris@586 3365 (this, tr("No record device available"),
Chris@586 3366 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 3367 if (action) action->setChecked(false);
Chris@586 3368 updateMenuStates();
Chris@586 3369 return;
Chris@586 3370 }
Chris@492 3371 }
Chris@478 3372
Chris@477 3373 if (m_recordTarget->isRecording()) {
Chris@492 3374 stop();
Chris@477 3375 return;
Chris@477 3376 }
Chris@490 3377
Chris@483 3378 if (m_audioRecordMode == RecordReplaceSession) {
Chris@490 3379 if (!checkSaveModified()) {
Chris@490 3380 if (action) action->setChecked(false);
Chris@490 3381 return;
Chris@490 3382 }
Chris@483 3383 }
Chris@487 3384
Chris@557 3385 if (m_viewManager) m_viewManager->setGlobalCentreFrame(0);
Chris@557 3386
Chris@611 3387 SVCERR << "MainWindowBase::record: about to resume" << endl;
Chris@492 3388 m_audioIO->resume();
Chris@509 3389
Chris@684 3390 WritableWaveFileModel *modelPtr = m_recordTarget->startRecording();
Chris@684 3391 if (!modelPtr) {
Chris@586 3392 SVCERR << "ERROR: MainWindowBase::record: Recording failed" << endl;
Chris@586 3393 QMessageBox::critical
Chris@586 3394 (this, tr("Recording failed"),
Chris@586 3395 tr("<b>Recording failed</b><p>Failed to switch to record mode (some internal problem?)</p>"));
Chris@490 3396 if (action) action->setChecked(false);
Chris@477 3397 return;
Chris@477 3398 }
Chris@477 3399
Chris@684 3400 if (!modelPtr->isOK()) {
Chris@611 3401 SVCERR << "MainWindowBase::record: Model not OK, stopping and suspending" << endl;
Chris@477 3402 m_recordTarget->stopRecording();
Chris@492 3403 m_audioIO->suspend();
Chris@586 3404 if (action) action->setChecked(false);
Chris@684 3405 delete modelPtr;
Chris@477 3406 return;
Chris@477 3407 }
Chris@611 3408
Chris@611 3409 SVCERR << "MainWindowBase::record: Model is OK, continuing..." << endl;
Chris@684 3410
Chris@684 3411 QString location = modelPtr->getLocation();
Chris@487 3412
Chris@687 3413 auto modelId = ModelById::add(std::shared_ptr<Model>(modelPtr));
Chris@483 3414
Chris@483 3415 if (m_audioRecordMode == RecordReplaceSession || !getMainModel()) {
Chris@478 3416
Chris@479 3417 //!!! duplication with openAudio here
Chris@479 3418
Chris@479 3419 QString templateName = getDefaultSessionTemplate();
Chris@479 3420 bool loadedTemplate = false;
Chris@479 3421
Chris@479 3422 if (templateName != "") {
Chris@479 3423 FileOpenStatus tplStatus = openSessionTemplate(templateName);
Chris@479 3424 if (tplStatus == FileOpenCancelled) {
Chris@611 3425 SVCERR << "MainWindowBase::record: Session template open cancelled, stopping and suspending" << endl;
Chris@490 3426 m_recordTarget->stopRecording();
Chris@492 3427 m_audioIO->suspend();
Chris@684 3428 ModelById::release(modelId);
Chris@479 3429 return;
Chris@479 3430 }
Chris@479 3431 if (tplStatus != FileOpenFailed) {
Chris@479 3432 loadedTemplate = true;
Chris@479 3433 }
Chris@479 3434 }
Chris@479 3435
Chris@479 3436 if (!loadedTemplate) {
Chris@479 3437 closeSession();
Chris@479 3438 createDocument();
Chris@479 3439 }
Chris@479 3440
Chris@684 3441 ModelId prevMain = getMainModelId();
Chris@684 3442 if (!prevMain.isNone()) {
Chris@479 3443 m_playSource->removeModel(prevMain);
Chris@479 3444 }
Chris@479 3445
Chris@684 3446 m_document->setMainModel(modelId);
Chris@478 3447 setupMenus();
Chris@577 3448 findTimeRulerLayer();
Chris@478 3449
Chris@684 3450 m_originalLocation = location;
Chris@669 3451
Chris@595 3452 if (loadedTemplate || (m_sessionFile == "")) {
Chris@595 3453 CommandHistory::getInstance()->clear();
Chris@595 3454 CommandHistory::getInstance()->documentSaved();
Chris@595 3455 }
Chris@479 3456
Chris@669 3457 m_documentModified = false;
Chris@669 3458 updateWindowTitle();
Chris@669 3459
Chris@478 3460 } else {
Chris@478 3461
Chris@478 3462 CommandHistory::getInstance()->startCompoundOperation
Chris@478 3463 (tr("Import Recorded Audio"), true);
Chris@478 3464
Chris@691 3465 m_document->addNonDerivedModel(modelId);
Chris@478 3466
Chris@478 3467 AddPaneCommand *command = new AddPaneCommand(this);
Chris@478 3468 CommandHistory::getInstance()->addCommand(command);
Chris@478 3469
Chris@478 3470 Pane *pane = command->getPane();
Chris@478 3471
Chris@478 3472 if (m_timeRulerLayer) {
Chris@478 3473 m_document->addLayerToView(pane, m_timeRulerLayer);
Chris@478 3474 }
Chris@478 3475
Chris@684 3476 Layer *newLayer = m_document->createImportedLayer(modelId);
Chris@478 3477
Chris@478 3478 if (newLayer) {
Chris@478 3479 m_document->addLayerToView(pane, newLayer);
Chris@478 3480 }
Chris@595 3481
Chris@478 3482 CommandHistory::getInstance()->endCompoundOperation();
Chris@477 3483 }
Chris@479 3484
Chris@479 3485 updateMenuStates();
Chris@684 3486 m_recentFiles.addFile(location);
Chris@479 3487 currentPaneChanged(m_paneStack->getCurrentPane());
Chris@611 3488
Chris@496 3489 emit audioFileLoaded();
Chris@477 3490 }
Chris@477 3491
Chris@477 3492 void
Chris@45 3493 MainWindowBase::ffwd()
Chris@45 3494 {
Chris@45 3495 if (!getMainModel()) return;
Chris@45 3496
Chris@708 3497 sv_frame_t playbackFrame = m_viewManager->getPlaybackFrame();
Chris@708 3498 sv_frame_t frame = playbackFrame + 1;
Chris@45 3499
Chris@85 3500 Pane *pane = m_paneStack->getCurrentPane();
Chris@45 3501 Layer *layer = getSnapLayer();
Chris@435 3502 sv_samplerate_t sr = getMainModel()->getSampleRate();
Chris@45 3503
Chris@708 3504 if (!pane || !layer) {
Chris@45 3505
Chris@45 3506 frame = RealTime::realTime2Frame
Chris@357 3507 (RealTime::frame2RealTime(frame, sr) + m_defaultFfwdRwdStep, sr);
Chris@435 3508 if (frame > getMainModel()->getEndFrame()) {
Chris@45 3509 frame = getMainModel()->getEndFrame();
Chris@45 3510 }
Chris@45 3511
Chris@45 3512 } else {
Chris@45 3513
Chris@708 3514 sv_frame_t pframe = pane->alignFromReference(frame);
Chris@366 3515 int resolution = 0;
Chris@708 3516 bool success = false;
Chris@708 3517
Chris@708 3518 while (layer->snapToFeatureFrame(pane, pframe, resolution,
Chris@715 3519 Layer::SnapRight, -1)) {
Chris@708 3520 if (pane->alignToReference(pframe) > playbackFrame) {
Chris@708 3521 success = true;
Chris@708 3522 break;
Chris@708 3523 } else {
Chris@708 3524 ++pframe;
Chris@708 3525 }
Chris@708 3526 }
Chris@708 3527
Chris@708 3528 if (success) {
Chris@708 3529 frame = pane->alignToReference(pframe);
Chris@85 3530 } else {
Chris@45 3531 frame = getMainModel()->getEndFrame();
Chris@45 3532 }
Chris@45 3533 }
Chris@45 3534
Chris@45 3535 if (frame < 0) frame = 0;
Chris@45 3536
Chris@45 3537 if (m_viewManager->getPlaySelectionMode()) {
Chris@435 3538 frame = m_viewManager->constrainFrameToSelection(frame);
Chris@45 3539 }
Chris@45 3540
Chris@45 3541 m_viewManager->setPlaybackFrame(frame);
Chris@166 3542
Chris@708 3543 if (frame >= getMainModel()->getEndFrame() &&
Chris@166 3544 m_playSource &&
Chris@166 3545 m_playSource->isPlaying() &&
Chris@166 3546 !m_viewManager->getPlayLoopMode()) {
Chris@166 3547 stop();
Chris@166 3548 }
Chris@45 3549 }
Chris@45 3550
Chris@45 3551 void
Chris@45 3552 MainWindowBase::ffwdEnd()
Chris@45 3553 {
Chris@45 3554 if (!getMainModel()) return;
Chris@45 3555
Chris@139 3556 if (m_playSource &&
Chris@139 3557 m_playSource->isPlaying() &&
Chris@139 3558 !m_viewManager->getPlayLoopMode()) {
Chris@139 3559 stop();
Chris@139 3560 }
Chris@139 3561
Chris@435 3562 sv_frame_t frame = getMainModel()->getEndFrame();
Chris@45 3563
Chris@45 3564 if (m_viewManager->getPlaySelectionMode()) {
Chris@45 3565 frame = m_viewManager->constrainFrameToSelection(frame);
Chris@45 3566 }
Chris@45 3567
Chris@45 3568 m_viewManager->setPlaybackFrame(frame);
Chris@45 3569 }
Chris@45 3570
Chris@45 3571 void
Chris@166 3572 MainWindowBase::ffwdSimilar()
Chris@166 3573 {
Chris@166 3574 if (!getMainModel()) return;
Chris@166 3575
Chris@166 3576 Layer *layer = getSnapLayer();
Chris@166 3577 if (!layer) { ffwd(); return; }
Chris@166 3578
Chris@166 3579 Pane *pane = m_paneStack->getCurrentPane();
Chris@435 3580 sv_frame_t frame = m_viewManager->getPlaybackFrame();
Chris@166 3581
Chris@366 3582 int resolution = 0;
Chris@166 3583 if (pane) frame = pane->alignFromReference(frame);
Chris@166 3584 if (layer->snapToSimilarFeature(m_paneStack->getCurrentPane(),
Chris@166 3585 frame, resolution, Layer::SnapRight)) {
Chris@166 3586 if (pane) frame = pane->alignToReference(frame);
Chris@166 3587 } else {
Chris@166 3588 frame = getMainModel()->getEndFrame();
Chris@166 3589 }
Chris@166 3590
Chris@166 3591 if (frame < 0) frame = 0;
Chris@166 3592
Chris@166 3593 if (m_viewManager->getPlaySelectionMode()) {
Chris@435 3594 frame = m_viewManager->constrainFrameToSelection(frame);
Chris@166 3595 }
Chris@166 3596
Chris@166 3597 m_viewManager->setPlaybackFrame(frame);
Chris@166 3598
Chris@435 3599 if (frame == getMainModel()->getEndFrame() &&
Chris@166 3600 m_playSource &&
Chris@166 3601 m_playSource->isPlaying() &&
Chris@166 3602 !m_viewManager->getPlayLoopMode()) {
Chris@166 3603 stop();
Chris@166 3604 }
Chris@166 3605 }
Chris@166 3606
Chris@166 3607 void
Chris@45 3608 MainWindowBase::rewind()
Chris@45 3609 {
Chris@45 3610 if (!getMainModel()) return;
Chris@45 3611
Chris@708 3612 sv_frame_t playbackFrame = m_viewManager->getPlaybackFrame();
Chris@708 3613 sv_frame_t frame = playbackFrame;
Chris@45 3614 if (frame > 0) --frame;
Chris@45 3615
Chris@85 3616 Pane *pane = m_paneStack->getCurrentPane();
Chris@45 3617 Layer *layer = getSnapLayer();
Chris@435 3618 sv_samplerate_t sr = getMainModel()->getSampleRate();
Chris@45 3619
Chris@45 3620 // when rewinding during playback, we want to allow a period
Chris@45 3621 // following a rewind target point at which the rewind will go to
Chris@45 3622 // the prior point instead of the immediately neighbouring one
Chris@45 3623 if (m_playSource && m_playSource->isPlaying()) {
Chris@45 3624 RealTime ct = RealTime::frame2RealTime(frame, sr);
Chris@357 3625 ct = ct - RealTime::fromSeconds(0.15);
Chris@45 3626 if (ct < RealTime::zeroTime) ct = RealTime::zeroTime;
Chris@45 3627 frame = RealTime::realTime2Frame(ct, sr);
Chris@45 3628 }
Chris@45 3629
Chris@708 3630 if (!pane || !layer) {
Chris@45 3631
Chris@45 3632 frame = RealTime::realTime2Frame
Chris@357 3633 (RealTime::frame2RealTime(frame, sr) - m_defaultFfwdRwdStep, sr);
Chris@435 3634 if (frame < getMainModel()->getStartFrame()) {
Chris@45 3635 frame = getMainModel()->getStartFrame();
Chris@45 3636 }
Chris@45 3637
Chris@45 3638 } else {
Chris@45 3639
Chris@708 3640 sv_frame_t pframe = pane->alignFromReference(frame);
Chris@366 3641 int resolution = 0;
Chris@708 3642 bool success = false;
Chris@708 3643
Chris@708 3644 while (layer->snapToFeatureFrame(pane, pframe, resolution,
Chris@715 3645 Layer::SnapLeft, -1)) {
Chris@708 3646 if (pane->alignToReference(pframe) < playbackFrame ||
Chris@708 3647 pframe <= 0) {
Chris@708 3648 success = true;
Chris@708 3649 break;
Chris@708 3650 } else {
Chris@708 3651 --pframe;
Chris@708 3652 }
Chris@708 3653 }
Chris@708 3654
Chris@708 3655 if (success) {
Chris@708 3656 frame = pane->alignToReference(pframe);
Chris@85 3657 } else {
Chris@45 3658 frame = getMainModel()->getStartFrame();
Chris@45 3659 }
Chris@45 3660 }
Chris@45 3661
Chris@45 3662 if (frame < 0) frame = 0;
Chris@45 3663
Chris@45 3664 if (m_viewManager->getPlaySelectionMode()) {
Chris@435 3665 frame = m_viewManager->constrainFrameToSelection(frame);
Chris@45 3666 }
Chris@45 3667
Chris@45 3668 m_viewManager->setPlaybackFrame(frame);
Chris@45 3669 }
Chris@45 3670
Chris@45 3671 void
Chris@45 3672 MainWindowBase::rewindStart()
Chris@45 3673 {
Chris@45 3674 if (!getMainModel()) return;
Chris@45 3675
Chris@435 3676 sv_frame_t frame = getMainModel()->getStartFrame();
Chris@45 3677
Chris@45 3678 if (m_viewManager->getPlaySelectionMode()) {
Chris@45 3679 frame = m_viewManager->constrainFrameToSelection(frame);
Chris@45 3680 }
Chris@45 3681
Chris@45 3682 m_viewManager->setPlaybackFrame(frame);
Chris@45 3683 }
Chris@45 3684
Chris@166 3685 void
Chris@166 3686 MainWindowBase::rewindSimilar()
Chris@166 3687 {
Chris@166 3688 if (!getMainModel()) return;
Chris@166 3689
Chris@166 3690 Layer *layer = getSnapLayer();
Chris@166 3691 if (!layer) { rewind(); return; }
Chris@166 3692
Chris@166 3693 Pane *pane = m_paneStack->getCurrentPane();
Chris@435 3694 sv_frame_t frame = m_viewManager->getPlaybackFrame();
Chris@166 3695
Chris@366 3696 int resolution = 0;
Chris@166 3697 if (pane) frame = pane->alignFromReference(frame);
Chris@166 3698 if (layer->snapToSimilarFeature(m_paneStack->getCurrentPane(),
Chris@166 3699 frame, resolution, Layer::SnapLeft)) {
Chris@166 3700 if (pane) frame = pane->alignToReference(frame);
Chris@166 3701 } else {
Chris@166 3702 frame = getMainModel()->getStartFrame();
Chris@166 3703 }
Chris@166 3704
Chris@166 3705 if (frame < 0) frame = 0;
Chris@166 3706
Chris@166 3707 if (m_viewManager->getPlaySelectionMode()) {
Chris@435 3708 frame = m_viewManager->constrainFrameToSelection(frame);
Chris@166 3709 }
Chris@166 3710
Chris@166 3711 m_viewManager->setPlaybackFrame(frame);
Chris@166 3712 }
Chris@166 3713
Chris@45 3714 Layer *
Chris@45 3715 MainWindowBase::getSnapLayer() const
Chris@45 3716 {
Chris@45 3717 Pane *pane = m_paneStack->getCurrentPane();
Chris@636 3718 if (!pane) return nullptr;
Chris@45 3719
Chris@45 3720 Layer *layer = pane->getSelectedLayer();
Chris@45 3721
Chris@45 3722 if (!dynamic_cast<TimeInstantLayer *>(layer) &&
Chris@45 3723 !dynamic_cast<TimeValueLayer *>(layer) &&
Chris@194 3724 !dynamic_cast<RegionLayer *>(layer) &&
Chris@45 3725 !dynamic_cast<TimeRulerLayer *>(layer)) {
Chris@45 3726
Chris@636 3727 layer = nullptr;
Chris@45 3728
Chris@45 3729 for (int i = pane->getLayerCount(); i > 0; --i) {
Chris@45 3730 Layer *l = pane->getLayer(i-1);
Chris@45 3731 if (dynamic_cast<TimeRulerLayer *>(l)) {
Chris@45 3732 layer = l;
Chris@45 3733 break;
Chris@45 3734 }
Chris@45 3735 }
Chris@45 3736 }
Chris@45 3737
Chris@45 3738 return layer;
Chris@45 3739 }
Chris@45 3740
Chris@45 3741 void
Chris@45 3742 MainWindowBase::stop()
Chris@45 3743 {
Chris@516 3744 if (m_recordTarget &&
Chris@516 3745 m_recordTarget->isRecording()) {
Chris@477 3746 m_recordTarget->stopRecording();
Chris@477 3747 }
Chris@516 3748
Chris@516 3749 if (!m_playSource) return;
Chris@516 3750
Chris@45 3751 m_playSource->stop();
Chris@45 3752
Chris@611 3753 SVCERR << "MainWindowBase::stop: suspending" << endl;
Chris@611 3754
Chris@487 3755 if (m_audioIO) m_audioIO->suspend();
Chris@509 3756 else if (m_playTarget) m_playTarget->suspend();
Chris@487 3757
Chris@45 3758 if (m_paneStack && m_paneStack->getCurrentPane()) {
Chris@45 3759 updateVisibleRangeDisplay(m_paneStack->getCurrentPane());
Chris@45 3760 } else {
Chris@45 3761 m_myStatusMessage = "";
Chris@378 3762 getStatusLabel()->setText("");
Chris@45 3763 }
Chris@45 3764 }
Chris@45 3765
Chris@45 3766 MainWindowBase::AddPaneCommand::AddPaneCommand(MainWindowBase *mw) :
Chris@45 3767 m_mw(mw),
Chris@636 3768 m_pane(nullptr),
Chris@636 3769 m_prevCurrentPane(nullptr),
Chris@45 3770 m_added(false)
Chris@45 3771 {
Chris@45 3772 }
Chris@45 3773
Chris@45 3774 MainWindowBase::AddPaneCommand::~AddPaneCommand()
Chris@45 3775 {
Chris@45 3776 if (m_pane && !m_added) {
Chris@595 3777 m_mw->m_paneStack->deletePane(m_pane);
Chris@45 3778 }
Chris@45 3779 }
Chris@45 3780
Chris@45 3781 QString
Chris@45 3782 MainWindowBase::AddPaneCommand::getName() const
Chris@45 3783 {
Chris@45 3784 return tr("Add Pane");
Chris@45 3785 }
Chris@45 3786
Chris@45 3787 void
Chris@45 3788 MainWindowBase::AddPaneCommand::execute()
Chris@45 3789 {
Chris@45 3790 if (!m_pane) {
Chris@595 3791 m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane();
Chris@595 3792 m_pane = m_mw->m_paneStack->addPane();
Chris@45 3793
Chris@45 3794 connect(m_pane, SIGNAL(contextHelpChanged(const QString &)),
Chris@45 3795 m_mw, SLOT(contextHelpChanged(const QString &)));
Chris@45 3796 } else {
Chris@595 3797 m_mw->m_paneStack->showPane(m_pane);
Chris@45 3798 }
Chris@45 3799
Chris@45 3800 m_mw->m_paneStack->setCurrentPane(m_pane);
Chris@45 3801 m_added = true;
Chris@45 3802 }
Chris@45 3803
Chris@45 3804 void
Chris@45 3805 MainWindowBase::AddPaneCommand::unexecute()
Chris@45 3806 {
Chris@45 3807 m_mw->m_paneStack->hidePane(m_pane);
Chris@45 3808 m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane);
Chris@45 3809 m_added = false;
Chris@45 3810 }
Chris@45 3811
Chris@45 3812 MainWindowBase::RemovePaneCommand::RemovePaneCommand(MainWindowBase *mw, Pane *pane) :
Chris@45 3813 m_mw(mw),
Chris@45 3814 m_pane(pane),
Chris@636 3815 m_prevCurrentPane(nullptr),
Chris@45 3816 m_added(true)
Chris@45 3817 {
Chris@45 3818 }
Chris@45 3819
Chris@45 3820 MainWindowBase::RemovePaneCommand::~RemovePaneCommand()
Chris@45 3821 {
Chris@45 3822 if (m_pane && !m_added) {
Chris@595 3823 m_mw->m_paneStack->deletePane(m_pane);
Chris@45 3824 }
Chris@45 3825 }
Chris@45 3826
Chris@45 3827 QString
Chris@45 3828 MainWindowBase::RemovePaneCommand::getName() const
Chris@45 3829 {
Chris@45 3830 return tr("Remove Pane");
Chris@45 3831 }
Chris@45 3832
Chris@45 3833 void
Chris@45 3834 MainWindowBase::RemovePaneCommand::execute()
Chris@45 3835 {
Chris@45 3836 m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane();
Chris@45 3837 m_mw->m_paneStack->hidePane(m_pane);
Chris@45 3838 m_added = false;
Chris@45 3839 }
Chris@45 3840
Chris@45 3841 void
Chris@45 3842 MainWindowBase::RemovePaneCommand::unexecute()
Chris@45 3843 {
Chris@45 3844 m_mw->m_paneStack->showPane(m_pane);
Chris@45 3845 m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane);
Chris@45 3846 m_added = true;
Chris@45 3847 }
Chris@45 3848
Chris@45 3849 void
Chris@45 3850 MainWindowBase::deleteCurrentPane()
Chris@45 3851 {
Chris@45 3852 CommandHistory::getInstance()->startCompoundOperation
Chris@595 3853 (tr("Delete Pane"), true);
Chris@45 3854
Chris@45 3855 Pane *pane = m_paneStack->getCurrentPane();
Chris@45 3856 if (pane) {
Chris@595 3857 while (pane->getLayerCount() > 0) {
Chris@595 3858 Layer *layer = pane->getLayer(0);
Chris@595 3859 if (layer) {
Chris@595 3860 m_document->removeLayerFromView(pane, layer);
Chris@595 3861 } else {
Chris@595 3862 break;
Chris@595 3863 }
Chris@595 3864 }
Chris@595 3865
Chris@595 3866 RemovePaneCommand *command = new RemovePaneCommand(this, pane);
Chris@595 3867 CommandHistory::getInstance()->addCommand(command);
Chris@45 3868 }
Chris@45 3869
Chris@45 3870 CommandHistory::getInstance()->endCompoundOperation();
Chris@45 3871
Chris@45 3872 updateMenuStates();
Chris@45 3873 }
Chris@45 3874
Chris@45 3875 void
Chris@45 3876 MainWindowBase::deleteCurrentLayer()
Chris@45 3877 {
Chris@45 3878 Pane *pane = m_paneStack->getCurrentPane();
Chris@45 3879 if (pane) {
Chris@595 3880 Layer *layer = pane->getSelectedLayer();
Chris@595 3881 if (layer) {
Chris@595 3882 m_document->removeLayerFromView(pane, layer);
Chris@595 3883 }
Chris@45 3884 }
Chris@45 3885 updateMenuStates();
Chris@45 3886 }
Chris@45 3887
Chris@45 3888 void
Chris@123 3889 MainWindowBase::editCurrentLayer()
Chris@123 3890 {
Chris@636 3891 Layer *layer = nullptr;
Chris@123 3892 Pane *pane = m_paneStack->getCurrentPane();
Chris@123 3893 if (pane) layer = pane->getSelectedLayer();
Chris@123 3894 if (!layer) return;
Chris@123 3895
Chris@684 3896 auto tabular = ModelById::getAs<TabularModel>(layer->getModel());
Chris@124 3897 if (!tabular) {
Chris@124 3898 //!!! how to prevent this function from being active if not
Chris@124 3899 //appropriate model type? or will we ultimately support
Chris@124 3900 //tabular display for all editable models?
Chris@233 3901 SVDEBUG << "NOTE: Not a tabular model" << endl;
Chris@124 3902 return;
Chris@124 3903 }
Chris@124 3904
Chris@123 3905 if (m_layerDataDialogMap.find(layer) != m_layerDataDialogMap.end()) {
Chris@126 3906 if (!m_layerDataDialogMap[layer].isNull()) {
Chris@126 3907 m_layerDataDialogMap[layer]->show();
Chris@126 3908 m_layerDataDialogMap[layer]->raise();
Chris@126 3909 return;
Chris@126 3910 }
Chris@123 3911 }
Chris@123 3912
Chris@125 3913 QString title = layer->getLayerPresentationName();
Chris@125 3914
Chris@684 3915 ModelDataTableDialog *dialog = new ModelDataTableDialog
Chris@684 3916 (layer->getModel(), title, this);
Chris@128 3917 dialog->setAttribute(Qt::WA_DeleteOnClose);
Chris@128 3918
Chris@128 3919 connectLayerEditDialog(dialog);
Chris@123 3920
Chris@128 3921 m_layerDataDialogMap[layer] = dialog;
Chris@128 3922 m_viewDataDialogMap[pane].insert(dialog);
Chris@128 3923
Chris@128 3924 dialog->show();
Chris@128 3925 }
Chris@128 3926
Chris@128 3927 void
Chris@128 3928 MainWindowBase::connectLayerEditDialog(ModelDataTableDialog *dialog)
Chris@128 3929 {
Chris@123 3930 connect(m_viewManager,
Chris@435 3931 SIGNAL(globalCentreFrameChanged(sv_frame_t)),
Chris@123 3932 dialog,
Chris@435 3933 SLOT(userScrolledToFrame(sv_frame_t)));
Chris@127 3934
Chris@127 3935 connect(m_viewManager,
Chris@435 3936 SIGNAL(playbackFrameChanged(sv_frame_t)),
Chris@127 3937 dialog,
Chris@435 3938 SLOT(playbackScrolledToFrame(sv_frame_t)));
Chris@127 3939
Chris@123 3940 connect(dialog,
Chris@435 3941 SIGNAL(scrollToFrame(sv_frame_t)),
Chris@123 3942 m_viewManager,
Chris@435 3943 SLOT(setGlobalCentreFrame(sv_frame_t)));
Chris@129 3944
Chris@129 3945 connect(dialog,
Chris@435 3946 SIGNAL(scrollToFrame(sv_frame_t)),
Chris@129 3947 m_viewManager,
Chris@435 3948 SLOT(setPlaybackFrame(sv_frame_t)));
Chris@128 3949 }
Chris@123 3950
Chris@123 3951 void
Chris@73 3952 MainWindowBase::previousPane()
Chris@73 3953 {
Chris@73 3954 if (!m_paneStack) return;
Chris@73 3955
Chris@73 3956 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@73 3957 if (!currentPane) return;
Chris@73 3958
Chris@73 3959 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@73 3960 if (m_paneStack->getPane(i) == currentPane) {
Chris@73 3961 if (i == 0) return;
Chris@73 3962 m_paneStack->setCurrentPane(m_paneStack->getPane(i-1));
Chris@73 3963 updateMenuStates();
Chris@73 3964 return;
Chris@73 3965 }
Chris@73 3966 }
Chris@73 3967 }
Chris@73 3968
Chris@73 3969 void
Chris@73 3970 MainWindowBase::nextPane()
Chris@73 3971 {
Chris@73 3972 if (!m_paneStack) return;
Chris@73 3973
Chris@73 3974 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@73 3975 if (!currentPane) return;
Chris@73 3976
Chris@73 3977 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@73 3978 if (m_paneStack->getPane(i) == currentPane) {
Chris@73 3979 if (i == m_paneStack->getPaneCount()-1) return;
Chris@73 3980 m_paneStack->setCurrentPane(m_paneStack->getPane(i+1));
Chris@73 3981 updateMenuStates();
Chris@73 3982 return;
Chris@73 3983 }
Chris@73 3984 }
Chris@73 3985 }
Chris@73 3986
Chris@73 3987 void
Chris@73 3988 MainWindowBase::previousLayer()
Chris@73 3989 {
Chris@73 3990 if (!m_paneStack) return;
Chris@73 3991
Chris@73 3992 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@73 3993 if (!currentPane) return;
Chris@73 3994
Chris@403 3995 int count = currentPane->getLayerCount();
Chris@403 3996 if (count == 0) return;
Chris@403 3997
Chris@73 3998 Layer *currentLayer = currentPane->getSelectedLayer();
Chris@403 3999
Chris@403 4000 if (!currentLayer) {
Chris@403 4001 // The pane itself is current
Chris@403 4002 m_paneStack->setCurrentLayer
Chris@403 4003 (currentPane, currentPane->getFixedOrderLayer(count-1));
Chris@403 4004 } else {
Chris@403 4005 for (int i = 0; i < count; ++i) {
Chris@403 4006 if (currentPane->getFixedOrderLayer(i) == currentLayer) {
Chris@403 4007 if (i == 0) {
Chris@403 4008 m_paneStack->setCurrentLayer
Chris@636 4009 (currentPane, nullptr); // pane
Chris@403 4010 } else {
Chris@403 4011 m_paneStack->setCurrentLayer
Chris@403 4012 (currentPane, currentPane->getFixedOrderLayer(i-1));
Chris@403 4013 }
Chris@403 4014 break;
Chris@403 4015 }
Chris@73 4016 }
Chris@73 4017 }
Chris@403 4018
Chris@403 4019 updateMenuStates();
Chris@73 4020 }
Chris@73 4021
Chris@73 4022 void
Chris@73 4023 MainWindowBase::nextLayer()
Chris@73 4024 {
Chris@73 4025 if (!m_paneStack) return;
Chris@73 4026
Chris@73 4027 Pane *currentPane = m_paneStack->getCurrentPane();
Chris@73 4028 if (!currentPane) return;
Chris@73 4029
Chris@403 4030 int count = currentPane->getLayerCount();
Chris@403 4031 if (count == 0) return;
Chris@403 4032
Chris@73 4033 Layer *currentLayer = currentPane->getSelectedLayer();
Chris@403 4034
Chris@403 4035 if (!currentLayer) {
Chris@403 4036 // The pane itself is current
Chris@403 4037 m_paneStack->setCurrentLayer
Chris@403 4038 (currentPane, currentPane->getFixedOrderLayer(0));
Chris@403 4039 } else {
Chris@403 4040 for (int i = 0; i < count; ++i) {
Chris@403 4041 if (currentPane->getFixedOrderLayer(i) == currentLayer) {
Chris@403 4042 if (i == currentPane->getLayerCount()-1) {
Chris@403 4043 m_paneStack->setCurrentLayer
Chris@636 4044 (currentPane, nullptr); // pane
Chris@403 4045 } else {
Chris@403 4046 m_paneStack->setCurrentLayer
Chris@403 4047 (currentPane, currentPane->getFixedOrderLayer(i+1));
Chris@403 4048 }
Chris@403 4049 break;
Chris@403 4050 }
Chris@73 4051 }
Chris@73 4052 }
Chris@403 4053
Chris@403 4054 updateMenuStates();
Chris@73 4055 }
Chris@73 4056
Chris@73 4057 void
Chris@435 4058 MainWindowBase::playbackFrameChanged(sv_frame_t frame)
Chris@45 4059 {
Chris@45 4060 if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
Chris@45 4061
Chris@187 4062 updatePositionStatusDisplays();
Chris@187 4063
Chris@45 4064 RealTime now = RealTime::frame2RealTime
Chris@45 4065 (frame, getMainModel()->getSampleRate());
Chris@45 4066
Chris@45 4067 if (now.sec == m_lastPlayStatusSec) return;
Chris@45 4068
Chris@45 4069 RealTime then = RealTime::frame2RealTime
Chris@45 4070 (m_playSource->getPlayEndFrame(), getMainModel()->getSampleRate());
Chris@45 4071
Chris@45 4072 QString nowStr;
Chris@45 4073 QString thenStr;
Chris@45 4074 QString remainingStr;
Chris@45 4075
Chris@45 4076 if (then.sec > 10) {
Chris@45 4077 nowStr = now.toSecText().c_str();
Chris@45 4078 thenStr = then.toSecText().c_str();
Chris@45 4079 remainingStr = (then - now).toSecText().c_str();
Chris@45 4080 m_lastPlayStatusSec = now.sec;
Chris@45 4081 } else {
Chris@45 4082 nowStr = now.toText(true).c_str();
Chris@45 4083 thenStr = then.toText(true).c_str();
Chris@45 4084 remainingStr = (then - now).toText(true).c_str();
Chris@45 4085 }
Chris@45 4086
Chris@45 4087 m_myStatusMessage = tr("Playing: %1 of %2 (%3 remaining)")
Chris@45 4088 .arg(nowStr).arg(thenStr).arg(remainingStr);
Chris@45 4089
Chris@378 4090 getStatusLabel()->setText(m_myStatusMessage);
Chris@45 4091 }
Chris@45 4092
Chris@45 4093 void
Chris@486 4094 MainWindowBase::recordDurationChanged(sv_frame_t frame, sv_samplerate_t rate)
Chris@486 4095 {
Chris@486 4096 RealTime duration = RealTime::frame2RealTime(frame, rate);
Chris@486 4097 QString durStr = duration.toSecText().c_str();
Chris@486 4098
Chris@486 4099 m_myStatusMessage = tr("Recording: %1").arg(durStr);
Chris@486 4100
Chris@486 4101 getStatusLabel()->setText(m_myStatusMessage);
Chris@486 4102 }
Chris@486 4103
Chris@486 4104 void
Chris@435 4105 MainWindowBase::globalCentreFrameChanged(sv_frame_t )
Chris@45 4106 {
Chris@45 4107 if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
Chris@636 4108 Pane *p = nullptr;
Chris@45 4109 if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
Chris@45 4110 if (!p->getFollowGlobalPan()) return;
Chris@45 4111 updateVisibleRangeDisplay(p);
Chris@45 4112 }
Chris@45 4113
Chris@45 4114 void
Chris@435 4115 MainWindowBase::viewCentreFrameChanged(View *v, sv_frame_t frame)
Chris@45 4116 {
Chris@233 4117 // SVDEBUG << "MainWindowBase::viewCentreFrameChanged(" << v << "," << frame << ")" << endl;
Chris@123 4118
Chris@123 4119 if (m_viewDataDialogMap.find(v) != m_viewDataDialogMap.end()) {
Chris@123 4120 for (DataDialogSet::iterator i = m_viewDataDialogMap[v].begin();
Chris@123 4121 i != m_viewDataDialogMap[v].end(); ++i) {
Chris@127 4122 (*i)->userScrolledToFrame(frame);
Chris@123 4123 }
Chris@123 4124 }
Chris@45 4125 if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
Chris@636 4126 Pane *p = nullptr;
Chris@45 4127 if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
Chris@45 4128 if (v == p) updateVisibleRangeDisplay(p);
Chris@45 4129 }
Chris@45 4130
Chris@45 4131 void
Chris@624 4132 MainWindowBase::viewZoomLevelChanged(View *v, ZoomLevel, bool )
Chris@45 4133 {
Chris@45 4134 if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
Chris@636 4135 Pane *p = nullptr;
Chris@45 4136 if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
Chris@45 4137 if (v == p) updateVisibleRangeDisplay(p);
Chris@45 4138 }
Chris@45 4139
Chris@45 4140 void
Chris@45 4141 MainWindowBase::layerAdded(Layer *)
Chris@45 4142 {
Chris@233 4143 // SVDEBUG << "MainWindowBase::layerAdded(" << layer << ")" << endl;
Chris@45 4144 updateMenuStates();
Chris@45 4145 }
Chris@45 4146
Chris@45 4147 void
Chris@45 4148 MainWindowBase::layerRemoved(Layer *)
Chris@45 4149 {
Chris@233 4150 // SVDEBUG << "MainWindowBase::layerRemoved(" << layer << ")" << endl;
Chris@45 4151 updateMenuStates();
Chris@45 4152 }
Chris@45 4153
Chris@45 4154 void
Chris@45 4155 MainWindowBase::layerAboutToBeDeleted(Layer *layer)
Chris@45 4156 {
Chris@233 4157 // SVDEBUG << "MainWindowBase::layerAboutToBeDeleted(" << layer << ")" << endl;
Chris@123 4158
Chris@128 4159 removeLayerEditDialog(layer);
Chris@123 4160
Chris@47 4161 if (m_timeRulerLayer && (layer == m_timeRulerLayer)) {
Chris@595 4162 // cerr << "(this is the time ruler layer)" << endl;
Chris@636 4163 m_timeRulerLayer = nullptr;
Chris@45 4164 }
Chris@45 4165 }
Chris@45 4166
Chris@45 4167 void
Chris@45 4168 MainWindowBase::layerInAView(Layer *layer, bool inAView)
Chris@45 4169 {
Chris@233 4170 // SVDEBUG << "MainWindowBase::layerInAView(" << layer << "," << inAView << ")" << endl;
Chris@128 4171
Chris@128 4172 if (!inAView) removeLayerEditDialog(layer);
Chris@45 4173
Chris@45 4174 // Check whether we need to add or remove model from play source
Chris@684 4175 ModelId modelId = layer->getModel();
Chris@684 4176 if (!modelId.isNone()) {
Chris@45 4177 if (inAView) {
Chris@684 4178 m_playSource->addModel(modelId);
Chris@45 4179 } else {
Chris@45 4180 bool found = false;
Chris@45 4181 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@45 4182 Pane *pane = m_paneStack->getPane(i);
Chris@45 4183 if (!pane) continue;
Chris@45 4184 for (int j = 0; j < pane->getLayerCount(); ++j) {
Chris@45 4185 Layer *pl = pane->getLayer(j);
Chris@183 4186 if (pl &&
Chris@183 4187 !dynamic_cast<TimeRulerLayer *>(pl) &&
Chris@684 4188 (pl->getModel() == modelId)) {
Chris@45 4189 found = true;
Chris@45 4190 break;
Chris@45 4191 }
Chris@45 4192 }
Chris@45 4193 if (found) break;
Chris@45 4194 }
Chris@173 4195 if (!found) {
Chris@684 4196 m_playSource->removeModel(modelId);
Chris@173 4197 }
Chris@45 4198 }
Chris@45 4199 }
Chris@45 4200
Chris@45 4201 updateMenuStates();
Chris@45 4202 }
Chris@45 4203
Chris@45 4204 void
Chris@128 4205 MainWindowBase::removeLayerEditDialog(Layer *layer)
Chris@128 4206 {
Chris@128 4207 if (m_layerDataDialogMap.find(layer) != m_layerDataDialogMap.end()) {
Chris@128 4208
Chris@128 4209 ModelDataTableDialog *dialog = m_layerDataDialogMap[layer];
Chris@128 4210
Chris@128 4211 for (ViewDataDialogMap::iterator vi = m_viewDataDialogMap.begin();
Chris@128 4212 vi != m_viewDataDialogMap.end(); ++vi) {
Chris@128 4213 vi->second.erase(dialog);
Chris@128 4214 }
Chris@128 4215
Chris@128 4216 m_layerDataDialogMap.erase(layer);
Chris@128 4217 delete dialog;
Chris@128 4218 }
Chris@128 4219 }
Chris@128 4220
Chris@128 4221 void
Chris@684 4222 MainWindowBase::modelAdded(ModelId model)
Chris@45 4223 {
Chris@233 4224 // SVDEBUG << "MainWindowBase::modelAdded(" << model << ")" << endl;
Chris@684 4225 std::cerr << "\nAdding model " << model << " to playsource " << std::endl;
Chris@45 4226 m_playSource->addModel(model);
Chris@45 4227 }
Chris@45 4228
Chris@45 4229 void
Chris@684 4230 MainWindowBase::mainModelChanged(ModelId modelId)
Chris@45 4231 {
Chris@233 4232 // SVDEBUG << "MainWindowBase::mainModelChanged(" << model << ")" << endl;
Chris@45 4233 updateDescriptionLabel();
Chris@684 4234 auto model = ModelById::getAs<WaveFileModel>(modelId);
Chris@45 4235 if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate());
Chris@714 4236 if (model && !(m_playTarget || m_audioIO) && (m_audioMode != AUDIO_NONE)) {
Chris@475 4237 createAudioIO();
Chris@360 4238 }
Chris@45 4239 }
Chris@45 4240
Chris@45 4241 void
Chris@55 4242 MainWindowBase::paneDeleteButtonClicked(Pane *pane)
Chris@55 4243 {
Chris@55 4244 bool found = false;
Chris@55 4245 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
Chris@55 4246 if (m_paneStack->getPane(i) == pane) {
Chris@55 4247 found = true;
Chris@55 4248 break;
Chris@55 4249 }
Chris@55 4250 }
Chris@55 4251 if (!found) {
Chris@233 4252 SVDEBUG << "MainWindowBase::paneDeleteButtonClicked: Unknown pane "
Chris@229 4253 << pane << endl;
Chris@55 4254 return;
Chris@55 4255 }
Chris@55 4256
Chris@55 4257 CommandHistory::getInstance()->startCompoundOperation
Chris@595 4258 (tr("Delete Pane"), true);
Chris@55 4259
Chris@55 4260 while (pane->getLayerCount() > 0) {
Chris@637 4261 Layer *layer = pane->getLayer(pane->getLayerCount() - 1);
Chris@55 4262 if (layer) {
Chris@55 4263 m_document->removeLayerFromView(pane, layer);
Chris@55 4264 } else {
Chris@55 4265 break;
Chris@55 4266 }
Chris@55 4267 }
Chris@55 4268
Chris@55 4269 RemovePaneCommand *command = new RemovePaneCommand(this, pane);
Chris@55 4270 CommandHistory::getInstance()->addCommand(command);
Chris@55 4271
Chris@55 4272 CommandHistory::getInstance()->endCompoundOperation();
Chris@55 4273
Chris@55 4274 updateMenuStates();
Chris@55 4275 }
Chris@55 4276
Chris@55 4277 void
Chris@684 4278 MainWindowBase::alignmentComplete(ModelId alignmentModelId)
Chris@429 4279 {
Chris@684 4280 cerr << "MainWindowBase::alignmentComplete(" << alignmentModelId << ")" << endl;
Chris@429 4281 }
Chris@429 4282
Chris@429 4283 void
Chris@45 4284 MainWindowBase::pollOSC()
Chris@45 4285 {
Chris@45 4286 if (!m_oscQueue || m_oscQueue->isEmpty()) return;
Chris@725 4287
Chris@758 4288 if (m_handlingOSC) {
Chris@758 4289 SVDEBUG << "MainWindowBase::pollOSC: "
Chris@758 4290 << "not making nested invocations, waiting"
Chris@758 4291 << endl;
Chris@758 4292 return;
Chris@758 4293 }
Chris@758 4294
Chris@758 4295 m_handlingOSC = true;
Chris@758 4296
Chris@725 4297 while (!m_oscQueue->isEmpty()) {
Chris@725 4298
Chris@725 4299 if (m_openingAudioFile) {
Chris@725 4300 SVDEBUG << "MainWindowBase::pollOSC: "
Chris@725 4301 << "waiting for audio to finish loading"
Chris@725 4302 << endl;
Chris@758 4303 m_handlingOSC = false;
Chris@725 4304 return;
Chris@725 4305 }
Chris@725 4306
Chris@725 4307 if (ModelTransformerFactory::getInstance()->haveRunningTransformers()) {
Chris@725 4308 SVDEBUG << "MainWindowBase::pollOSC: "
Chris@725 4309 << "waiting for running transforms to complete"
Chris@725 4310 << endl;
Chris@758 4311 m_handlingOSC = false;
Chris@725 4312 return;
Chris@725 4313 }
Chris@725 4314
Chris@725 4315 SVDEBUG << "MainWindowBase::pollOSC: have "
Chris@725 4316 << m_oscQueue->getMessagesAvailable()
Chris@725 4317 << " messages" << endl;
Chris@725 4318
Chris@725 4319 OSCMessage message = m_oscQueue->readMessage();
Chris@725 4320
Chris@725 4321 if (message.getTarget() != 0) {
Chris@725 4322 SVCERR << "MainWindowBase::pollOSC: ignoring message with target "
Chris@725 4323 << message.getTarget() << " (we are target 0)" << endl;
Chris@758 4324 m_handlingOSC = false;
Chris@725 4325 continue;
Chris@725 4326 }
Chris@725 4327
Chris@725 4328 handleOSCMessage(message);
Chris@725 4329
Chris@725 4330 disconnect(m_oscQueue, SIGNAL(messagesAvailable()),
Chris@725 4331 this, SLOT(pollOSC()));
Chris@725 4332
Chris@725 4333 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents |
Chris@725 4334 QEventLoop::ExcludeSocketNotifiers);
Chris@45 4335 }
Chris@758 4336
Chris@758 4337 m_handlingOSC = false;
Chris@758 4338
Chris@758 4339 connect(m_oscQueue, SIGNAL(messagesAvailable()),
Chris@758 4340 this, SLOT(pollOSC()));
Chris@45 4341 }
Chris@45 4342
Chris@45 4343 void
Chris@45 4344 MainWindowBase::inProgressSelectionChanged()
Chris@45 4345 {
Chris@636 4346 Pane *currentPane = nullptr;
Chris@45 4347 if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
justin@331 4348 if (currentPane) {
justin@331 4349 //cerr << "JTEST: mouse event on selection pane" << endl;
justin@331 4350 updateVisibleRangeDisplay(currentPane);
justin@331 4351 }
Chris@45 4352 }
Chris@45 4353
Chris@45 4354 void
Chris@45 4355 MainWindowBase::contextHelpChanged(const QString &s)
Chris@45 4356 {
Chris@378 4357 QLabel *lab = getStatusLabel();
Chris@375 4358
Chris@45 4359 if (s == "" && m_myStatusMessage != "") {
Chris@378 4360 if (lab->text() != m_myStatusMessage) {
Chris@378 4361 lab->setText(m_myStatusMessage);
Chris@375 4362 }
Chris@45 4363 return;
Chris@45 4364 }
Chris@375 4365
Chris@378 4366 lab->setText(s);
Chris@45 4367 }
Chris@45 4368
Chris@45 4369 void
Chris@45 4370 MainWindowBase::openHelpUrl(QString url)
Chris@45 4371 {
Chris@45 4372 // This method mostly lifted from Qt Assistant source code
Chris@45 4373
Chris@45 4374 QProcess *process = new QProcess(this);
Chris@45 4375 connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
Chris@45 4376
Chris@45 4377 QStringList args;
Chris@45 4378
Chris@45 4379 #ifdef Q_OS_MAC
Chris@45 4380 args.append(url);
Chris@45 4381 process->start("open", args);
Chris@45 4382 #else
Chris@45 4383 #ifdef Q_OS_WIN32
Chris@599 4384 std::string pfiles;
Chris@599 4385 (void)getEnvUtf8("ProgramFiles", pfiles);
Chris@599 4386 QString command =
Chris@599 4387 QString::fromStdString(pfiles) +
Chris@599 4388 QString("\\Internet Explorer\\IEXPLORE.EXE");
Chris@358 4389
Chris@358 4390 args.append(url);
Chris@358 4391 process->start(command, args);
Chris@45 4392 #else
Chris@45 4393 if (!qgetenv("KDE_FULL_SESSION").isEmpty()) {
Chris@45 4394 args.append("exec");
Chris@45 4395 args.append(url);
Chris@45 4396 process->start("kfmclient", args);
Chris@45 4397 } else if (!qgetenv("BROWSER").isEmpty()) {
Chris@45 4398 args.append(url);
Chris@45 4399 process->start(qgetenv("BROWSER"), args);
Chris@45 4400 } else {
Chris@45 4401 args.append(url);
Chris@45 4402 process->start("firefox", args);
Chris@45 4403 }
Chris@45 4404 #endif
Chris@45 4405 #endif
Chris@45 4406 }
Chris@45 4407
Chris@483 4408 void
Chris@483 4409 MainWindowBase::openLocalFolder(QString path)
Chris@483 4410 {
Chris@483 4411 QDir d(path);
Chris@483 4412 if (d.exists()) {
Chris@483 4413 QStringList args;
Chris@483 4414 QString path = d.canonicalPath();
Chris@483 4415 #if defined Q_OS_WIN32
Chris@483 4416 // Although the Win32 API is quite happy to have
Chris@483 4417 // forward slashes as directory separators, Windows
Chris@483 4418 // Explorer is not
Chris@483 4419 path = path.replace('/', '\\');
Chris@483 4420 args << path;
Chris@483 4421 QProcess::execute("c:/windows/explorer.exe", args);
Chris@483 4422 #else
Chris@483 4423 args << path;
Chris@605 4424 QProcess process;
Chris@605 4425 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
Chris@606 4426 env.insert("LD_LIBRARY_PATH", "");
Chris@605 4427 process.setProcessEnvironment(env);
Chris@605 4428 process.start(
Chris@483 4429 #if defined Q_OS_MAC
Chris@483 4430 "/usr/bin/open",
Chris@483 4431 #else
Chris@483 4432 "/usr/bin/xdg-open",
Chris@483 4433 #endif
Chris@483 4434 args);
Chris@608 4435 process.waitForFinished();
Chris@483 4436 #endif
Chris@483 4437 }
Chris@483 4438 }
Chris@483 4439