annotate framework/MainWindowBase.cpp @ 720:55f317633b93

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