Chris@45: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@45: Chris@45: /* Chris@45: Sonic Visualiser Chris@45: An audio file viewer and annotation editor. Chris@45: Centre for Digital Music, Queen Mary, University of London. Chris@45: This file copyright 2006-2007 Chris Cannam and QMUL. Chris@45: Chris@45: This program is free software; you can redistribute it and/or Chris@45: modify it under the terms of the GNU General Public License as Chris@45: published by the Free Software Foundation; either version 2 of the Chris@45: License, or (at your option) any later version. See the file Chris@45: COPYING included with this distribution for more information. Chris@45: */ Chris@45: Chris@45: #include "MainWindowBase.h" Chris@46: #include "Document.h" Chris@45: Chris@45: #include "view/Pane.h" Chris@45: #include "view/PaneStack.h" Chris@479: #include "data/model/ReadOnlyWaveFileModel.h" Chris@477: #include "data/model/WritableWaveFileModel.h" Chris@45: #include "data/model/SparseOneDimensionalModel.h" Chris@45: #include "data/model/NoteModel.h" Chris@45: #include "data/model/Labeller.h" Chris@124: #include "data/model/TabularModel.h" Chris@45: #include "view/ViewManager.h" Chris@45: Chris@45: #include "layer/WaveformLayer.h" Chris@45: #include "layer/TimeRulerLayer.h" Chris@45: #include "layer/TimeInstantLayer.h" Chris@45: #include "layer/TimeValueLayer.h" Chris@45: #include "layer/Colour3DPlotLayer.h" Chris@45: #include "layer/SliceLayer.h" Chris@45: #include "layer/SliceableLayer.h" Chris@45: #include "layer/ImageLayer.h" Chris@184: #include "layer/NoteLayer.h" matthiasm@267: #include "layer/FlexiNoteLayer.h" Chris@184: #include "layer/RegionLayer.h" Chris@45: Chris@45: #include "widgets/ListInputDialog.h" Chris@105: #include "widgets/CommandHistory.h" Chris@109: #include "widgets/ProgressDialog.h" Chris@109: #include "widgets/MIDIFileImportDialog.h" Chris@109: #include "widgets/CSVFormatDialog.h" Chris@123: #include "widgets/ModelDataTableDialog.h" Chris@341: #include "widgets/InteractiveFileFinder.h" Chris@45: Chris@468: #include "audio/AudioCallbackPlaySource.h" Chris@574: #include "audio/AudioCallbackRecordTarget.h" Chris@468: #include "audio/PlaySpeedRangeMapper.h" Chris@475: Chris@45: #include "data/fileio/DataFileReaderFactory.h" Chris@45: #include "data/fileio/PlaylistFileReader.h" Chris@45: #include "data/fileio/WavFileWriter.h" Chris@45: #include "data/fileio/MIDIFileWriter.h" Chris@659: #include "data/fileio/CSVFileWriter.h" Chris@45: #include "data/fileio/BZipFileDevice.h" Chris@45: #include "data/fileio/FileSource.h" Chris@152: #include "data/fileio/AudioFileReaderFactory.h" Chris@134: #include "rdf/RDFImporter.h" Chris@659: #include "rdf/RDFExporter.h" Chris@45: Chris@45: #include "base/RecentFiles.h" Chris@45: Chris@45: #include "base/XmlExportable.h" Chris@45: #include "base/Profiler.h" Chris@45: #include "base/Preferences.h" Chris@217: #include "base/TempWriteFile.h" Chris@217: #include "base/Exceptions.h" Chris@223: #include "base/ResourceFinder.h" Chris@45: Chris@45: #include "data/osc/OSCQueue.h" Chris@157: #include "data/midi/MIDIInput.h" Chris@654: #include "OSCScript.h" Chris@45: Chris@599: #include "system/System.h" Chris@599: Chris@468: #include Chris@475: #include Chris@468: #include Chris@551: #include Chris@468: Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@432: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: #include Chris@684: #include Chris@354: #include Chris@45: Chris@45: #include Chris@45: #include Chris@45: #include Chris@45: Chris@45: using std::vector; Chris@45: using std::map; Chris@45: using std::set; Chris@45: Chris@255: #ifdef Q_WS_X11 Chris@255: #define Window X11Window Chris@255: #include Chris@255: #include Chris@255: #include Chris@255: #include Chris@255: Chris@255: static int handle_x11_error(Display *dpy, XErrorEvent *err) Chris@255: { Chris@255: char errstr[256]; Chris@255: XGetErrorText(dpy, err->error_code, errstr, 256); Chris@255: if (err->error_code != BadWindow) { Chris@595: cerr << "Sonic Visualiser: X Error: " Chris@595: << errstr << " " << int(err->error_code) Chris@595: << "\nin major opcode: " Chris@595: << int(err->request_code) << endl; Chris@255: } Chris@255: return 0; Chris@255: } Chris@255: #undef Window Chris@255: #endif Chris@45: Chris@475: MainWindowBase::MainWindowBase(SoundOptions options) : Chris@636: m_document(nullptr), Chris@636: m_paneStack(nullptr), Chris@636: m_viewManager(nullptr), Chris@636: m_timeRulerLayer(nullptr), Chris@475: m_soundOptions(options), Chris@636: m_playSource(nullptr), Chris@636: m_recordTarget(nullptr), Chris@636: m_resamplerWrapper(nullptr), Chris@636: m_playTarget(nullptr), Chris@636: m_audioIO(nullptr), Chris@636: m_oscQueue(nullptr), Chris@636: m_oscQueueStarter(nullptr), Chris@654: m_oscScript(nullptr), Chris@636: m_midiInput(nullptr), Chris@45: m_recentFiles("RecentFiles", 20), Chris@54: m_recentTransforms("RecentTransforms", 20), Chris@45: m_documentModified(false), Chris@45: m_openingAudioFile(false), Chris@45: m_abandoning(false), Chris@636: m_labeller(nullptr), Chris@357: m_lastPlayStatusSec(0), Chris@357: m_initialDarkBackground(false), Chris@378: m_defaultFfwdRwdStep(2, 0), Chris@483: m_audioRecordMode(RecordCreateAdditionalModel), Chris@636: m_statusLabel(nullptr), Chris@426: m_iconsVisibleInMenus(true), Chris@636: m_menuShortcutMapper(nullptr) Chris@45: { Chris@113: Profiler profiler("MainWindowBase::MainWindowBase"); Chris@113: Chris@591: SVDEBUG << "MainWindowBase::MainWindowBase" << endl; Chris@591: Chris@475: if (options & WithAudioInput) { Chris@475: if (!(options & WithAudioOutput)) { Chris@591: SVCERR << "WARNING: MainWindowBase: WithAudioInput requires WithAudioOutput -- recording will not work" << endl; Chris@475: } Chris@475: } Chris@475: Chris@438: qRegisterMetaType("sv_frame_t"); Chris@438: qRegisterMetaType("sv_samplerate_t"); Chris@687: qRegisterMetaType("ModelId"); Chris@438: Chris@255: #ifdef Q_WS_X11 Chris@255: XSetErrorHandler(handle_x11_error); Chris@255: #endif Chris@255: Chris@452: connect(this, SIGNAL(hideSplash()), this, SLOT(emitHideSplash())); Chris@452: Chris@45: connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()), Chris@595: this, SLOT(documentModified())); Chris@45: connect(CommandHistory::getInstance(), SIGNAL(documentRestored()), Chris@595: this, SLOT(documentRestored())); Chris@45: Chris@591: SVDEBUG << "MainWindowBase: Creating view manager" << endl; Chris@591: Chris@45: m_viewManager = new ViewManager(); Chris@45: connect(m_viewManager, SIGNAL(selectionChanged()), Chris@595: this, SLOT(updateMenuStates())); Chris@45: connect(m_viewManager, SIGNAL(inProgressSelectionChanged()), Chris@595: this, SLOT(inProgressSelectionChanged())); Chris@45: Chris@591: SVDEBUG << "MainWindowBase: Calculating view font size" << endl; Chris@591: Chris@105: // set a sensible default font size for views -- cannot do this Chris@105: // in Preferences, which is in base and not supposed to use QtGui Chris@436: int viewFontSize = int(QApplication::font().pointSize() * 0.9); Chris@105: QSettings settings; Chris@105: settings.beginGroup("Preferences"); Chris@105: viewFontSize = settings.value("view-font-size", viewFontSize).toInt(); Chris@105: settings.setValue("view-font-size", viewFontSize); Chris@105: settings.endGroup(); Chris@105: Chris@591: SVDEBUG << "MainWindowBase: View font size is " << viewFontSize << endl; Chris@591: Chris@511: #ifdef NOT_DEFINED // This no longer works correctly on any platform AFAICS Chris@45: Preferences::BackgroundMode mode = Chris@45: Preferences::getInstance()->getBackgroundMode(); Chris@45: m_initialDarkBackground = m_viewManager->getGlobalDarkBackground(); Chris@45: if (mode != Preferences::BackgroundFromTheme) { Chris@45: m_viewManager->setGlobalDarkBackground Chris@45: (mode == Preferences::DarkBackground); Chris@45: } Chris@511: #endif Chris@45: Chris@636: m_paneStack = new PaneStack(nullptr, m_viewManager); Chris@45: connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)), Chris@595: this, SLOT(currentPaneChanged(Pane *))); Chris@45: connect(m_paneStack, SIGNAL(currentLayerChanged(Pane *, Layer *)), Chris@595: this, SLOT(currentLayerChanged(Pane *, Layer *))); Chris@45: connect(m_paneStack, SIGNAL(rightButtonMenuRequested(Pane *, QPoint)), Chris@45: this, SLOT(rightButtonMenuRequested(Pane *, QPoint))); Chris@45: connect(m_paneStack, SIGNAL(contextHelpChanged(const QString &)), Chris@45: this, SLOT(contextHelpChanged(const QString &))); Chris@45: connect(m_paneStack, SIGNAL(paneAdded(Pane *)), Chris@45: this, SLOT(paneAdded(Pane *))); Chris@45: connect(m_paneStack, SIGNAL(paneHidden(Pane *)), Chris@45: this, SLOT(paneHidden(Pane *))); Chris@45: connect(m_paneStack, SIGNAL(paneAboutToBeDeleted(Pane *)), Chris@45: this, SLOT(paneAboutToBeDeleted(Pane *))); Chris@45: connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QStringList)), Chris@45: this, SLOT(paneDropAccepted(Pane *, QStringList))); Chris@45: connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QString)), Chris@45: this, SLOT(paneDropAccepted(Pane *, QString))); Chris@55: connect(m_paneStack, SIGNAL(paneDeleteButtonClicked(Pane *)), Chris@55: this, SLOT(paneDeleteButtonClicked(Pane *))); Chris@591: Chris@591: SVDEBUG << "MainWindowBase: Creating play source" << endl; Chris@571: Chris@574: m_playSource = new AudioCallbackPlaySource Chris@574: (m_viewManager, QApplication::applicationName()); Chris@574: Chris@475: if (m_soundOptions & WithAudioInput) { Chris@591: SVDEBUG << "MainWindowBase: Creating record target" << endl; Chris@574: m_recordTarget = new AudioCallbackRecordTarget Chris@574: (m_viewManager, QApplication::applicationName()); Chris@572: connect(m_recordTarget, Chris@572: SIGNAL(recordDurationChanged(sv_frame_t, sv_samplerate_t)), Chris@572: this, Chris@572: SLOT(recordDurationChanged(sv_frame_t, sv_samplerate_t))); Chris@475: } Chris@45: Chris@436: connect(m_playSource, SIGNAL(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)), Chris@591: this, SLOT(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool))); Chris@570: connect(m_playSource, SIGNAL(channelCountIncreased(int)), Chris@570: this, SLOT(audioChannelCountIncreased(int))); Chris@45: connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()), Chris@45: this, SLOT(audioOverloadPluginDisabled())); Chris@130: connect(m_playSource, SIGNAL(audioTimeStretchMultiChannelDisabled()), Chris@130: this, SLOT(audioTimeStretchMultiChannelDisabled())); Chris@45: Chris@574: connect(m_viewManager, SIGNAL(monitoringLevelsChanged(float, float)), Chris@595: this, SLOT(monitoringLevelsChanged(float, float))); Chris@45: Chris@435: connect(m_viewManager, SIGNAL(playbackFrameChanged(sv_frame_t)), Chris@435: this, SLOT(playbackFrameChanged(sv_frame_t))); Chris@435: Chris@435: connect(m_viewManager, SIGNAL(globalCentreFrameChanged(sv_frame_t)), Chris@435: this, SLOT(globalCentreFrameChanged(sv_frame_t))); Chris@435: Chris@435: connect(m_viewManager, SIGNAL(viewCentreFrameChanged(View *, sv_frame_t)), Chris@435: this, SLOT(viewCentreFrameChanged(View *, sv_frame_t))); Chris@366: Chris@640: connect(m_viewManager, SIGNAL(viewZoomLevelChanged(View *, ZoomLevel, bool)), Chris@640: this, SLOT(viewZoomLevelChanged(View *, ZoomLevel, bool))); Chris@45: Chris@45: connect(Preferences::getInstance(), Chris@45: SIGNAL(propertyChanged(PropertyContainer::PropertyName)), Chris@45: this, Chris@45: SLOT(preferenceChanged(PropertyContainer::PropertyName))); Chris@45: Chris@591: SVDEBUG << "MainWindowBase: Creating labeller" << endl; Chris@591: Chris@45: Labeller::ValueType labellerType = Labeller::ValueFromTwoLevelCounter; Chris@45: settings.beginGroup("MainWindow"); Chris@230: Chris@45: labellerType = (Labeller::ValueType) Chris@45: settings.value("labellertype", (int)labellerType).toInt(); Chris@45: int cycle = settings.value("labellercycle", 4).toInt(); Chris@230: Chris@45: settings.endGroup(); Chris@45: Chris@45: m_labeller = new Labeller(labellerType); Chris@45: m_labeller->setCounterCycleSize(cycle); Chris@113: Chris@475: if (m_soundOptions & WithMIDIInput) { Chris@591: SVDEBUG << "MainWindowBase: Creating MIDI input" << endl; Chris@161: m_midiInput = new MIDIInput(QApplication::applicationName(), this); Chris@161: } Chris@452: Chris@452: QTimer::singleShot(1500, this, SIGNAL(hideSplash())); Chris@591: Chris@591: SVDEBUG << "MainWindowBase: Constructor done" << endl; Chris@45: } Chris@45: Chris@45: MainWindowBase::~MainWindowBase() Chris@45: { Chris@233: SVDEBUG << "MainWindowBase::~MainWindowBase" << endl; Chris@540: Chris@540: // We have to delete the breakfastquay::SystemPlaybackTarget or Chris@540: // breakfastquay::SystemAudioIO object (whichever we have -- it Chris@540: // depends on whether we handle recording or not) before we delete Chris@540: // the ApplicationPlaybackSource and ApplicationRecordTarget that Chris@540: // they refer to. Chris@556: Chris@556: deleteAudioIO(); Chris@540: Chris@540: // Then delete the Application objects. Chris@45: delete m_playSource; Chris@475: delete m_recordTarget; Chris@540: Chris@45: delete m_viewManager; cannam@632: delete m_midiInput; cannam@632: Chris@654: if (m_oscScript) { Chris@654: disconnect(m_oscScript, nullptr, nullptr, nullptr); Chris@654: m_oscScript->abandon(); Chris@654: m_oscScript->wait(1000); Chris@654: if (m_oscScript->isRunning()) { Chris@654: m_oscScript->terminate(); Chris@654: m_oscScript->wait(1000); Chris@654: } Chris@654: delete m_oscScript; Chris@654: } Chris@654: Chris@639: if (m_oscQueueStarter) { Chris@644: disconnect(m_oscQueueStarter, nullptr, nullptr, nullptr); cannam@632: m_oscQueueStarter->wait(1000); Chris@639: if (m_oscQueueStarter->isRunning()) { Chris@639: m_oscQueueStarter->terminate(); Chris@639: m_oscQueueStarter->wait(1000); Chris@639: } Chris@639: delete m_oscQueueStarter; Chris@639: delete m_oscQueue; cannam@632: } cannam@632: Chris@45: Profiles::getInstance()->dump(); Chris@45: } Chris@45: Chris@113: void Chris@452: MainWindowBase::emitHideSplash() Chris@452: { Chris@591: SVDEBUG << "MainWindowBase: Hiding splash screen" << endl; Chris@452: emit hideSplash(this); Chris@452: } Chris@452: Chris@452: void Chris@354: MainWindowBase::finaliseMenus() Chris@354: { Chris@591: SVDEBUG << "MainWindowBase::finaliseMenus called" << endl; Chris@591: Chris@390: delete m_menuShortcutMapper; Chris@636: m_menuShortcutMapper = nullptr; Chris@390: Chris@391: foreach (QShortcut *sc, m_appShortcuts) { Chris@391: delete sc; Chris@391: } Chris@391: m_appShortcuts.clear(); Chris@391: Chris@354: QMenuBar *mb = menuBar(); Chris@394: Chris@396: // This used to find all children of QMenu type, and call Chris@396: // finaliseMenu on those. But it seems we are getting hold of some Chris@396: // menus that way that are not actually active in the menu bar and Chris@396: // are not returned in their parent menu's actions() list, and if Chris@396: // we finalise those, we end up with duplicate shortcuts in the Chris@396: // app shortcut mapper. So we should do this by descending the Chris@396: // menu tree through only those menus accessible via actions() Chris@396: // from their parents instead. Chris@396: Chris@394: QList menus = mb->findChildren Chris@394: (QString(), Qt::FindDirectChildrenOnly); Chris@394: Chris@354: foreach (QMenu *menu, menus) { Chris@354: if (menu) finaliseMenu(menu); Chris@354: } Chris@591: Chris@591: SVDEBUG << "MainWindowBase::finaliseMenus done" << endl; Chris@354: } Chris@354: Chris@354: void Chris@426: MainWindowBase::finaliseMenu(QMenu *menu) Chris@354: { Chris@426: foreach (QAction *a, menu->actions()) { Chris@426: a->setIconVisibleInMenu(m_iconsVisibleInMenus); Chris@426: } Chris@426: Chris@354: #ifdef Q_OS_MAC Chris@354: // See https://bugreports.qt-project.org/browse/QTBUG-38256 and Chris@354: // our issue #890 http://code.soundsoftware.ac.uk/issues/890 -- Chris@354: // single-key shortcuts that are associated only with a menu Chris@384: // action (and not with a toolbar button) do not work with Qt 5.x Chris@384: // under OS/X. Chris@354: // Chris@354: // Apparently Cocoa never handled them as a matter of course, but Chris@354: // earlier versions of Qt picked them up as widget shortcuts and Chris@354: // handled them anyway. That behaviour was removed to fix a crash Chris@354: // when invoking a menu while its window was overridden by a modal Chris@354: // dialog (https://bugreports.qt-project.org/browse/QTBUG-30657). Chris@354: // Chris@354: // This workaround restores the single-key shortcut behaviour by Chris@384: // searching in menus for single-key shortcuts that are associated Chris@384: // only with the menu and not with a toolbar button, and Chris@384: // augmenting them with global application shortcuts that invoke Chris@384: // the relevant actions, testing whether the actions are enabled Chris@384: // on invocation. Chris@354: // Chris@384: // (Previously this acted on all single-key shortcuts in menus, Chris@384: // and it removed the shortcut from the action when it created Chris@384: // each new global one, in order to avoid an "ambiguous shortcut" Chris@384: // error in the case where the action was also associated with a Chris@384: // toolbar button. But that has the unwelcome side-effect of Chris@384: // removing the shortcut hint from the menu entry. So now we leave Chris@384: // the shortcut in the menu action as well as creating a global Chris@384: // one, and we only act on shortcuts that have no toolbar button, Chris@384: // i.e. that will not otherwise work. The downside is that if this Chris@384: // bug is fixed in a future Qt release, we will start getting Chris@384: // "ambiguous shortcut" errors from the menu entry actions and Chris@384: // will need to update the code.) Chris@354: Chris@443: // Update: The bug was fixed in Qt 5.4 for shortcuts with no Chris@443: // modifier, and I believe it is fixed in Qt 5.5 for shortcuts Chris@443: // with Shift modifiers. The below reflects that Chris@443: Chris@443: #if (QT_VERSION < QT_VERSION_CHECK(5, 5, 0)) Chris@443: Chris@390: if (!m_menuShortcutMapper) { Chris@390: m_menuShortcutMapper = new QSignalMapper(this); Chris@392: connect(m_menuShortcutMapper, SIGNAL(mapped(QObject *)), Chris@392: this, SLOT(menuActionMapperInvoked(QObject *))); Chris@390: } Chris@390: Chris@354: foreach (QAction *a, menu->actions()) { Chris@394: Chris@394: if (a->isSeparator()) { Chris@394: continue; Chris@394: } else if (a->menu()) { Chris@394: finaliseMenu(a->menu()); Chris@394: } else { Chris@394: Chris@394: QWidgetList ww = a->associatedWidgets(); Chris@394: bool hasButton = false; Chris@394: foreach (QWidget *w, ww) { Chris@394: if (qobject_cast(w)) { Chris@394: hasButton = true; Chris@394: break; Chris@394: } Chris@394: } Chris@394: if (hasButton) continue; Chris@394: QKeySequence sc = a->shortcut(); Chris@399: Chris@399: // Note that the set of "single-key shortcuts" that aren't Chris@399: // working and that we need to handle here includes those Chris@399: // with the Shift modifier mask as well as those with no Chris@399: // modifier at all Chris@443: #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) Chris@443: // Nothing needed Chris@443: if (false) { Chris@443: #elif (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) Chris@443: if (sc.count() == 1 && Chris@443: (sc[0] & Qt::KeyboardModifierMask) == Qt::ShiftModifier) { Chris@443: #else Chris@399: if (sc.count() == 1 && Chris@399: ((sc[0] & Qt::KeyboardModifierMask) == Qt::NoModifier || Chris@399: (sc[0] & Qt::KeyboardModifierMask) == Qt::ShiftModifier)) { Chris@443: #endif Chris@394: QShortcut *newSc = new QShortcut(sc, a->parentWidget()); Chris@394: QObject::connect(newSc, SIGNAL(activated()), Chris@394: m_menuShortcutMapper, SLOT(map())); Chris@394: m_menuShortcutMapper->setMapping(newSc, a); Chris@394: m_appShortcuts.push_back(newSc); Chris@384: } Chris@384: } Chris@354: } Chris@354: #endif Chris@443: #endif Chris@354: } Chris@354: Chris@354: void Chris@354: MainWindowBase::menuActionMapperInvoked(QObject *o) Chris@354: { Chris@354: QAction *a = qobject_cast(o); Chris@354: if (a && a->isEnabled()) { Chris@354: a->trigger(); Chris@354: } Chris@354: } Chris@354: Chris@354: void Chris@168: MainWindowBase::resizeConstrained(QSize size) Chris@168: { Chris@684: QScreen *screen = QApplication::primaryScreen(); Chris@684: QRect available = screen->availableGeometry(); Chris@168: QSize actual(std::min(size.width(), available.width()), Chris@168: std::min(size.height(), available.height())); Chris@168: resize(actual); Chris@168: } Chris@168: Chris@168: void Chris@658: MainWindowBase::startOSCQueue(bool withNetworkPort) Chris@304: { Chris@658: m_oscQueueStarter = new OSCQueueStarter(this, withNetworkPort); Chris@304: connect(m_oscQueueStarter, SIGNAL(finished()), this, SLOT(oscReady())); Chris@304: m_oscQueueStarter->start(); Chris@304: } Chris@304: Chris@304: void Chris@113: MainWindowBase::oscReady() Chris@113: { Chris@113: if (m_oscQueue && m_oscQueue->isOK()) { Chris@113: connect(m_oscQueue, SIGNAL(messagesAvailable()), this, SLOT(pollOSC())); Chris@113: QTimer *oscTimer = new QTimer(this); Chris@113: connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC())); Chris@113: oscTimer->start(1000); Chris@658: Chris@658: if (m_oscQueue->hasPort()) { Chris@658: SVDEBUG << "Finished setting up OSC interface" << endl; Chris@658: } else { Chris@658: SVDEBUG << "Finished setting up internal-only OSC queue" << endl; Chris@658: } Chris@654: Chris@654: if (m_oscScriptFile != QString()) { Chris@654: startOSCScript(); Chris@654: } Chris@113: } Chris@113: } Chris@113: Chris@654: void Chris@654: MainWindowBase::startOSCScript() Chris@654: { Chris@654: m_oscScript = new OSCScript(m_oscScriptFile, m_oscQueue); Chris@654: connect(m_oscScript, SIGNAL(finished()), Chris@654: this, SLOT(oscScriptFinished())); Chris@654: m_oscScriptFile = QString(); Chris@654: m_oscScript->start(); Chris@654: } Chris@654: Chris@654: void Chris@654: MainWindowBase::cueOSCScript(QString fileName) Chris@654: { Chris@654: m_oscScriptFile = fileName; Chris@654: if (m_oscQueue && m_oscQueue->isOK()) { Chris@654: startOSCScript(); Chris@654: } Chris@654: } Chris@654: Chris@654: void Chris@654: MainWindowBase::oscScriptFinished() Chris@654: { Chris@654: delete m_oscScript; Chris@654: m_oscScript = 0; Chris@654: } Chris@654: Chris@45: QString Chris@45: MainWindowBase::getOpenFileName(FileFinder::FileType type) Chris@45: { Chris@45: FileFinder *ff = FileFinder::getInstance(); Chris@358: Chris@358: if (type == FileFinder::AnyFile) { Chris@684: if (!getMainModelId().isNone() && Chris@636: m_paneStack != nullptr && Chris@636: m_paneStack->getCurrentPane() != nullptr) { // can import a layer Chris@45: return ff->getOpenFileName(FileFinder::AnyFile, m_sessionFile); Chris@45: } else { Chris@45: return ff->getOpenFileName(FileFinder::SessionOrAudioFile, Chris@45: m_sessionFile); Chris@45: } Chris@358: } Chris@358: Chris@358: QString lastPath = m_sessionFile; Chris@358: Chris@358: if (type == FileFinder::AudioFile) { Chris@358: lastPath = m_audioFile; Chris@45: } Chris@358: Chris@358: return ff->getOpenFileName(type, lastPath); Chris@45: } Chris@45: Chris@45: QString Chris@45: MainWindowBase::getSaveFileName(FileFinder::FileType type) Chris@45: { Chris@358: QString lastPath = m_sessionFile; Chris@358: Chris@358: if (type == FileFinder::AudioFile) { Chris@358: lastPath = m_audioFile; Chris@358: } Chris@358: Chris@45: FileFinder *ff = FileFinder::getInstance(); Chris@358: return ff->getSaveFileName(type, lastPath); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::registerLastOpenedFilePath(FileFinder::FileType type, QString path) Chris@45: { Chris@45: FileFinder *ff = FileFinder::getInstance(); Chris@45: ff->registerLastOpenedFilePath(type, path); Chris@45: } Chris@45: Chris@222: QString Chris@222: MainWindowBase::getDefaultSessionTemplate() const Chris@222: { Chris@231: QSettings settings; Chris@231: settings.beginGroup("MainWindow"); Chris@231: QString templateName = settings.value("sessiontemplate", "").toString(); Chris@231: if (templateName == "") templateName = "default"; Chris@231: return templateName; Chris@222: } Chris@222: Chris@222: void Chris@251: MainWindowBase::setDefaultSessionTemplate(QString n) Chris@251: { Chris@251: QSettings settings; Chris@251: settings.beginGroup("MainWindow"); Chris@251: settings.setValue("sessiontemplate", n); Chris@251: } Chris@251: Chris@251: void Chris@45: MainWindowBase::updateMenuStates() Chris@45: { Chris@636: Pane *currentPane = nullptr; Chris@636: Layer *currentLayer = nullptr; Chris@45: Chris@45: if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); Chris@45: if (currentPane) currentLayer = currentPane->getSelectedLayer(); Chris@45: Chris@73: bool havePrevPane = false, haveNextPane = false; Chris@73: bool havePrevLayer = false, haveNextLayer = false; Chris@73: Chris@73: if (currentPane) { Chris@73: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@73: if (m_paneStack->getPane(i) == currentPane) { Chris@73: if (i > 0) havePrevPane = true; Chris@73: if (i < m_paneStack->getPaneCount()-1) haveNextPane = true; Chris@73: break; Chris@73: } Chris@73: } Chris@403: // the prev/next layer commands actually include the pane Chris@403: // itself as one of the selectables -- so we always have a Chris@403: // prev and next layer, as long as we have a pane with at Chris@403: // least one layer in it Chris@403: if (currentPane->getLayerCount() > 0) { Chris@403: havePrevLayer = true; Chris@403: haveNextLayer = true; Chris@73: } Chris@73: } Chris@73: Chris@45: bool haveCurrentPane = Chris@636: (currentPane != nullptr); Chris@45: bool haveCurrentLayer = Chris@45: (haveCurrentPane && Chris@636: (currentLayer != nullptr)); Chris@45: bool haveMainModel = Chris@684: (!getMainModelId().isNone()); Chris@45: bool havePlayTarget = Chris@636: (m_playTarget != nullptr || m_audioIO != nullptr); Chris@45: bool haveSelection = Chris@595: (m_viewManager && Chris@595: !m_viewManager->getSelections().empty()); Chris@45: bool haveCurrentEditableLayer = Chris@595: (haveCurrentLayer && Chris@595: currentLayer->isLayerEditable()); Chris@45: bool haveCurrentTimeInstantsLayer = Chris@595: (haveCurrentLayer && Chris@595: dynamic_cast(currentLayer)); Chris@184: bool haveCurrentDurationLayer = Chris@595: (haveCurrentLayer && Chris@595: (dynamic_cast(currentLayer) || Chris@595: dynamic_cast(currentLayer) || Chris@184: dynamic_cast(currentLayer))); Chris@45: bool haveCurrentColour3DPlot = Chris@45: (haveCurrentLayer && Chris@45: dynamic_cast(currentLayer)); Chris@45: bool haveClipboardContents = Chris@45: (m_viewManager && Chris@45: !m_viewManager->getClipboard().empty()); Chris@146: bool haveTabularLayer = Chris@146: (haveCurrentLayer && Chris@684: ModelById::isa(currentLayer->getModel())); Chris@45: Chris@45: emit canAddPane(haveMainModel); Chris@45: emit canDeleteCurrentPane(haveCurrentPane); Chris@45: emit canZoom(haveMainModel && haveCurrentPane); Chris@45: emit canScroll(haveMainModel && haveCurrentPane); Chris@45: emit canAddLayer(haveMainModel && haveCurrentPane); Chris@45: emit canImportMoreAudio(haveMainModel); Chris@259: emit canReplaceMainAudio(haveMainModel); Chris@45: emit canImportLayer(haveMainModel && haveCurrentPane); Chris@45: emit canExportAudio(haveMainModel); Chris@289: emit canChangeSessionTemplate(haveMainModel); Chris@45: emit canExportLayer(haveMainModel && Chris@45: (haveCurrentEditableLayer || haveCurrentColour3DPlot)); Chris@45: emit canExportImage(haveMainModel && haveCurrentPane); Chris@45: emit canDeleteCurrentLayer(haveCurrentLayer); Chris@45: emit canRenameLayer(haveCurrentLayer); Chris@45: emit canEditLayer(haveCurrentEditableLayer); Chris@146: emit canEditLayerTabular(haveCurrentEditableLayer || haveTabularLayer); Chris@45: emit canMeasureLayer(haveCurrentLayer); Chris@45: emit canSelect(haveMainModel && haveCurrentPane); Chris@188: emit canPlay(haveMainModel && havePlayTarget); Chris@453: emit canFfwd(haveMainModel); Chris@453: emit canRewind(haveMainModel); Chris@87: emit canPaste(haveClipboardContents); Chris@45: emit canInsertInstant(haveCurrentPane); Chris@45: emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection); Chris@184: emit canInsertItemAtSelection(haveCurrentPane && haveSelection && haveCurrentDurationLayer); Chris@45: emit canRenumberInstants(haveCurrentTimeInstantsLayer && haveSelection); Chris@537: emit canSubdivideInstants(haveCurrentTimeInstantsLayer && haveSelection); Chris@538: emit canWinnowInstants(haveCurrentTimeInstantsLayer && haveSelection); Chris@45: emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection); Chris@45: emit canClearSelection(haveSelection); Chris@45: emit canEditSelection(haveSelection && haveCurrentEditableLayer); Chris@45: emit canSave(m_sessionFile != "" && m_documentModified); Chris@601: emit canSaveAs(haveMainModel); // possibly used only in Tony, not SV Chris@73: emit canSelectPreviousPane(havePrevPane); Chris@73: emit canSelectNextPane(haveNextPane); Chris@73: emit canSelectPreviousLayer(havePrevLayer); Chris@73: emit canSelectNextLayer(haveNextLayer); Chris@586: Chris@586: // This is quite subtle -- whereas we can play back only if a Chris@586: // system play target or I/O exists, we can record even if no Chris@586: // record source (i.e. audioIO) exists because we can record into Chris@586: // an empty session before the audio device has been Chris@586: // opened. However, if there is no record *target* then recording Chris@586: // was actively disabled (flag not set in m_soundOptions). And if Chris@586: // we have a play target instead of an audioIO, then we must have Chris@586: // tried to open the device but failed to find any capture source. Chris@586: bool recordDisabled = (m_recordTarget == nullptr); Chris@586: bool recordDeviceFailed = (m_playTarget != nullptr && m_audioIO == nullptr); Chris@586: emit canRecord(!recordDisabled && !recordDeviceFailed); Chris@45: } Chris@45: Chris@45: void Chris@669: MainWindowBase::updateWindowTitle() Chris@669: { Chris@669: QString title; Chris@669: Chris@669: if (m_sessionFile != "") { Chris@669: if (m_originalLocation != "" && Chris@669: m_originalLocation != m_sessionFile) { // session + location Chris@669: title = tr("%1: %2 [%3]") Chris@669: .arg(QApplication::applicationName()) Chris@669: .arg(QFileInfo(m_sessionFile).fileName()) Chris@669: .arg(m_originalLocation); Chris@669: } else { // session only Chris@669: title = tr("%1: %2") Chris@669: .arg(QApplication::applicationName()) Chris@669: .arg(QFileInfo(m_sessionFile).fileName()); Chris@669: } Chris@669: } else { Chris@669: if (m_originalLocation != "") { // location only Chris@669: title = tr("%1: %2") Chris@669: .arg(QApplication::applicationName()) Chris@669: .arg(m_originalLocation); Chris@669: } else { // neither Chris@669: title = QApplication::applicationName(); Chris@669: } Chris@669: } Chris@669: Chris@669: if (m_documentModified) { Chris@669: title = tr("%1 (modified)").arg(title); Chris@669: } Chris@669: Chris@669: setWindowTitle(title); Chris@669: } Chris@669: Chris@669: void Chris@45: MainWindowBase::documentModified() Chris@45: { Chris@233: // SVDEBUG << "MainWindowBase::documentModified" << endl; Chris@45: Chris@45: m_documentModified = true; Chris@669: updateWindowTitle(); Chris@45: updateMenuStates(); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::documentRestored() Chris@45: { Chris@233: // SVDEBUG << "MainWindowBase::documentRestored" << endl; Chris@45: Chris@45: m_documentModified = false; Chris@669: updateWindowTitle(); Chris@45: updateMenuStates(); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::playLoopToggled() Chris@45: { Chris@45: QAction *action = dynamic_cast(sender()); Chris@45: Chris@45: if (action) { Chris@595: m_viewManager->setPlayLoopMode(action->isChecked()); Chris@45: } else { Chris@595: m_viewManager->setPlayLoopMode(!m_viewManager->getPlayLoopMode()); Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::playSelectionToggled() Chris@45: { Chris@45: QAction *action = dynamic_cast(sender()); Chris@45: Chris@45: if (action) { Chris@595: m_viewManager->setPlaySelectionMode(action->isChecked()); Chris@45: } else { Chris@595: m_viewManager->setPlaySelectionMode(!m_viewManager->getPlaySelectionMode()); Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::playSoloToggled() Chris@45: { Chris@45: QAction *action = dynamic_cast(sender()); Chris@45: Chris@45: if (action) { Chris@595: m_viewManager->setPlaySoloMode(action->isChecked()); Chris@45: } else { Chris@595: m_viewManager->setPlaySoloMode(!m_viewManager->getPlaySoloMode()); Chris@45: } Chris@45: Chris@45: if (m_viewManager->getPlaySoloMode()) { Chris@45: currentPaneChanged(m_paneStack->getCurrentPane()); Chris@45: } else { Chris@684: m_viewManager->setPlaybackModel({}); Chris@45: if (m_playSource) { Chris@45: m_playSource->clearSoloModelSet(); Chris@45: } Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::currentPaneChanged(Pane *p) Chris@45: { Chris@45: updateMenuStates(); Chris@45: updateVisibleRangeDisplay(p); Chris@45: Chris@45: if (!p) return; Chris@45: Chris@45: if (!(m_viewManager && Chris@45: m_playSource && Chris@45: m_viewManager->getPlaySoloMode())) { Chris@684: if (m_viewManager) { Chris@684: m_viewManager->setPlaybackModel(ModelId()); Chris@684: } Chris@45: return; Chris@45: } Chris@45: Chris@684: ModelId prevPlaybackModel = m_viewManager->getPlaybackModel(); Chris@60: Chris@93: // What we want here is not the currently playing frame (unless we Chris@93: // are about to clear out the audio playback buffers -- which may Chris@93: // or may not be possible, depending on the audio driver). What Chris@93: // we want is the frame that was last committed to the soundcard Chris@93: // buffers, as the audio driver will continue playing up to that Chris@93: // frame before switching to whichever one we decide we want to Chris@93: // switch to, regardless of our efforts. Chris@93: Chris@435: sv_frame_t frame = m_playSource->getCurrentBufferedFrame(); Chris@93: Chris@388: cerr << "currentPaneChanged: current frame (in ref model) = " << frame << endl; Chris@45: Chris@45: View::ModelSet soloModels = p->getModels(); Chris@45: Chris@57: View::ModelSet sources; Chris@684: for (ModelId modelId: sources) { Chris@190: // If a model in this pane is derived from something else, Chris@190: // then we want to play that model as well -- if the model Chris@190: // that's derived from it is not something that is itself Chris@190: // individually playable (e.g. a waveform) Chris@684: if (auto model = ModelById::get(modelId)) { Chris@684: if (!ModelById::isa(modelId) && Chris@684: !model->getSourceModel().isNone()) { Chris@684: sources.insert(model->getSourceModel()); Chris@684: } Chris@57: } Chris@57: } Chris@684: for (ModelId modelId: sources) { Chris@684: soloModels.insert(modelId); Chris@57: } Chris@57: Chris@60: //!!! Need an "atomic" way of telling the play source that the Chris@60: //playback model has changed, and changing it on ViewManager -- Chris@60: //the play source should be making the setPlaybackModel call to Chris@60: //ViewManager Chris@60: Chris@684: ModelId newPlaybackModel; Chris@684: Chris@684: for (ModelId modelId: soloModels) { Chris@684: if (ModelById::isa(modelId)) { Chris@684: m_viewManager->setPlaybackModel(modelId); Chris@684: newPlaybackModel = modelId; Chris@45: } Chris@45: } Chris@45: Chris@45: m_playSource->setSoloModelSet(soloModels); Chris@45: Chris@684: if (!prevPlaybackModel.isNone() && !newPlaybackModel.isNone() && Chris@684: prevPlaybackModel != newPlaybackModel) { Chris@684: if (m_playSource->isPlaying()) { Chris@684: m_playSource->play(frame); Chris@684: } Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::currentLayerChanged(Pane *p, Layer *) Chris@45: { Chris@45: updateMenuStates(); Chris@45: updateVisibleRangeDisplay(p); Chris@45: } Chris@45: Chris@597: sv_frame_t Chris@597: MainWindowBase::getModelsStartFrame() const Chris@597: { Chris@597: sv_frame_t startFrame = 0; Chris@597: if (!m_paneStack) return startFrame; Chris@597: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@597: sv_frame_t thisStart = m_paneStack->getPane(i)->getModelsStartFrame(); Chris@597: if (i == 0 || thisStart < startFrame) { Chris@597: startFrame = thisStart; Chris@597: } Chris@597: } Chris@597: return startFrame; Chris@597: } Chris@597: Chris@597: sv_frame_t Chris@597: MainWindowBase::getModelsEndFrame() const Chris@597: { Chris@597: sv_frame_t endFrame = 0; Chris@597: if (!m_paneStack) return endFrame; Chris@597: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@597: sv_frame_t thisEnd = m_paneStack->getPane(i)->getModelsEndFrame(); Chris@597: if (i == 0 || thisEnd > endFrame) { Chris@597: endFrame = thisEnd; Chris@597: } Chris@597: } Chris@597: return endFrame; Chris@597: } Chris@597: Chris@45: void Chris@45: MainWindowBase::selectAll() Chris@45: { Chris@597: m_viewManager->setSelection(Selection(getModelsStartFrame(), Chris@597: getModelsEndFrame())); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::selectToStart() Chris@45: { Chris@684: m_viewManager->setSelection(Selection(getModelsStartFrame(), Chris@595: m_viewManager->getGlobalCentreFrame())); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::selectToEnd() Chris@45: { Chris@45: m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(), Chris@684: getModelsEndFrame())); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::selectVisible() Chris@45: { Chris@684: auto model = getMainModel(); Chris@45: if (!model) return; Chris@45: Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (!currentPane) return; Chris@45: Chris@435: sv_frame_t startFrame, endFrame; Chris@45: Chris@684: if (currentPane->getStartFrame() < 0) { Chris@684: startFrame = 0; Chris@684: } else { Chris@684: startFrame = currentPane->getStartFrame(); Chris@684: } Chris@684: Chris@684: if (currentPane->getEndFrame() > model->getEndFrame()) { Chris@684: endFrame = model->getEndFrame(); Chris@684: } else { Chris@684: endFrame = currentPane->getEndFrame(); Chris@684: } Chris@45: Chris@45: m_viewManager->setSelection(Selection(startFrame, endFrame)); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::clearSelection() Chris@45: { Chris@45: m_viewManager->clearSelections(); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::cut() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (!currentPane) return; Chris@45: Chris@45: Layer *layer = currentPane->getSelectedLayer(); Chris@45: if (!layer) return; Chris@45: Chris@45: Clipboard &clipboard = m_viewManager->getClipboard(); Chris@45: clipboard.clear(); Chris@45: Chris@45: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@45: Chris@45: CommandHistory::getInstance()->startCompoundOperation(tr("Cut"), true); Chris@45: Chris@45: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@45: i != selections.end(); ++i) { Chris@86: layer->copy(currentPane, *i, clipboard); Chris@45: layer->deleteSelection(*i); Chris@45: } Chris@45: Chris@45: CommandHistory::getInstance()->endCompoundOperation(); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::copy() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (!currentPane) return; Chris@45: Chris@45: Layer *layer = currentPane->getSelectedLayer(); Chris@45: if (!layer) return; Chris@45: Chris@45: Clipboard &clipboard = m_viewManager->getClipboard(); Chris@45: clipboard.clear(); Chris@45: Chris@45: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@45: Chris@45: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@45: i != selections.end(); ++i) { Chris@86: layer->copy(currentPane, *i, clipboard); Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::paste() Chris@45: { Chris@215: pasteRelative(0); Chris@215: } Chris@215: Chris@215: void Chris@215: MainWindowBase::pasteAtPlaybackPosition() Chris@215: { Chris@435: sv_frame_t pos = getFrame(); Chris@215: Clipboard &clipboard = m_viewManager->getClipboard(); Chris@215: if (!clipboard.empty()) { Chris@435: sv_frame_t firstEventFrame = clipboard.getPoints()[0].getFrame(); Chris@435: sv_frame_t offset = 0; Chris@215: if (firstEventFrame < 0) { Chris@366: offset = pos - firstEventFrame; Chris@354: } else if (firstEventFrame < pos) { Chris@366: offset = pos - firstEventFrame; Chris@215: } else { Chris@366: offset = -(firstEventFrame - pos); Chris@215: } Chris@215: pasteRelative(offset); Chris@215: } Chris@215: } Chris@215: Chris@215: void Chris@435: MainWindowBase::pasteRelative(sv_frame_t offset) Chris@215: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (!currentPane) return; Chris@45: Chris@45: Layer *layer = currentPane->getSelectedLayer(); Chris@45: Chris@45: Clipboard &clipboard = m_viewManager->getClipboard(); Chris@87: Chris@98: bool inCompound = false; Chris@87: Chris@87: if (!layer || !layer->isLayerEditable()) { Chris@87: Chris@87: CommandHistory::getInstance()->startCompoundOperation Chris@87: (tr("Paste"), true); Chris@87: Chris@87: // no suitable current layer: create one of the most Chris@87: // appropriate sort Chris@87: LayerFactory::LayerType type = Chris@87: LayerFactory::getInstance()->getLayerTypeForClipboardContents(clipboard); Chris@87: layer = m_document->createEmptyLayer(type); Chris@87: Chris@87: if (!layer) { Chris@87: CommandHistory::getInstance()->endCompoundOperation(); Chris@87: return; Chris@45: } Chris@87: Chris@87: m_document->addLayerToView(currentPane, layer); Chris@87: m_paneStack->setCurrentLayer(currentPane, layer); Chris@87: Chris@87: inCompound = true; Chris@45: } Chris@45: Chris@215: layer->paste(currentPane, clipboard, offset, true); Chris@45: Chris@87: if (inCompound) CommandHistory::getInstance()->endCompoundOperation(); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::deleteSelected() Chris@45: { Chris@45: if (m_paneStack->getCurrentPane() && Chris@595: m_paneStack->getCurrentPane()->getSelectedLayer()) { Chris@45: Chris@45: Layer *layer = m_paneStack->getCurrentPane()->getSelectedLayer(); Chris@45: Chris@409: if (m_viewManager) { Chris@409: Chris@409: if (m_viewManager->getToolMode() == ViewManager::MeasureMode) { Chris@409: Chris@409: layer->deleteCurrentMeasureRect(); Chris@45: Chris@409: } else { Chris@409: Chris@409: MultiSelection::SelectionList selections = Chris@409: m_viewManager->getSelections(); Chris@409: Chris@409: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@409: i != selections.end(); ++i) { Chris@409: layer->deleteSelection(*i); Chris@409: } Chris@45: } Chris@595: } Chris@45: } Chris@45: } Chris@45: Chris@161: // FrameTimer method Chris@161: Chris@435: sv_frame_t Chris@161: MainWindowBase::getFrame() const Chris@161: { Chris@161: if (m_playSource && m_playSource->isPlaying()) { Chris@161: return m_playSource->getCurrentPlayingFrame(); Chris@161: } else { Chris@161: return m_viewManager->getPlaybackFrame(); Chris@161: } Chris@161: } Chris@161: Chris@45: void Chris@45: MainWindowBase::insertInstant() Chris@45: { Chris@161: insertInstantAt(getFrame()); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::insertInstantsAtBoundaries() Chris@45: { Chris@45: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@45: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@45: i != selections.end(); ++i) { Chris@435: sv_frame_t start = i->getStartFrame(); Chris@435: sv_frame_t end = i->getEndFrame(); Chris@45: if (start != end) { Chris@184: insertInstantAt(start); Chris@184: insertInstantAt(end); Chris@45: } Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@435: MainWindowBase::insertInstantAt(sv_frame_t frame) Chris@45: { Chris@45: Pane *pane = m_paneStack->getCurrentPane(); Chris@45: if (!pane) { Chris@45: return; Chris@45: } Chris@45: Chris@74: frame = pane->alignFromReference(frame); Chris@74: Chris@45: Layer *layer = dynamic_cast Chris@45: (pane->getSelectedLayer()); Chris@45: Chris@45: if (!layer) { Chris@45: for (int i = pane->getLayerCount(); i > 0; --i) { Chris@45: layer = dynamic_cast(pane->getLayer(i - 1)); Chris@45: if (layer) break; Chris@45: } Chris@45: Chris@45: if (!layer) { Chris@45: CommandHistory::getInstance()->startCompoundOperation Chris@45: (tr("Add Point"), true); Chris@45: layer = m_document->createEmptyLayer(LayerFactory::TimeInstants); Chris@45: if (layer) { Chris@45: m_document->addLayerToView(pane, layer); Chris@45: m_paneStack->setCurrentLayer(pane, layer); Chris@45: } Chris@45: CommandHistory::getInstance()->endCompoundOperation(); Chris@45: } Chris@45: } Chris@45: Chris@45: if (layer) { Chris@45: Chris@684: ModelId model = layer->getModel(); Chris@684: auto sodm = ModelById::getAs(model); Chris@45: Chris@45: if (sodm) { Chris@651: Event point(frame, ""); Chris@651: Event prevPoint(0); Chris@45: bool havePrevPoint = false; Chris@45: Chris@651: ChangeEventsCommand *command = Chris@684: new ChangeEventsCommand(model.untyped, tr("Add Point")); Chris@45: Chris@409: if (m_labeller) { Chris@409: Chris@409: if (m_labeller->requiresPrevPoint()) { Chris@651: Chris@651: if (sodm->getNearestEventMatching Chris@651: (frame, Chris@651: [](Event) { return true; }, Chris@651: EventSeries::Backward, Chris@651: prevPoint)) { Chris@409: havePrevPoint = true; Chris@409: } Chris@45: } Chris@45: Chris@45: m_labeller->setSampleRate(sodm->getSampleRate()); Chris@45: Chris@651: Labeller::Relabelling relabelling = m_labeller->label Chris@636: (point, havePrevPoint ? &prevPoint : nullptr); Chris@651: Chris@651: if (relabelling.first == Labeller::AppliesToPreviousEvent) { Chris@651: command->remove(prevPoint); Chris@651: command->add(relabelling.second); Chris@651: } else { Chris@651: point = relabelling.second; Chris@45: } Chris@45: } Chris@45: Chris@651: command->add(point); Chris@45: Chris@45: command->setName(tr("Add Point at %1 s") Chris@45: .arg(RealTime::frame2RealTime Chris@45: (frame, Chris@45: sodm->getSampleRate()) Chris@45: .toText(false).c_str())); Chris@45: Chris@108: Command *c = command->finish(); Chris@684: if (c) { Chris@684: CommandHistory::getInstance()->addCommand(c, false); Chris@684: } Chris@45: } Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@184: MainWindowBase::insertItemAtSelection() Chris@184: { Chris@184: MultiSelection::SelectionList selections = m_viewManager->getSelections(); Chris@184: for (MultiSelection::SelectionList::iterator i = selections.begin(); Chris@184: i != selections.end(); ++i) { Chris@435: sv_frame_t start = i->getStartFrame(); Chris@435: sv_frame_t end = i->getEndFrame(); Chris@184: if (start < end) { Chris@184: insertItemAt(start, end - start); Chris@184: } Chris@184: } Chris@184: } Chris@184: Chris@184: void Chris@435: MainWindowBase::insertItemAt(sv_frame_t frame, sv_frame_t duration) Chris@184: { Chris@184: Pane *pane = m_paneStack->getCurrentPane(); Chris@184: if (!pane) { Chris@184: return; Chris@184: } Chris@184: Chris@184: // ugh! Chris@184: Chris@435: sv_frame_t alignedStart = pane->alignFromReference(frame); Chris@435: sv_frame_t alignedEnd = pane->alignFromReference(frame + duration); Chris@184: if (alignedStart >= alignedEnd) return; Chris@435: sv_frame_t alignedDuration = alignedEnd - alignedStart; Chris@184: Chris@636: Command *c = nullptr; Chris@184: Chris@184: QString name = tr("Add Item at %1 s") Chris@184: .arg(RealTime::frame2RealTime Chris@184: (alignedStart, Chris@184: getMainModel()->getSampleRate()) Chris@184: .toText(false).c_str()); Chris@184: Chris@184: Layer *layer = pane->getSelectedLayer(); Chris@184: if (!layer) return; Chris@184: Chris@687: ModelId modelId = layer->getModel(); Chris@687: Chris@687: auto rm = ModelById::getAs(modelId); Chris@184: if (rm) { Chris@648: Event point(alignedStart, Chris@648: rm->getValueMaximum() + 1, Chris@648: alignedDuration, Chris@648: ""); Chris@684: ChangeEventsCommand *command = new ChangeEventsCommand Chris@687: (modelId.untyped, name); Chris@648: command->add(point); Chris@184: c = command->finish(); Chris@184: } Chris@184: Chris@184: if (c) { Chris@184: CommandHistory::getInstance()->addCommand(c, false); Chris@184: return; Chris@184: } Chris@184: Chris@687: auto nm = ModelById::getAs(modelId); Chris@184: if (nm) { Chris@648: Event point(alignedStart, Chris@648: nm->getValueMinimum(), Chris@648: alignedDuration, Chris@648: 1.f, Chris@648: ""); Chris@684: ChangeEventsCommand *command = new ChangeEventsCommand Chris@687: (modelId.untyped, name); Chris@646: command->add(point); Chris@184: c = command->finish(); Chris@184: } Chris@184: Chris@184: if (c) { Chris@184: CommandHistory::getInstance()->addCommand(c, false); Chris@184: return; Chris@184: } Chris@184: } Chris@184: Chris@184: void Chris@45: MainWindowBase::renumberInstants() Chris@45: { Chris@45: Pane *pane = m_paneStack->getCurrentPane(); Chris@45: if (!pane) return; Chris@45: Chris@45: Layer *layer = dynamic_cast(pane->getSelectedLayer()); Chris@45: if (!layer) return; Chris@45: Chris@45: MultiSelection ms(m_viewManager->getSelection()); Chris@45: Chris@684: auto sodm = ModelById::getAs(layer->getModel()); Chris@45: if (!sodm) return; Chris@45: Chris@45: if (!m_labeller) return; Chris@45: Chris@45: Labeller labeller(*m_labeller); Chris@45: labeller.setSampleRate(sodm->getSampleRate()); Chris@684: Chris@684: /*!!! Chris@684: Command *c = labeller.labelAll(sodm->getId().untyped, &ms); Chris@537: if (c) CommandHistory::getInstance()->addCommand(c, false); Chris@649: */ Chris@537: } Chris@537: Chris@537: void Chris@537: MainWindowBase::subdivideInstantsBy(int n) Chris@537: { Chris@537: Pane *pane = m_paneStack->getCurrentPane(); Chris@537: if (!pane) return; Chris@537: Chris@537: Layer *layer = dynamic_cast(pane->getSelectedLayer()); Chris@537: if (!layer) return; Chris@537: Chris@537: MultiSelection ms(m_viewManager->getSelection()); Chris@537: Chris@684: auto sodm = ModelById::getAs(layer->getModel()); Chris@537: if (!sodm) return; Chris@537: Chris@537: if (!m_labeller) return; Chris@537: Chris@537: Labeller labeller(*m_labeller); Chris@537: labeller.setSampleRate(sodm->getSampleRate()); Chris@537: Chris@649: (void)n; Chris@684: /*!!! Chris@684: Command *c = labeller.subdivide(sodm->getId().untyped, &ms, n); Chris@537: if (c) CommandHistory::getInstance()->addCommand(c, false); Chris@684: */ Chris@45: } Chris@45: Chris@538: void Chris@538: MainWindowBase::winnowInstantsBy(int n) Chris@538: { Chris@538: Pane *pane = m_paneStack->getCurrentPane(); Chris@538: if (!pane) return; Chris@538: Chris@538: Layer *layer = dynamic_cast(pane->getSelectedLayer()); Chris@538: if (!layer) return; Chris@538: Chris@538: MultiSelection ms(m_viewManager->getSelection()); Chris@538: Chris@684: auto sodm = ModelById::getAs(layer->getModel()); Chris@538: if (!sodm) return; Chris@538: Chris@538: if (!m_labeller) return; Chris@538: Chris@538: Labeller labeller(*m_labeller); Chris@538: labeller.setSampleRate(sodm->getSampleRate()); Chris@538: Chris@649: (void)n; Chris@684: //!!! to update: (and the above two functions) Chris@684: /* Chris@684: Command *c = labeller.winnow(sodm->getId.untyped, &ms, n); Chris@538: if (c) CommandHistory::getInstance()->addCommand(c, false); Chris@684: */ Chris@538: } Chris@538: Chris@45: MainWindowBase::FileOpenStatus Chris@373: MainWindowBase::openPath(QString fileOrUrl, AudioFileOpenMode mode) Chris@45: { Chris@134: ProgressDialog dialog(tr("Opening file or URL..."), true, 2000, this); Chris@134: connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash())); Chris@109: return open(FileSource(fileOrUrl, &dialog), mode); Chris@45: } Chris@45: Chris@45: MainWindowBase::FileOpenStatus Chris@45: MainWindowBase::open(FileSource source, AudioFileOpenMode mode) Chris@45: { Chris@45: FileOpenStatus status; Chris@45: Chris@45: if (!source.isAvailable()) return FileOpenFailed; Chris@45: source.waitForData(); Chris@45: Chris@636: bool canImportLayer = (getMainModel() != nullptr && Chris@636: m_paneStack != nullptr && Chris@636: m_paneStack->getCurrentPane() != nullptr); Chris@45: Chris@152: bool rdf = (source.getExtension().toLower() == "rdf" || Chris@152: source.getExtension().toLower() == "n3" || Chris@152: source.getExtension().toLower() == "ttl"); Chris@152: Chris@152: bool audio = AudioFileReaderFactory::getKnownExtensions().contains Chris@152: (source.getExtension().toLower()); Chris@145: Chris@145: bool rdfSession = false; Chris@145: if (rdf) { Chris@145: RDFImporter::RDFDocumentType rdfType = Chris@145: RDFImporter::identifyDocumentType Chris@145: (QUrl::fromLocalFile(source.getLocalFilename()).toString()); Chris@145: if (rdfType == RDFImporter::AudioRefAndAnnotations || Chris@145: rdfType == RDFImporter::AudioRef) { Chris@145: rdfSession = true; Chris@145: } else if (rdfType == RDFImporter::NotRDF) { Chris@145: rdf = false; Chris@145: } Chris@145: } Chris@145: Chris@579: try { Chris@579: if (rdf) { Chris@579: if (rdfSession) { Chris@579: bool cancel = false; Chris@579: if (!canImportLayer || shouldCreateNewSessionForRDFAudio(&cancel)) { Chris@579: return openSession(source); Chris@579: } else if (cancel) { Chris@579: return FileOpenCancelled; Chris@579: } else { Chris@579: return openLayer(source); Chris@579: } Chris@145: } else { Chris@579: if ((status = openSession(source)) != FileOpenFailed) { Chris@579: return status; Chris@579: } else if (!canImportLayer) { Chris@579: return FileOpenWrongMode; Chris@579: } else if ((status = openLayer(source)) != FileOpenFailed) { Chris@579: return status; Chris@579: } else { Chris@579: return FileOpenFailed; Chris@579: } Chris@145: } Chris@145: } Chris@579: Chris@579: if (audio && (status = openAudio(source, mode)) != FileOpenFailed) { Chris@579: return status; Chris@579: } else if ((status = openSession(source)) != FileOpenFailed) { Chris@579: return status; Chris@579: } else if ((status = openPlaylist(source, mode)) != FileOpenFailed) { Chris@579: return status; Chris@579: } else if (!canImportLayer) { Chris@579: return FileOpenWrongMode; Chris@579: } else if ((status = openImage(source)) != FileOpenFailed) { Chris@579: return status; Chris@579: } else if ((status = openLayer(source)) != FileOpenFailed) { Chris@579: return status; Chris@579: } else { Chris@579: return FileOpenFailed; Chris@579: } Chris@579: } catch (const InsufficientDiscSpace &e) { Chris@579: emit hideSplash(); Chris@579: m_openingAudioFile = false; Chris@594: SVCERR << "MainWindowBase: Caught InsufficientDiscSpace in file open" << endl; Chris@579: QMessageBox::critical Chris@579: (this, tr("Not enough disc space"), Chris@579: tr("Not enough disc space

There doesn't appear to be enough spare disc space to accommodate any necessary temporary files.

Please clear some space and try again.

").arg(e.what())); Chris@579: return FileOpenFailed; Chris@592: } catch (const std::bad_alloc &e) { // reader may have rethrown this after cleaning up Chris@592: emit hideSplash(); Chris@592: m_openingAudioFile = false; Chris@594: SVCERR << "MainWindowBase: Caught bad_alloc in file open" << endl; Chris@592: QMessageBox::critical Chris@592: (this, tr("Not enough memory"), Chris@592: tr("Not enough memory

There doesn't appear to be enough memory to accommodate any necessary temporary data.

")); Chris@592: return FileOpenFailed; Chris@45: } Chris@45: } Chris@45: Chris@45: MainWindowBase::FileOpenStatus Chris@626: MainWindowBase::openAudio(FileSource source, Chris@626: AudioFileOpenMode mode, Chris@227: QString templateName) Chris@45: { Chris@386: SVDEBUG << "MainWindowBase::openAudio(" << source.getLocation() << ") with mode " << mode << " and template " << templateName << endl; Chris@45: Chris@222: if (templateName == "") { Chris@231: templateName = getDefaultSessionTemplate(); Chris@577: SVDEBUG << "(Default template is: \"" << templateName << "\")" << endl; Chris@222: } Chris@220: Chris@374: // cerr << "template is: \"" << templateName << "\"" << endl; Chris@223: Chris@413: if (!source.isAvailable()) { Chris@413: if (source.wasCancelled()) { Chris@413: return FileOpenCancelled; Chris@413: } else { Chris@413: return FileOpenFailed; Chris@413: } Chris@413: } Chris@413: Chris@45: source.waitForData(); Chris@45: Chris@45: m_openingAudioFile = true; Chris@45: Chris@435: sv_samplerate_t rate = 0; Chris@45: Chris@626: SVDEBUG << "Checking whether to preserve incoming audio file's sample rate" Chris@626: << endl; Chris@626: Chris@360: if (Preferences::getInstance()->getFixedSampleRate() != 0) { Chris@360: rate = Preferences::getInstance()->getFixedSampleRate(); Chris@626: SVDEBUG << "No: preferences specify fixed rate of " << rate << endl; Chris@360: } else if (Preferences::getInstance()->getResampleOnLoad()) { Chris@552: if (getMainModel()) { Chris@626: if (mode == ReplaceSession || mode == ReplaceMainModel) { Chris@626: 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: } else { Chris@626: rate = getMainModel()->getSampleRate(); Chris@626: SVDEBUG << "No: preferences specify resampling to match main model, whose rate is currently " << rate << endl; Chris@626: } Chris@552: } Chris@45: } Chris@45: Chris@626: if (rate == 0) { Chris@626: SVDEBUG << "Yes, preserving incoming file rate" << endl; Chris@626: } Chris@626: Chris@684: auto newModel = std::make_shared(source, rate); Chris@45: if (!newModel->isOK()) { Chris@45: m_openingAudioFile = false; Chris@413: if (source.wasCancelled()) { Chris@413: return FileOpenCancelled; Chris@413: } else { Chris@413: return FileOpenFailed; Chris@413: } Chris@45: } Chris@45: Chris@687: auto newModelId = ModelById::add(newModel); Chris@687: return addOpenedAudioModel(source, newModelId, mode, templateName, true); Chris@604: } Chris@604: Chris@604: MainWindowBase::FileOpenStatus Chris@604: MainWindowBase::addOpenedAudioModel(FileSource source, Chris@684: ModelId newModel, Chris@604: AudioFileOpenMode mode, Chris@604: QString templateName, Chris@604: bool registerSource) Chris@604: { Chris@45: if (mode == AskUser) { Chris@45: if (getMainModel()) { Chris@45: Chris@147: QSettings settings; Chris@147: settings.beginGroup("MainWindow"); Chris@221: int lastMode = settings.value("lastaudioopenmode", 0).toBool(); Chris@147: settings.endGroup(); Chris@221: int imode = 0; Chris@45: Chris@45: QStringList items; Chris@221: items << tr("Close the current session and start a new one") Chris@221: << tr("Replace the main audio file in this session") Chris@221: << tr("Add the audio file to this session"); Chris@45: Chris@45: bool ok = false; Chris@45: QString item = ListInputDialog::getItem Chris@45: (this, tr("Select target for import"), Chris@221: tr("Select a target for import

You already have an audio file loaded.
What would you like to do with the new audio file?"), Chris@221: items, lastMode, &ok); Chris@45: Chris@45: if (!ok || item.isEmpty()) { Chris@684: ModelById::release(newModel); Chris@45: m_openingAudioFile = false; Chris@45: return FileOpenCancelled; Chris@45: } Chris@45: Chris@221: for (int i = 0; i < items.size(); ++i) { Chris@221: if (item == items[i]) imode = i; Chris@221: } Chris@221: Chris@147: settings.beginGroup("MainWindow"); Chris@221: settings.setValue("lastaudioopenmode", imode); Chris@147: settings.endGroup(); Chris@45: Chris@221: mode = (AudioFileOpenMode)imode; Chris@45: Chris@45: } else { Chris@221: // no main model: make a new session Chris@221: mode = ReplaceSession; Chris@45: } Chris@45: } Chris@45: Chris@45: if (mode == ReplaceCurrentPane) { Chris@45: Chris@45: Pane *pane = m_paneStack->getCurrentPane(); Chris@45: if (pane) { Chris@45: if (getMainModel()) { Chris@45: View::ModelSet models(pane->getModels()); Chris@684: if (models.find(getMainModelId()) != models.end()) { Chris@221: // Current pane contains main model: replace that Chris@45: mode = ReplaceMainModel; Chris@45: } Chris@221: // Otherwise the current pane has a non-default model, Chris@221: // which we will deal with later Chris@45: } else { Chris@221: // We have no main model, so start a new session with Chris@221: // optional template Chris@221: mode = ReplaceSession; Chris@45: } Chris@45: } else { Chris@221: // We seem to have no current pane! Oh well Chris@45: mode = CreateAdditionalModel; Chris@45: } Chris@45: } Chris@45: Chris@684: if (mode == CreateAdditionalModel && getMainModelId().isNone()) { Chris@386: SVDEBUG << "Mode is CreateAdditionalModel but we have no main model, switching to ReplaceSession mode" << endl; Chris@221: mode = ReplaceSession; Chris@221: } Chris@221: Chris@221: bool loadedTemplate = false; Chris@221: Chris@221: if (mode == ReplaceSession) { Chris@258: Chris@258: if (!checkSaveModified()) return FileOpenCancelled; Chris@258: Chris@386: SVDEBUG << "SV looking for template " << templateName << endl; Chris@230: if (templateName != "") { Chris@230: FileOpenStatus tplStatus = openSessionTemplate(templateName); Chris@258: if (tplStatus == FileOpenCancelled) { Chris@577: SVDEBUG << "Template load cancelled" << endl; Chris@258: return FileOpenCancelled; Chris@258: } Chris@230: if (tplStatus != FileOpenFailed) { Chris@577: SVDEBUG << "Template load succeeded" << endl; Chris@230: loadedTemplate = true; Chris@221: } Chris@221: } Chris@221: Chris@221: if (!loadedTemplate) { Chris@386: SVDEBUG << "No template found: closing session, creating new empty document" << endl; Chris@221: closeSession(); Chris@221: createDocument(); Chris@221: } Chris@221: Chris@386: SVDEBUG << "Now switching to ReplaceMainModel mode" << endl; Chris@45: mode = ReplaceMainModel; Chris@45: } Chris@45: Chris@164: emit activity(tr("Import audio file \"%1\"").arg(source.getLocation())); Chris@669: Chris@45: if (mode == ReplaceMainModel) { Chris@45: Chris@684: ModelId prevMain = getMainModelId(); Chris@684: if (!prevMain.isNone()) { Chris@45: m_playSource->removeModel(prevMain); Chris@45: } Chris@45: Chris@248: SVDEBUG << "SV about to call setMainModel(" << newModel << "): prevMain is " << prevMain << endl; Chris@248: Chris@595: m_document->setMainModel(newModel); Chris@595: Chris@595: setupMenus(); Chris@595: Chris@669: m_originalLocation = source.getLocation(); Chris@669: Chris@595: if (loadedTemplate || (m_sessionFile == "")) { Chris@595: CommandHistory::getInstance()->clear(); Chris@595: CommandHistory::getInstance()->documentSaved(); Chris@595: m_documentModified = false; Chris@595: } else { Chris@595: if (m_documentModified) { Chris@595: m_documentModified = false; Chris@595: } Chris@595: } Chris@45: Chris@604: if (!source.isRemote() && registerSource) { Chris@604: m_audioFile = source.getLocalFilename(); Chris@604: } Chris@45: Chris@669: updateWindowTitle(); Chris@669: Chris@45: } else if (mode == CreateAdditionalModel) { Chris@45: Chris@577: SVCERR << "Mode is CreateAdditionalModel" << endl; Chris@577: Chris@595: CommandHistory::getInstance()->startCompoundOperation Chris@595: (tr("Import \"%1\"").arg(source.getBasename()), true); Chris@595: Chris@691: m_document->addNonDerivedModel(newModel); Chris@595: Chris@595: AddPaneCommand *command = new AddPaneCommand(this); Chris@595: CommandHistory::getInstance()->addCommand(command); Chris@595: Chris@595: Pane *pane = command->getPane(); Chris@45: Chris@47: if (m_timeRulerLayer) { Chris@577: SVCERR << "Have time ruler, adding it" << endl; Chris@47: m_document->addLayerToView(pane, m_timeRulerLayer); Chris@577: } else { Chris@577: SVCERR << "Do not have time ruler" << endl; Chris@47: } Chris@45: Chris@595: Layer *newLayer = m_document->createImportedLayer(newModel); Chris@595: Chris@595: if (newLayer) { Chris@595: m_document->addLayerToView(pane, newLayer); Chris@595: } Chris@595: Chris@595: CommandHistory::getInstance()->endCompoundOperation(); Chris@45: Chris@45: } else if (mode == ReplaceCurrentPane) { Chris@45: Chris@45: // We know there is a current pane, otherwise we would have Chris@45: // reset the mode to CreateAdditionalModel above; and we know Chris@45: // the current pane does not contain the main model, otherwise Chris@45: // we would have reset it to ReplaceMainModel. But we don't Chris@45: // know whether the pane contains a waveform model at all. Chris@45: Chris@45: Pane *pane = m_paneStack->getCurrentPane(); Chris@636: Layer *replace = nullptr; Chris@45: Chris@45: for (int i = 0; i < pane->getLayerCount(); ++i) { Chris@45: Layer *layer = pane->getLayer(i); Chris@45: if (dynamic_cast(layer)) { Chris@45: replace = layer; Chris@45: break; Chris@45: } Chris@45: } Chris@45: Chris@595: CommandHistory::getInstance()->startCompoundOperation Chris@595: (tr("Import \"%1\"").arg(source.getBasename()), true); Chris@595: Chris@691: m_document->addNonDerivedModel(newModel); Chris@45: Chris@45: if (replace) { Chris@45: m_document->removeLayerFromView(pane, replace); Chris@45: } Chris@45: Chris@595: Layer *newLayer = m_document->createImportedLayer(newModel); Chris@595: Chris@595: if (newLayer) { Chris@595: m_document->addLayerToView(pane, newLayer); Chris@595: } Chris@595: Chris@595: CommandHistory::getInstance()->endCompoundOperation(); Chris@45: } Chris@45: Chris@45: updateMenuStates(); Chris@622: Chris@622: if (registerSource) { Chris@622: m_recentFiles.addFile(source.getLocation()); Chris@622: } Chris@604: if (!source.isRemote() && registerSource) { Chris@45: // for file dialog Chris@45: registerLastOpenedFilePath(FileFinder::AudioFile, Chris@45: source.getLocalFilename()); Chris@45: } Chris@622: Chris@45: m_openingAudioFile = false; Chris@45: Chris@45: currentPaneChanged(m_paneStack->getCurrentPane()); Chris@45: Chris@342: emit audioFileLoaded(); Chris@342: Chris@45: return FileOpenSucceeded; Chris@45: } Chris@45: Chris@45: MainWindowBase::FileOpenStatus Chris@45: MainWindowBase::openPlaylist(FileSource source, AudioFileOpenMode mode) Chris@45: { Chris@233: SVDEBUG << "MainWindowBase::openPlaylist(" << source.getLocation() << ")" << endl; Chris@135: Chris@45: std::set extensions; Chris@45: PlaylistFileReader::getSupportedExtensions(extensions); Chris@152: QString extension = source.getExtension().toLower(); Chris@45: if (extensions.find(extension) == extensions.end()) return FileOpenFailed; Chris@45: Chris@45: if (!source.isAvailable()) return FileOpenFailed; Chris@45: source.waitForData(); Chris@45: Chris@45: PlaylistFileReader reader(source.getLocalFilename()); Chris@45: if (!reader.isOK()) return FileOpenFailed; Chris@45: Chris@45: PlaylistFileReader::Playlist playlist = reader.load(); Chris@45: Chris@45: bool someSuccess = false; Chris@45: Chris@45: for (PlaylistFileReader::Playlist::const_iterator i = playlist.begin(); Chris@45: i != playlist.end(); ++i) { Chris@45: Chris@134: ProgressDialog dialog(tr("Opening playlist..."), true, 2000, this); Chris@134: connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash())); Chris@109: FileOpenStatus status = openAudio(FileSource(*i, &dialog), mode); Chris@45: Chris@45: if (status == FileOpenCancelled) { Chris@45: return FileOpenCancelled; Chris@45: } Chris@45: Chris@45: if (status == FileOpenSucceeded) { Chris@45: someSuccess = true; Chris@45: mode = CreateAdditionalModel; Chris@45: } Chris@45: } Chris@45: Chris@45: if (someSuccess) return FileOpenSucceeded; Chris@45: else return FileOpenFailed; Chris@45: } Chris@45: Chris@45: MainWindowBase::FileOpenStatus Chris@45: MainWindowBase::openLayer(FileSource source) Chris@45: { Chris@233: SVDEBUG << "MainWindowBase::openLayer(" << source.getLocation() << ")" << endl; Chris@135: Chris@45: Pane *pane = m_paneStack->getCurrentPane(); Chris@45: Chris@45: if (!pane) { Chris@595: // shouldn't happen, as the menu action should have been disabled Chris@595: cerr << "WARNING: MainWindowBase::openLayer: no current pane" << endl; Chris@595: return FileOpenWrongMode; Chris@45: } Chris@45: Chris@45: if (!getMainModel()) { Chris@595: // shouldn't happen, as the menu action should have been disabled Chris@595: cerr << "WARNING: MainWindowBase::openLayer: No main model -- hence no default sample rate available" << endl; Chris@595: return FileOpenWrongMode; Chris@45: } Chris@45: Chris@45: if (!source.isAvailable()) return FileOpenFailed; Chris@45: source.waitForData(); Chris@45: Chris@45: QString path = source.getLocalFilename(); Chris@45: Chris@145: RDFImporter::RDFDocumentType rdfType = Chris@145: RDFImporter::identifyDocumentType(QUrl::fromLocalFile(path).toString()); Chris@145: Chris@293: // cerr << "RDF type: (in layer) " << (int) rdfType << endl; Chris@148: Chris@145: if (rdfType != RDFImporter::NotRDF) { Chris@145: Chris@145: return openLayersFromRDF(source); Chris@134: Chris@152: } else if (source.getExtension().toLower() == "svl" || Chris@152: (source.getExtension().toLower() == "xml" && Chris@140: (SVFileReader::identifyXmlFile(source.getLocalFilename()) Chris@140: == SVFileReader::SVLayerFile))) { Chris@45: Chris@45: PaneCallback callback(this); Chris@45: QFile file(path); Chris@45: Chris@45: if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { Chris@293: cerr << "ERROR: MainWindowBase::openLayer(" Chris@294: << source.getLocation() Chris@293: << "): Failed to open file for reading" << endl; Chris@45: return FileOpenFailed; Chris@45: } Chris@45: Chris@45: SVFileReader reader(m_document, callback, source.getLocation()); Chris@79: connect Chris@79: (&reader, SIGNAL(modelRegenerationFailed(QString, QString, QString)), Chris@79: this, SLOT(modelRegenerationFailed(QString, QString, QString))); Chris@79: connect Chris@79: (&reader, SIGNAL(modelRegenerationWarning(QString, QString, QString)), Chris@79: this, SLOT(modelRegenerationWarning(QString, QString, QString))); Chris@45: reader.setCurrentPane(pane); Chris@45: Chris@45: QXmlInputSource inputSource(&file); Chris@45: reader.parse(inputSource); Chris@45: Chris@45: if (!reader.isOK()) { Chris@293: cerr << "ERROR: MainWindowBase::openLayer(" Chris@294: << source.getLocation() Chris@45: << "): Failed to read XML file: " Chris@293: << reader.getErrorString() << endl; Chris@45: return FileOpenFailed; Chris@45: } Chris@45: Chris@164: emit activity(tr("Import layer XML file \"%1\"").arg(source.getLocation())); Chris@164: Chris@45: m_recentFiles.addFile(source.getLocation()); Chris@45: Chris@45: if (!source.isRemote()) { Chris@45: registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog Chris@45: } Chris@45: Chris@75: return FileOpenSucceeded; Chris@75: Chris@45: } else { Chris@45: Chris@45: try { Chris@45: Chris@109: MIDIFileImportDialog midiDlg(this); Chris@109: Chris@684: Model *newModelPtr = DataFileReaderFactory::loadNonCSV Chris@109: (path, &midiDlg, getMainModel()->getSampleRate()); Chris@45: Chris@684: if (!newModelPtr) { Chris@643: CSVFormatDialog *dialog = Chris@643: new CSVFormatDialog(this, Chris@643: path, Chris@643: getMainModel()->getSampleRate(), Chris@643: 5); Chris@109: if (dialog->exec() == QDialog::Accepted) { Chris@684: newModelPtr = DataFileReaderFactory::loadCSV Chris@109: (path, dialog->getFormat(), Chris@109: getMainModel()->getSampleRate()); Chris@109: } Chris@619: delete dialog; Chris@109: } Chris@109: Chris@684: if (newModelPtr) { Chris@45: Chris@233: SVDEBUG << "MainWindowBase::openLayer: Have model" << endl; Chris@45: Chris@164: emit activity(tr("Import MIDI file \"%1\"").arg(source.getLocation())); Chris@164: Chris@687: ModelId modelId = Chris@687: ModelById::add(std::shared_ptr(newModelPtr)); Chris@684: Chris@684: Layer *newLayer = m_document->createImportedLayer(modelId); Chris@45: Chris@45: if (newLayer) { Chris@45: Chris@45: m_document->addLayerToView(pane, newLayer); Chris@88: m_paneStack->setCurrentLayer(pane, newLayer); Chris@88: Chris@45: m_recentFiles.addFile(source.getLocation()); Chris@45: Chris@45: if (!source.isRemote()) { Chris@45: registerLastOpenedFilePath Chris@45: (FileFinder::LayerFile, Chris@45: path); // for file dialog Chris@45: } Chris@88: Chris@45: return FileOpenSucceeded; Chris@45: } Chris@45: } Chris@45: } catch (DataFileReaderFactory::Exception e) { Chris@45: if (e == DataFileReaderFactory::ImportCancelled) { Chris@45: return FileOpenCancelled; Chris@45: } Chris@45: } Chris@45: } Chris@45: Chris@45: return FileOpenFailed; Chris@45: } Chris@45: Chris@45: MainWindowBase::FileOpenStatus Chris@45: MainWindowBase::openImage(FileSource source) Chris@45: { Chris@233: SVDEBUG << "MainWindowBase::openImage(" << source.getLocation() << ")" << endl; Chris@135: Chris@45: Pane *pane = m_paneStack->getCurrentPane(); Chris@45: Chris@45: if (!pane) { Chris@595: // shouldn't happen, as the menu action should have been disabled Chris@595: cerr << "WARNING: MainWindowBase::openImage: no current pane" << endl; Chris@595: return FileOpenWrongMode; Chris@45: } Chris@45: Chris@684: if (!getMainModel()) { Chris@45: return FileOpenWrongMode; Chris@45: } Chris@45: Chris@45: bool newLayer = false; Chris@45: Chris@45: ImageLayer *il = dynamic_cast(pane->getSelectedLayer()); Chris@45: if (!il) { Chris@45: for (int i = pane->getLayerCount()-1; i >= 0; --i) { Chris@45: il = dynamic_cast(pane->getLayer(i)); Chris@45: if (il) break; Chris@45: } Chris@45: } Chris@45: if (!il) { Chris@45: il = dynamic_cast Chris@45: (m_document->createEmptyLayer(LayerFactory::Image)); Chris@45: if (!il) return FileOpenFailed; Chris@45: newLayer = true; Chris@45: } Chris@45: Chris@45: // We don't put the image file in Recent Files Chris@45: Chris@293: cerr << "openImage: trying location \"" << source.getLocation() << "\" in image layer" << endl; Chris@45: Chris@45: if (!il->addImage(m_viewManager->getGlobalCentreFrame(), source.getLocation())) { Chris@45: if (newLayer) { Chris@52: m_document->deleteLayer(il); // also releases its model Chris@45: } Chris@45: return FileOpenFailed; Chris@45: } else { Chris@45: if (newLayer) { Chris@45: m_document->addLayerToView(pane, il); Chris@45: } Chris@45: m_paneStack->setCurrentLayer(pane, il); Chris@45: } Chris@45: Chris@45: return FileOpenSucceeded; Chris@45: } Chris@45: Chris@45: MainWindowBase::FileOpenStatus Chris@427: MainWindowBase::openDirOfAudio(QString dirPath) Chris@427: { Chris@427: QDir dir(dirPath); Chris@427: QStringList files = dir.entryList(QDir::Files | QDir::Readable); Chris@427: files.sort(); Chris@427: Chris@427: FileOpenStatus status = FileOpenFailed; Chris@427: bool first = true; Chris@427: bool cancelled = false; Chris@427: Chris@427: foreach (QString file, files) { Chris@427: Chris@427: FileSource source(dir.filePath(file)); Chris@427: if (!source.isAvailable()) { Chris@427: continue; Chris@427: } Chris@427: Chris@427: if (AudioFileReaderFactory::getKnownExtensions().contains Chris@427: (source.getExtension().toLower())) { Chris@427: Chris@427: AudioFileOpenMode mode = CreateAdditionalModel; Chris@427: if (first) mode = ReplaceSession; Chris@427: Chris@427: switch (openAudio(source, mode)) { Chris@427: case FileOpenSucceeded: Chris@427: status = FileOpenSucceeded; Chris@427: first = false; Chris@427: break; Chris@427: case FileOpenFailed: Chris@427: break; Chris@427: case FileOpenCancelled: Chris@427: cancelled = true; Chris@427: break; Chris@427: case FileOpenWrongMode: Chris@427: break; Chris@427: } Chris@427: } Chris@427: Chris@427: if (cancelled) break; Chris@427: } Chris@427: Chris@427: return status; Chris@427: } Chris@427: Chris@427: MainWindowBase::FileOpenStatus Chris@373: MainWindowBase::openSessionPath(QString fileOrUrl) Chris@45: { Chris@134: ProgressDialog dialog(tr("Opening session..."), true, 2000, this); Chris@134: connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash())); Chris@109: return openSession(FileSource(fileOrUrl, &dialog)); Chris@45: } Chris@45: Chris@45: MainWindowBase::FileOpenStatus Chris@45: MainWindowBase::openSession(FileSource source) Chris@45: { Chris@233: SVDEBUG << "MainWindowBase::openSession(" << source.getLocation() << ")" << endl; Chris@135: Chris@45: if (!source.isAvailable()) return FileOpenFailed; Chris@145: source.waitForData(); Chris@141: Chris@341: QString sessionExt = Chris@341: InteractiveFileFinder::getInstance()->getApplicationSessionExtension(); Chris@341: Chris@341: if (source.getExtension().toLower() != sessionExt) { Chris@145: Chris@145: RDFImporter::RDFDocumentType rdfType = Chris@145: RDFImporter::identifyDocumentType Chris@145: (QUrl::fromLocalFile(source.getLocalFilename()).toString()); Chris@145: Chris@293: // cerr << "RDF type: " << (int)rdfType << endl; Chris@148: Chris@145: if (rdfType == RDFImporter::AudioRefAndAnnotations || Chris@145: rdfType == RDFImporter::AudioRef) { Chris@145: return openSessionFromRDF(source); Chris@145: } else if (rdfType != RDFImporter::NotRDF) { Chris@145: return FileOpenFailed; Chris@145: } Chris@145: Chris@152: if (source.getExtension().toLower() == "xml") { Chris@140: if (SVFileReader::identifyXmlFile(source.getLocalFilename()) == Chris@140: SVFileReader::SVSessionFile) { Chris@293: cerr << "This XML file looks like a session file, attempting to open it as a session" << endl; Chris@140: } else { Chris@140: return FileOpenFailed; Chris@140: } Chris@140: } else { Chris@140: return FileOpenFailed; Chris@140: } Chris@140: } Chris@45: Chris@636: QXmlInputSource *inputSource = nullptr; Chris@636: BZipFileDevice *bzFile = nullptr; Chris@636: QFile *rawFile = nullptr; Chris@140: Chris@341: if (source.getExtension().toLower() == sessionExt) { Chris@140: bzFile = new BZipFileDevice(source.getLocalFilename()); Chris@140: if (!bzFile->open(QIODevice::ReadOnly)) { Chris@140: delete bzFile; Chris@140: return FileOpenFailed; Chris@140: } Chris@140: inputSource = new QXmlInputSource(bzFile); Chris@140: } else { Chris@140: rawFile = new QFile(source.getLocalFilename()); Chris@140: inputSource = new QXmlInputSource(rawFile); Chris@140: } Chris@140: Chris@140: if (!checkSaveModified()) { Chris@140: if (bzFile) bzFile->close(); Chris@140: delete inputSource; Chris@140: delete bzFile; Chris@140: delete rawFile; Chris@140: return FileOpenCancelled; Chris@140: } Chris@45: Chris@45: QString error; Chris@45: closeSession(); Chris@45: createDocument(); Chris@45: Chris@45: PaneCallback callback(this); Chris@45: m_viewManager->clearSelections(); Chris@45: Chris@45: SVFileReader reader(m_document, callback, source.getLocation()); Chris@79: connect Chris@79: (&reader, SIGNAL(modelRegenerationFailed(QString, QString, QString)), Chris@79: this, SLOT(modelRegenerationFailed(QString, QString, QString))); Chris@79: connect Chris@79: (&reader, SIGNAL(modelRegenerationWarning(QString, QString, QString)), Chris@79: this, SLOT(modelRegenerationWarning(QString, QString, QString))); Chris@140: Chris@140: reader.parse(*inputSource); Chris@45: Chris@45: if (!reader.isOK()) { Chris@45: error = tr("SV XML file read error:\n%1").arg(reader.getErrorString()); Chris@45: } Chris@45: Chris@140: if (bzFile) bzFile->close(); Chris@140: Chris@140: delete inputSource; Chris@140: delete bzFile; Chris@140: delete rawFile; Chris@45: Chris@45: bool ok = (error == ""); Chris@45: Chris@45: if (ok) { Chris@45: Chris@164: emit activity(tr("Import session file \"%1\"").arg(source.getLocation())); Chris@164: Chris@601: if (!source.isRemote() && !m_document->isIncomplete()) { Chris@601: // Setting the session file path enables the Save (as Chris@601: // opposed to Save As...) option. We can't do this if we Chris@601: // don't have a local path to save to, but we also don't Chris@601: // want to do it if we failed to find an audio file or Chris@601: // similar on load, as the audio reference would then end Chris@601: // up being lost from any saved or auto-saved-on-exit copy Chris@601: m_sessionFile = source.getLocalFilename(); Chris@602: } else { Chris@602: QMessageBox::warning Chris@602: (this, Chris@602: tr("Incomplete session loaded"), Chris@603: 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: QMessageBox::Ok); Chris@601: } Chris@595: Chris@669: updateWindowTitle(); Chris@595: setupMenus(); Chris@577: findTimeRulerLayer(); Chris@45: Chris@595: CommandHistory::getInstance()->clear(); Chris@595: CommandHistory::getInstance()->documentSaved(); Chris@595: m_documentModified = false; Chris@595: updateMenuStates(); Chris@45: Chris@227: m_recentFiles.addFile(source.getLocation()); Chris@45: Chris@45: if (!source.isRemote()) { Chris@45: // for file dialog Chris@45: registerLastOpenedFilePath(FileFinder::SessionFile, Chris@227: source.getLocalFilename()); Chris@45: } Chris@45: Chris@669: m_originalLocation = source.getLocation(); Chris@669: Chris@342: emit sessionLoaded(); Chris@342: Chris@669: updateWindowTitle(); Chris@45: } Chris@669: Chris@45: return ok ? FileOpenSucceeded : FileOpenFailed; Chris@45: } Chris@45: Chris@141: MainWindowBase::FileOpenStatus Chris@230: MainWindowBase::openSessionTemplate(QString templateName) Chris@230: { Chris@230: // Template in the user's template directory takes Chris@230: // priority over a bundled one; we don't unbundle, but Chris@230: // open directly from the bundled file (where applicable) Chris@230: ResourceFinder rf; Chris@230: QString tfile = rf.getResourcePath("templates", templateName + ".svt"); Chris@230: if (tfile != "") { Chris@294: cerr << "SV loading template file " << tfile << endl; Chris@230: return openSessionTemplate(FileSource("file:" + tfile)); Chris@230: } else { Chris@230: return FileOpenFailed; Chris@230: } Chris@230: } Chris@230: Chris@230: MainWindowBase::FileOpenStatus Chris@227: MainWindowBase::openSessionTemplate(FileSource source) Chris@227: { Chris@294: cerr << "MainWindowBase::openSessionTemplate(" << source.getLocation() << ")" << endl; Chris@227: Chris@227: if (!source.isAvailable()) return FileOpenFailed; Chris@227: source.waitForData(); Chris@227: Chris@636: QXmlInputSource *inputSource = nullptr; Chris@636: QFile *file = nullptr; Chris@227: Chris@227: file = new QFile(source.getLocalFilename()); Chris@227: inputSource = new QXmlInputSource(file); Chris@227: Chris@227: if (!checkSaveModified()) { Chris@227: delete inputSource; Chris@227: delete file; Chris@227: return FileOpenCancelled; Chris@227: } Chris@227: Chris@227: QString error; Chris@227: closeSession(); Chris@227: createDocument(); Chris@227: Chris@227: PaneCallback callback(this); Chris@227: m_viewManager->clearSelections(); Chris@227: Chris@227: SVFileReader reader(m_document, callback, source.getLocation()); Chris@227: connect Chris@227: (&reader, SIGNAL(modelRegenerationFailed(QString, QString, QString)), Chris@227: this, SLOT(modelRegenerationFailed(QString, QString, QString))); Chris@227: connect Chris@227: (&reader, SIGNAL(modelRegenerationWarning(QString, QString, QString)), Chris@227: this, SLOT(modelRegenerationWarning(QString, QString, QString))); Chris@227: Chris@227: reader.parse(*inputSource); Chris@227: Chris@227: if (!reader.isOK()) { Chris@227: error = tr("SV XML file read error:\n%1").arg(reader.getErrorString()); Chris@227: } Chris@227: Chris@227: delete inputSource; Chris@227: delete file; Chris@227: Chris@227: bool ok = (error == ""); Chris@227: Chris@227: if (ok) { Chris@227: Chris@227: emit activity(tr("Open session template \"%1\"").arg(source.getLocation())); Chris@227: Chris@595: setupMenus(); Chris@577: findTimeRulerLayer(); Chris@227: Chris@595: CommandHistory::getInstance()->clear(); Chris@595: CommandHistory::getInstance()->documentSaved(); Chris@595: m_documentModified = false; Chris@595: updateMenuStates(); Chris@342: Chris@342: emit sessionLoaded(); Chris@227: } Chris@227: Chris@669: updateWindowTitle(); Chris@669: Chris@227: return ok ? FileOpenSucceeded : FileOpenFailed; Chris@227: } Chris@227: Chris@227: MainWindowBase::FileOpenStatus Chris@141: MainWindowBase::openSessionFromRDF(FileSource source) Chris@141: { Chris@233: SVDEBUG << "MainWindowBase::openSessionFromRDF(" << source.getLocation() << ")" << endl; Chris@141: Chris@141: if (!source.isAvailable()) return FileOpenFailed; Chris@141: source.waitForData(); Chris@141: Chris@145: if (!checkSaveModified()) { Chris@145: return FileOpenCancelled; Chris@141: } Chris@143: Chris@145: closeSession(); Chris@145: createDocument(); Chris@145: Chris@145: FileOpenStatus status = openLayersFromRDF(source); Chris@141: Chris@141: setupMenus(); Chris@577: findTimeRulerLayer(); Chris@669: Chris@141: CommandHistory::getInstance()->clear(); Chris@141: CommandHistory::getInstance()->documentSaved(); Chris@141: m_documentModified = false; Chris@669: updateWindowTitle(); Chris@145: Chris@342: emit sessionLoaded(); Chris@342: Chris@145: return status; Chris@145: } Chris@145: Chris@145: MainWindowBase::FileOpenStatus Chris@145: MainWindowBase::openLayersFromRDF(FileSource source) Chris@145: { Chris@435: sv_samplerate_t rate = 0; Chris@145: Chris@233: SVDEBUG << "MainWindowBase::openLayersFromRDF" << endl; Chris@186: Chris@145: ProgressDialog dialog(tr("Importing from RDF..."), true, 2000, this); Chris@145: connect(&dialog, SIGNAL(showing()), this, SIGNAL(hideSplash())); Chris@145: Chris@145: if (getMainModel()) { Chris@145: rate = getMainModel()->getSampleRate(); Chris@145: } else if (Preferences::getInstance()->getResampleOnLoad()) { Chris@552: if (getMainModel()) { Chris@552: rate = getMainModel()->getSampleRate(); Chris@552: } Chris@145: } Chris@145: Chris@145: RDFImporter importer Chris@145: (QUrl::fromLocalFile(source.getLocalFilename()).toString(), rate); Chris@145: Chris@145: if (!importer.isOK()) { Chris@147: if (importer.getErrorString() != "") { Chris@147: QMessageBox::critical Chris@147: (this, tr("Failed to import RDF"), Chris@147: tr("Failed to import RDF

Importing data from RDF document at \"%1\" failed: %2

") Chris@147: .arg(source.getLocation()).arg(importer.getErrorString())); Chris@147: } Chris@145: return FileOpenFailed; Chris@145: } Chris@145: Chris@687: std::vector modelIds = importer.getDataModels(&dialog); Chris@145: Chris@145: dialog.setMessage(tr("Importing from RDF...")); Chris@145: Chris@687: if (modelIds.empty()) { Chris@186: QMessageBox::critical Chris@186: (this, tr("Failed to import RDF"), Chris@186: tr("Failed to import RDF

No suitable data models found for import from RDF document at \"%1\"

").arg(source.getLocation())); Chris@145: return FileOpenFailed; Chris@145: } Chris@145: Chris@164: emit activity(tr("Import RDF document \"%1\"").arg(source.getLocation())); Chris@684: Chris@684: std::set added; Chris@684: Chris@684: for (auto modelId: modelIds) { Chris@684: Chris@684: if (ModelById::isa(modelId)) { Chris@145: Chris@145: Pane *pane = addPaneToStack(); Chris@636: Layer *layer = nullptr; Chris@145: Chris@145: if (m_timeRulerLayer) { Chris@145: m_document->addLayerToView(pane, m_timeRulerLayer); Chris@145: } Chris@145: Chris@145: if (!getMainModel()) { Chris@684: m_document->setMainModel(modelId); Chris@145: layer = m_document->createMainModelLayer(LayerFactory::Waveform); Chris@145: } else { Chris@684: layer = m_document->createImportedLayer(modelId); Chris@145: } Chris@145: Chris@145: m_document->addLayerToView(pane, layer); Chris@145: Chris@684: added.insert(modelId); Chris@684: Chris@684: for (auto otherId: modelIds) { Chris@684: Chris@684: if (otherId == modelId) continue; Chris@684: Chris@684: bool isDependent = false; Chris@684: if (auto dm = ModelById::get(otherId)) { Chris@684: if (dm->getSourceModel() == modelId) { Chris@684: isDependent = true; Chris@684: } Chris@684: } Chris@684: if (!isDependent) continue; Chris@684: Chris@684: layer = m_document->createImportedLayer(otherId); Chris@145: Chris@145: if (layer->isLayerOpaque() || Chris@145: dynamic_cast(layer)) { Chris@145: Chris@156: // these always go in a new pane, with nothing Chris@156: // else going in the same pane Chris@156: Chris@145: Pane *singleLayerPane = addPaneToStack(); Chris@145: if (m_timeRulerLayer) { Chris@145: m_document->addLayerToView(singleLayerPane, m_timeRulerLayer); Chris@145: } Chris@145: m_document->addLayerToView(singleLayerPane, layer); Chris@145: Chris@156: } else if (layer->getLayerColourSignificance() == Chris@156: Layer::ColourHasMeaningfulValue) { Chris@156: Chris@156: // these can go in a pane with something else, but Chris@156: // only if none of the something elses also have Chris@156: // this quality Chris@156: Chris@156: bool needNewPane = false; Chris@156: for (int i = 0; i < pane->getLayerCount(); ++i) { Chris@156: Layer *otherLayer = pane->getLayer(i); Chris@156: if (otherLayer && Chris@156: (otherLayer->getLayerColourSignificance() == Chris@156: Layer::ColourHasMeaningfulValue)) { Chris@156: needNewPane = true; Chris@156: break; Chris@156: } Chris@156: } Chris@156: if (needNewPane) { Chris@156: pane = addPaneToStack(); Chris@156: } Chris@156: Chris@156: m_document->addLayerToView(pane, layer); Chris@156: Chris@145: } else { Chris@145: Chris@145: if (pane->getLayerCount() > 4) { Chris@145: pane = addPaneToStack(); Chris@145: } Chris@145: Chris@145: m_document->addLayerToView(pane, layer); Chris@145: } Chris@145: Chris@684: added.insert(otherId); Chris@145: } Chris@145: } Chris@145: } Chris@145: Chris@684: for (auto modelId : modelIds) { Chris@684: Chris@684: if (added.find(modelId) == added.end()) { Chris@145: Chris@684: Layer *layer = m_document->createImportedLayer(modelId); Chris@145: if (!layer) return FileOpenFailed; Chris@145: Chris@145: Pane *singleLayerPane = addPaneToStack(); Chris@145: if (m_timeRulerLayer) { Chris@145: m_document->addLayerToView(singleLayerPane, m_timeRulerLayer); Chris@145: } Chris@145: m_document->addLayerToView(singleLayerPane, layer); Chris@145: } Chris@145: } Chris@145: Chris@145: m_recentFiles.addFile(source.getLocation()); Chris@145: return FileOpenSucceeded; Chris@141: } Chris@141: Chris@584: class AudioLogCallback : public breakfastquay::AudioFactory::LogCallback Chris@584: { Chris@584: public: Chris@584: void log(std::string message) const override { Chris@584: SVDEBUG << message << endl; Chris@584: } Chris@584: }; Chris@584: Chris@45: void Chris@475: MainWindowBase::createAudioIO() Chris@45: { Chris@475: if (m_playTarget || m_audioIO) return; Chris@475: Chris@584: static AudioLogCallback audioLogCallback; Chris@584: breakfastquay::AudioFactory::setLogCallback(&audioLogCallback); Chris@584: Chris@475: if (!(m_soundOptions & WithAudioOutput)) return; Chris@45: Chris@126: QSettings settings; Chris@126: settings.beginGroup("Preferences"); Chris@547: QString implementation = settings.value Chris@547: ("audio-target", "").toString(); Chris@547: QString suffix; Chris@547: if (implementation != "") suffix = "-" + implementation; Chris@547: QString recordDevice = settings.value Chris@547: ("audio-record-device" + suffix, "").toString(); Chris@547: QString playbackDevice = settings.value Chris@547: ("audio-playback-device" + suffix, "").toString(); Chris@126: settings.endGroup(); Chris@547: Chris@547: if (implementation == "auto") { Chris@547: implementation = ""; Chris@547: } Chris@468: Chris@547: breakfastquay::AudioFactory::Preference preference; Chris@547: preference.implementation = implementation.toStdString(); Chris@547: preference.recordDevice = recordDevice.toStdString(); Chris@547: preference.playbackDevice = playbackDevice.toStdString(); Chris@547: Chris@547: SVCERR << "createAudioIO: Preferred implementation = \"" Chris@547: << preference.implementation << "\"" << endl; Chris@547: SVCERR << "createAudioIO: Preferred playback device = \"" Chris@547: << preference.playbackDevice << "\"" << endl; Chris@547: SVCERR << "createAudioIO: Preferred record device = \"" Chris@547: << preference.recordDevice << "\"" << endl; Chris@475: Chris@551: if (!m_resamplerWrapper) { Chris@551: m_resamplerWrapper = new breakfastquay::ResamplerWrapper(m_playSource); Chris@551: m_playSource->setResamplerWrapper(m_resamplerWrapper); Chris@551: } Chris@569: Chris@569: std::string errorString; Chris@551: Chris@475: if (m_soundOptions & WithAudioInput) { Chris@475: m_audioIO = breakfastquay::AudioFactory:: Chris@569: createCallbackIO(m_recordTarget, m_resamplerWrapper, Chris@569: preference, errorString); Chris@525: if (m_audioIO) { Chris@611: SVCERR << "MainWindowBase::createAudioIO: Suspending on creation" << endl; Chris@525: m_audioIO->suspend(); // start in suspended state Chris@525: m_playSource->setSystemPlaybackTarget(m_audioIO); Chris@586: } else { Chris@586: // Failed to create audio I/O; this may just mean there is Chris@586: // no record device, so fall through to see what happens Chris@586: // next. We only report complete failure if we end up with Chris@586: // neither m_audioIO nor m_playTarget. Chris@525: } Chris@586: } Chris@586: Chris@586: if (!m_audioIO) { Chris@475: m_playTarget = breakfastquay::AudioFactory:: Chris@569: createCallbackPlayTarget(m_resamplerWrapper, Chris@569: preference, errorString); Chris@525: if (m_playTarget) { Chris@611: SVCERR << "MainWindowBase::createAudioIO: Suspending on creation" << endl; Chris@525: m_playTarget->suspend(); // start in suspended state Chris@525: m_playSource->setSystemPlaybackTarget(m_playTarget); Chris@525: } Chris@475: } Chris@475: Chris@475: if (!m_playTarget && !m_audioIO) { Chris@104: emit hideSplash(); Chris@569: QString message; Chris@569: QString error = errorString.c_str(); Chris@569: QString firstBit, secondBit; Chris@547: if (implementation == "") { Chris@569: if (error == "") { Chris@569: firstBit = tr("No audio available

Could not open an audio device.

"); Chris@569: } else { Chris@569: firstBit = tr("No audio available

Could not open audio device: %1

").arg(error); Chris@569: } Chris@569: if (m_soundOptions & WithAudioInput) { Chris@569: secondBit = tr("

Automatic audio device detection failed. Audio playback and recording will not be available during this session.

"); Chris@569: } else { Chris@569: secondBit = tr("

Automatic audio device detection failed. Audio playback will not be available during this session.

"); Chris@569: } Chris@126: } else { Chris@569: QString driverName = breakfastquay::AudioFactory:: Chris@569: getImplementationDescription(implementation.toStdString()) Chris@569: .c_str(); Chris@569: if (error == "") { Chris@569: firstBit = tr("No audio available

Failed to open your preferred audio driver (\"%1\").

").arg(driverName); Chris@569: } else { Chris@569: firstBit = tr("No audio available

Failed to open your preferred audio driver (\"%1\"): %2.

").arg(driverName).arg(error); Chris@569: } Chris@569: if (m_soundOptions & WithAudioInput) { Chris@569: secondBit = tr("

Audio playback and recording will not be available during this session.

"); Chris@569: } else { Chris@569: secondBit = tr("

Audio playback will not be available during this session.

"); Chris@569: } Chris@126: } Chris@570: SVDEBUG << "createAudioIO: ERROR: Failed to open audio device \"" Chris@570: << implementation << "\": error is: " << error << endl; Chris@569: QMessageBox::warning(this, tr("Couldn't open audio device"), Chris@569: firstBit + secondBit, QMessageBox::Ok); Chris@45: } Chris@45: } Chris@45: Chris@556: void Chris@556: MainWindowBase::deleteAudioIO() Chris@556: { Chris@556: // First prevent this trying to call target. Chris@559: if (m_playSource) { Chris@636: m_playSource->setSystemPlaybackTarget(nullptr); Chris@636: m_playSource->setResamplerWrapper(nullptr); Chris@559: } Chris@556: Chris@556: // Then delete the breakfastquay::System object. Chris@556: // Only one of these two exists! Chris@556: delete m_audioIO; Chris@556: delete m_playTarget; Chris@556: Chris@559: // And the breakfastquay resampler wrapper. We need to Chris@559: // delete/recreate this if the channel count changes, which is one Chris@559: // of the use cases for recreateAudioIO() calling this Chris@559: delete m_resamplerWrapper; Chris@559: Chris@636: m_audioIO = nullptr; Chris@636: m_playTarget = nullptr; Chris@636: m_resamplerWrapper = nullptr; Chris@556: } Chris@556: Chris@556: void Chris@556: MainWindowBase::recreateAudioIO() Chris@556: { Chris@556: deleteAudioIO(); Chris@556: createAudioIO(); Chris@556: } Chris@556: Chris@570: void Chris@570: MainWindowBase::audioChannelCountIncreased(int) Chris@570: { Chris@611: SVCERR << "MainWindowBase::audioChannelCountIncreased" << endl; Chris@570: recreateAudioIO(); Chris@610: Chris@610: if (m_recordTarget && Chris@610: m_recordTarget->isRecording() && Chris@610: m_audioIO) { Chris@610: SVCERR << "MainWindowBase::audioChannelCountIncreased: we were recording already, so resuming IO now" << endl; Chris@610: m_audioIO->resume(); Chris@610: } Chris@570: } Chris@570: Chris@684: ModelId Chris@685: MainWindowBase::getMainModelId() const Chris@684: { Chris@684: if (!m_document) return {}; Chris@684: return m_document->getMainModel(); Chris@684: } Chris@684: Chris@684: std::shared_ptr Chris@685: MainWindowBase::getMainModel() const Chris@45: { Chris@684: return ModelById::getAs(getMainModelId()); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::createDocument() Chris@45: { Chris@45: m_document = new Document; Chris@45: Chris@45: connect(m_document, SIGNAL(layerAdded(Layer *)), Chris@595: this, SLOT(layerAdded(Layer *))); Chris@45: connect(m_document, SIGNAL(layerRemoved(Layer *)), Chris@595: this, SLOT(layerRemoved(Layer *))); Chris@45: connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)), Chris@595: this, SLOT(layerAboutToBeDeleted(Layer *))); Chris@45: connect(m_document, SIGNAL(layerInAView(Layer *, bool)), Chris@595: this, SLOT(layerInAView(Layer *, bool))); Chris@45: Chris@684: connect(m_document, SIGNAL(modelAdded(ModelId )), Chris@684: this, SLOT(modelAdded(ModelId ))); Chris@687: connect(m_document, SIGNAL(mainModelChanged(ModelId)), Chris@687: this, SLOT(mainModelChanged(ModelId))); Chris@45: Chris@78: connect(m_document, SIGNAL(modelGenerationFailed(QString, QString)), Chris@78: this, SLOT(modelGenerationFailed(QString, QString))); Chris@78: connect(m_document, SIGNAL(modelRegenerationWarning(QString, QString, QString)), Chris@78: this, SLOT(modelRegenerationWarning(QString, QString, QString))); Chris@687: connect(m_document, SIGNAL(alignmentComplete(ModelId)), Chris@687: this, SLOT(alignmentComplete(ModelId))); Chris@423: connect(m_document, SIGNAL(alignmentFailed(QString)), Chris@423: this, SLOT(alignmentFailed(QString))); Chris@160: Chris@667: m_document->setAutoAlignment(m_viewManager->getAlignMode()); Chris@667: Chris@160: emit replacedDocument(); Chris@45: } Chris@45: Chris@45: bool Chris@45: MainWindowBase::saveSessionFile(QString path) Chris@45: { Chris@217: try { Chris@217: Chris@217: TempWriteFile temp(path); Chris@217: Chris@217: BZipFileDevice bzFile(temp.getTemporaryFilename()); Chris@217: if (!bzFile.open(QIODevice::WriteOnly)) { Chris@293: cerr << "Failed to open session file \"" Chris@294: << temp.getTemporaryFilename() Chris@217: << "\" for writing: " Chris@293: << bzFile.errorString() << endl; Chris@217: return false; Chris@217: } Chris@217: Chris@217: QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); Chris@217: Chris@217: QTextStream out(&bzFile); Chris@432: out.setCodec(QTextCodec::codecForName("UTF-8")); Chris@226: toXml(out, false); Chris@217: out.flush(); Chris@217: Chris@217: QApplication::restoreOverrideCursor(); Chris@217: Chris@217: if (!bzFile.isOK()) { Chris@217: QMessageBox::critical(this, tr("Failed to write file"), Chris@217: tr("Save failed

Failed to write to file \"%1\": %2") Chris@217: .arg(path).arg(bzFile.errorString())); Chris@217: bzFile.close(); Chris@217: return false; Chris@217: } Chris@217: Chris@217: bzFile.close(); Chris@217: temp.moveToTarget(); Chris@217: return true; Chris@217: Chris@217: } catch (FileOperationFailed &f) { Chris@217: Chris@217: QMessageBox::critical(this, tr("Failed to write file"), Chris@217: tr("Save failed

Failed to write to file \"%1\": %2") Chris@217: .arg(path).arg(f.what())); Chris@45: return false; Chris@45: } Chris@45: } Chris@45: Chris@224: bool Chris@224: MainWindowBase::saveSessionTemplate(QString path) Chris@224: { Chris@224: try { Chris@224: Chris@224: TempWriteFile temp(path); Chris@224: Chris@224: QFile file(temp.getTemporaryFilename()); Chris@224: if (!file.open(QIODevice::WriteOnly)) { Chris@293: cerr << "Failed to open session template file \"" Chris@294: << temp.getTemporaryFilename() Chris@224: << "\" for writing: " Chris@294: << file.errorString() << endl; Chris@224: return false; Chris@224: } Chris@224: Chris@224: QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); Chris@224: Chris@224: QTextStream out(&file); Chris@432: out.setCodec(QTextCodec::codecForName("UTF-8")); Chris@226: toXml(out, true); Chris@224: out.flush(); Chris@224: Chris@224: QApplication::restoreOverrideCursor(); Chris@224: Chris@224: file.close(); Chris@224: temp.moveToTarget(); Chris@224: return true; Chris@224: Chris@224: } catch (FileOperationFailed &f) { Chris@224: Chris@224: QMessageBox::critical(this, tr("Failed to write file"), Chris@224: tr("Save failed

Failed to write to file \"%1\": %2") Chris@224: .arg(path).arg(f.what())); Chris@224: return false; Chris@224: } Chris@224: } Chris@224: Chris@659: bool Chris@659: MainWindowBase::exportLayerTo(Layer *layer, QString path, QString &error) Chris@659: { Chris@659: if (QFileInfo(path).suffix() == "") path += ".svl"; Chris@659: Chris@659: QString suffix = QFileInfo(path).suffix().toLower(); Chris@659: Chris@684: auto model = ModelById::get(layer->getModel()); Chris@684: if (!model) { Chris@684: error = tr("Internal error: unknown model"); Chris@684: return false; Chris@684: } Chris@659: Chris@659: if (suffix == "xml" || suffix == "svl") { Chris@659: Chris@659: QFile file(path); Chris@659: if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { Chris@659: error = tr("Failed to open file %1 for writing").arg(path); Chris@659: } else { Chris@659: QTextStream out(&file); Chris@659: out.setCodec(QTextCodec::codecForName("UTF-8")); Chris@659: out << "\n" Chris@659: << "\n" Chris@659: << "\n" Chris@659: << " \n"; Chris@659: Chris@659: model->toXml(out, " "); Chris@659: Chris@659: out << " \n" Chris@659: << " \n"; Chris@659: Chris@659: layer->toXml(out, " "); Chris@659: Chris@659: out << " \n" Chris@659: << "\n"; Chris@659: } Chris@659: Chris@659: } else if (suffix == "mid" || suffix == "midi") { Chris@659: Chris@684: auto nm = ModelById::getAs(layer->getModel()); Chris@659: Chris@659: if (!nm) { Chris@659: error = tr("Can't export non-note layers to MIDI"); Chris@659: } else { Chris@684: MIDIFileWriter writer(path, nm.get(), nm->getSampleRate()); Chris@659: writer.write(); Chris@659: if (!writer.isOK()) { Chris@659: error = writer.getError(); Chris@659: } Chris@659: } Chris@659: Chris@659: } else if (suffix == "ttl" || suffix == "n3") { Chris@659: Chris@684: if (!RDFExporter::canExportModel(model.get())) { Chris@659: error = tr("Sorry, cannot export this layer type to RDF (supported types are: region, note, text, time instants, time values)"); Chris@659: } else { Chris@684: RDFExporter exporter(path, model.get()); Chris@659: exporter.write(); Chris@659: if (!exporter.isOK()) { Chris@659: error = exporter.getError(); Chris@659: } Chris@659: } Chris@659: Chris@659: } else { Chris@659: Chris@684: CSVFileWriter writer(path, model.get(), Chris@659: ((suffix == "csv") ? "," : "\t")); Chris@659: writer.write(); Chris@659: Chris@659: if (!writer.isOK()) { Chris@659: error = writer.getError(); Chris@659: } Chris@659: } Chris@659: Chris@659: return (error == ""); Chris@659: } Chris@659: Chris@45: void Chris@226: MainWindowBase::toXml(QTextStream &out, bool asTemplate) Chris@45: { Chris@45: QString indent(" "); Chris@45: Chris@45: out << "\n"; Chris@45: out << "\n"; Chris@45: out << "\n"; Chris@45: Chris@226: if (asTemplate) { Chris@226: m_document->toXmlAsTemplate(out, "", ""); Chris@226: } else { Chris@226: m_document->toXml(out, "", ""); Chris@226: } Chris@45: Chris@45: out << "\n"; Chris@45: Chris@45: out << QString(" \n") Chris@595: .arg(width()).arg(height()); Chris@45: Chris@45: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@45: Chris@595: Pane *pane = m_paneStack->getPane(i); Chris@595: Chris@595: if (pane) { Chris@45: pane->toXml(out, indent); Chris@595: } Chris@45: } Chris@45: Chris@45: out << "\n"; Chris@45: Chris@45: m_viewManager->getSelection().toXml(out); Chris@45: Chris@45: out << "\n"; Chris@45: } Chris@45: Chris@45: Pane * Chris@45: MainWindowBase::addPaneToStack() Chris@45: { Chris@342: cerr << "MainWindowBase::addPaneToStack()" << endl; Chris@45: AddPaneCommand *command = new AddPaneCommand(this); Chris@45: CommandHistory::getInstance()->addCommand(command); Chris@57: Pane *pane = command->getPane(); Chris@57: return pane; Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::zoomIn() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (currentPane) currentPane->zoom(true); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::zoomOut() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (currentPane) currentPane->zoom(false); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::zoomToFit() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (!currentPane) return; Chris@45: Chris@684: auto model = getMainModel(); Chris@45: if (!model) return; Chris@45: Chris@434: sv_frame_t start = model->getStartFrame(); Chris@434: sv_frame_t end = model->getEndFrame(); Chris@60: if (m_playSource) end = std::max(end, m_playSource->getPlayEndFrame()); Chris@366: int pixels = currentPane->width(); Chris@366: Chris@366: int sw = currentPane->getVerticalScaleWidth(); Chris@45: if (pixels > sw * 2) pixels -= sw * 2; Chris@45: else pixels = 1; Chris@45: if (pixels > 4) pixels -= 4; Chris@45: Chris@624: ZoomLevel zoomLevel = ZoomLevel::fromRatio(pixels, end - start); Chris@45: currentPane->setZoomLevel(zoomLevel); Chris@45: currentPane->setCentreFrame((start + end) / 2); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::zoomDefault() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@302: QSettings settings; Chris@302: settings.beginGroup("MainWindow"); Chris@302: int zoom = settings.value("zoom-default", 1024).toInt(); Chris@302: settings.endGroup(); Chris@624: if (currentPane) { Chris@624: currentPane->setZoomLevel(ZoomLevel(ZoomLevel::FramesPerPixel, zoom)); Chris@624: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::scrollLeft() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (currentPane) currentPane->scroll(false, false); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::jumpLeft() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (currentPane) currentPane->scroll(false, true); Chris@45: } Chris@45: Chris@45: void Chris@162: MainWindowBase::peekLeft() Chris@162: { Chris@162: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@162: if (currentPane) currentPane->scroll(false, false, false); Chris@162: } Chris@162: Chris@162: void Chris@45: MainWindowBase::scrollRight() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (currentPane) currentPane->scroll(true, false); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::jumpRight() Chris@45: { Chris@45: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@45: if (currentPane) currentPane->scroll(true, true); Chris@45: } Chris@45: Chris@45: void Chris@162: MainWindowBase::peekRight() Chris@162: { Chris@162: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@162: if (currentPane) currentPane->scroll(true, false, false); Chris@162: } Chris@162: Chris@162: void Chris@45: MainWindowBase::showNoOverlays() Chris@45: { Chris@45: m_viewManager->setOverlayMode(ViewManager::NoOverlays); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::showMinimalOverlays() Chris@45: { Chris@335: m_viewManager->setOverlayMode(ViewManager::StandardOverlays); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::showAllOverlays() Chris@45: { Chris@45: m_viewManager->setOverlayMode(ViewManager::AllOverlays); Chris@45: } Chris@45: Chris@45: void Chris@577: MainWindowBase::findTimeRulerLayer() Chris@577: { Chris@577: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@577: Pane *pane = m_paneStack->getPane(i); Chris@577: if (!pane) continue; Chris@577: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@577: Layer *layer = pane->getLayer(j); Chris@577: if (!dynamic_cast(layer)) continue; Chris@577: m_timeRulerLayer = layer; Chris@577: return; Chris@577: } Chris@577: } Chris@577: if (m_timeRulerLayer) { Chris@577: SVCERR << "WARNING: Time ruler layer was not reset to 0 before session template loaded?" << endl; Chris@577: delete m_timeRulerLayer; Chris@636: m_timeRulerLayer = nullptr; Chris@577: } Chris@577: } Chris@577: Chris@577: void Chris@211: MainWindowBase::toggleTimeRulers() Chris@211: { Chris@211: bool haveRulers = false; Chris@211: bool someHidden = false; Chris@211: Chris@211: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@211: Chris@211: Pane *pane = m_paneStack->getPane(i); Chris@211: if (!pane) continue; Chris@211: Chris@211: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@211: Chris@211: Layer *layer = pane->getLayer(j); Chris@211: if (!dynamic_cast(layer)) continue; Chris@211: Chris@211: haveRulers = true; Chris@211: if (layer->isLayerDormant(pane)) someHidden = true; Chris@211: } Chris@211: } Chris@211: Chris@211: if (haveRulers) { Chris@211: Chris@211: bool show = someHidden; Chris@211: Chris@211: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@211: Chris@211: Pane *pane = m_paneStack->getPane(i); Chris@211: if (!pane) continue; Chris@211: Chris@211: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@211: Chris@211: Layer *layer = pane->getLayer(j); Chris@211: if (!dynamic_cast(layer)) continue; Chris@211: Chris@211: layer->showLayer(pane, show); Chris@211: } Chris@211: } Chris@211: } Chris@211: } Chris@211: Chris@211: void Chris@45: MainWindowBase::toggleZoomWheels() Chris@45: { Chris@45: if (m_viewManager->getZoomWheelsEnabled()) { Chris@45: m_viewManager->setZoomWheelsEnabled(false); Chris@45: } else { Chris@45: m_viewManager->setZoomWheelsEnabled(true); Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::togglePropertyBoxes() Chris@45: { Chris@45: if (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks) { Chris@45: if (Preferences::getInstance()->getPropertyBoxLayout() == Chris@45: Preferences::VerticallyStacked) { Chris@45: m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); Chris@45: } else { Chris@45: m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); Chris@45: } Chris@45: } else { Chris@45: m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks); Chris@45: } Chris@45: } Chris@45: Chris@378: QLabel * Chris@378: MainWindowBase::getStatusLabel() const Chris@378: { Chris@378: if (!m_statusLabel) { Chris@378: m_statusLabel = new QLabel(); Chris@378: statusBar()->addWidget(m_statusLabel, 1); Chris@378: } Chris@379: Chris@379: QList frames = statusBar()->findChildren(); Chris@379: foreach (QFrame *f, frames) { Chris@379: f->setFrameStyle(QFrame::NoFrame); Chris@379: } Chris@379: Chris@378: return m_statusLabel; Chris@378: } Chris@378: Chris@45: void Chris@45: MainWindowBase::toggleStatusBar() Chris@45: { Chris@45: QSettings settings; Chris@45: settings.beginGroup("MainWindow"); Chris@45: bool sb = settings.value("showstatusbar", true).toBool(); Chris@45: Chris@45: if (sb) { Chris@45: statusBar()->hide(); Chris@45: } else { Chris@45: statusBar()->show(); Chris@45: } Chris@45: Chris@45: settings.setValue("showstatusbar", !sb); Chris@45: Chris@45: settings.endGroup(); Chris@45: } Chris@45: Chris@45: void Chris@256: MainWindowBase::toggleCentreLine() Chris@256: { Chris@256: if (m_viewManager->shouldShowCentreLine()) { Chris@256: m_viewManager->setShowCentreLine(false); Chris@256: } else { Chris@256: m_viewManager->setShowCentreLine(true); Chris@256: } Chris@256: } Chris@256: Chris@256: void Chris@45: MainWindowBase::preferenceChanged(PropertyContainer::PropertyName name) Chris@45: { Chris@45: if (name == "Property Box Layout") { Chris@45: if (m_paneStack->getLayoutStyle() != PaneStack::NoPropertyStacks) { Chris@45: if (Preferences::getInstance()->getPropertyBoxLayout() == Chris@45: Preferences::VerticallyStacked) { Chris@45: m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); Chris@45: } else { Chris@45: m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); Chris@45: } Chris@45: } Chris@45: } else if (name == "Background Mode" && m_viewManager) { Chris@45: Preferences::BackgroundMode mode = Chris@45: Preferences::getInstance()->getBackgroundMode(); Chris@45: if (mode == Preferences::BackgroundFromTheme) { Chris@45: m_viewManager->setGlobalDarkBackground(m_initialDarkBackground); Chris@45: } else if (mode == Preferences::DarkBackground) { Chris@45: m_viewManager->setGlobalDarkBackground(true); Chris@45: } else { Chris@45: m_viewManager->setGlobalDarkBackground(false); Chris@45: } Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::play() Chris@45: { Chris@516: if ((m_recordTarget && m_recordTarget->isRecording()) || Chris@516: (m_playSource && m_playSource->isPlaying())) { Chris@45: stop(); Chris@479: QAction *action = qobject_cast(sender()); Chris@479: if (action) action->setChecked(false); Chris@45: } else { Chris@487: if (m_audioIO) m_audioIO->resume(); Chris@509: else if (m_playTarget) m_playTarget->resume(); Chris@45: playbackFrameChanged(m_viewManager->getPlaybackFrame()); Chris@595: m_playSource->play(m_viewManager->getPlaybackFrame()); Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@477: MainWindowBase::record() Chris@477: { Chris@586: QAction *action = qobject_cast(sender()); Chris@586: Chris@478: if (!(m_soundOptions & WithAudioInput)) { Chris@586: if (action) action->setChecked(false); Chris@478: return; Chris@478: } Chris@478: Chris@477: if (!m_recordTarget) { Chris@586: if (action) action->setChecked(false); Chris@477: return; Chris@477: } Chris@477: Chris@478: if (!m_audioIO) { Chris@611: SVDEBUG << "MainWindowBase::record: about to create audio IO" << endl; Chris@478: createAudioIO(); Chris@478: } Chris@492: Chris@492: if (!m_audioIO) { Chris@586: if (!m_playTarget) { Chris@586: // Don't need to report this, createAudioIO should have Chris@586: if (action) action->setChecked(false); Chris@586: return; Chris@586: } else { Chris@586: // Need to report this: if the play target exists instead Chris@586: // of the audio IO, then that means we failed to open a Chris@586: // capture device. The record control should be disabled Chris@586: // in that situation, so if it happens here, that must Chris@586: // mean this is the first time we ever tried to open the Chris@586: // audio device, hence the need to report the problem here Chris@586: QMessageBox::critical Chris@586: (this, tr("No record device available"), Chris@586: tr("No record device available

Failed to find or open an audio device for recording. Only playback will be available.

")); Chris@586: if (action) action->setChecked(false); Chris@586: updateMenuStates(); Chris@586: return; Chris@586: } Chris@492: } Chris@478: Chris@477: if (m_recordTarget->isRecording()) { Chris@492: stop(); Chris@477: return; Chris@477: } Chris@490: Chris@483: if (m_audioRecordMode == RecordReplaceSession) { Chris@490: if (!checkSaveModified()) { Chris@490: if (action) action->setChecked(false); Chris@490: return; Chris@490: } Chris@483: } Chris@487: Chris@557: if (m_viewManager) m_viewManager->setGlobalCentreFrame(0); Chris@557: Chris@611: SVCERR << "MainWindowBase::record: about to resume" << endl; Chris@492: m_audioIO->resume(); Chris@509: Chris@684: WritableWaveFileModel *modelPtr = m_recordTarget->startRecording(); Chris@684: if (!modelPtr) { Chris@586: SVCERR << "ERROR: MainWindowBase::record: Recording failed" << endl; Chris@586: QMessageBox::critical Chris@586: (this, tr("Recording failed"), Chris@586: tr("Recording failed

Failed to switch to record mode (some internal problem?)

")); Chris@490: if (action) action->setChecked(false); Chris@477: return; Chris@477: } Chris@477: Chris@684: if (!modelPtr->isOK()) { Chris@611: SVCERR << "MainWindowBase::record: Model not OK, stopping and suspending" << endl; Chris@477: m_recordTarget->stopRecording(); Chris@492: m_audioIO->suspend(); Chris@586: if (action) action->setChecked(false); Chris@684: delete modelPtr; Chris@477: return; Chris@477: } Chris@611: Chris@611: SVCERR << "MainWindowBase::record: Model is OK, continuing..." << endl; Chris@684: Chris@684: QString location = modelPtr->getLocation(); Chris@487: Chris@687: auto modelId = ModelById::add(std::shared_ptr(modelPtr)); Chris@483: Chris@483: if (m_audioRecordMode == RecordReplaceSession || !getMainModel()) { Chris@478: Chris@479: //!!! duplication with openAudio here Chris@479: Chris@479: QString templateName = getDefaultSessionTemplate(); Chris@479: bool loadedTemplate = false; Chris@479: Chris@479: if (templateName != "") { Chris@479: FileOpenStatus tplStatus = openSessionTemplate(templateName); Chris@479: if (tplStatus == FileOpenCancelled) { Chris@611: SVCERR << "MainWindowBase::record: Session template open cancelled, stopping and suspending" << endl; Chris@490: m_recordTarget->stopRecording(); Chris@492: m_audioIO->suspend(); Chris@684: ModelById::release(modelId); Chris@479: return; Chris@479: } Chris@479: if (tplStatus != FileOpenFailed) { Chris@479: loadedTemplate = true; Chris@479: } Chris@479: } Chris@479: Chris@479: if (!loadedTemplate) { Chris@479: closeSession(); Chris@479: createDocument(); Chris@479: } Chris@479: Chris@684: ModelId prevMain = getMainModelId(); Chris@684: if (!prevMain.isNone()) { Chris@479: m_playSource->removeModel(prevMain); Chris@479: } Chris@479: Chris@684: m_document->setMainModel(modelId); Chris@478: setupMenus(); Chris@577: findTimeRulerLayer(); Chris@478: Chris@684: m_originalLocation = location; Chris@669: Chris@595: if (loadedTemplate || (m_sessionFile == "")) { Chris@595: CommandHistory::getInstance()->clear(); Chris@595: CommandHistory::getInstance()->documentSaved(); Chris@595: } Chris@479: Chris@669: m_documentModified = false; Chris@669: updateWindowTitle(); Chris@669: Chris@478: } else { Chris@478: Chris@478: CommandHistory::getInstance()->startCompoundOperation Chris@478: (tr("Import Recorded Audio"), true); Chris@478: Chris@691: m_document->addNonDerivedModel(modelId); Chris@478: Chris@478: AddPaneCommand *command = new AddPaneCommand(this); Chris@478: CommandHistory::getInstance()->addCommand(command); Chris@478: Chris@478: Pane *pane = command->getPane(); Chris@478: Chris@478: if (m_timeRulerLayer) { Chris@478: m_document->addLayerToView(pane, m_timeRulerLayer); Chris@478: } Chris@478: Chris@684: Layer *newLayer = m_document->createImportedLayer(modelId); Chris@478: Chris@478: if (newLayer) { Chris@478: m_document->addLayerToView(pane, newLayer); Chris@478: } Chris@595: Chris@478: CommandHistory::getInstance()->endCompoundOperation(); Chris@477: } Chris@479: Chris@479: updateMenuStates(); Chris@684: m_recentFiles.addFile(location); Chris@479: currentPaneChanged(m_paneStack->getCurrentPane()); Chris@611: Chris@496: emit audioFileLoaded(); Chris@477: } Chris@477: Chris@477: void Chris@45: MainWindowBase::ffwd() Chris@45: { Chris@45: if (!getMainModel()) return; Chris@45: Chris@435: sv_frame_t frame = m_viewManager->getPlaybackFrame(); Chris@45: ++frame; Chris@45: Chris@85: Pane *pane = m_paneStack->getCurrentPane(); Chris@45: Layer *layer = getSnapLayer(); Chris@435: sv_samplerate_t sr = getMainModel()->getSampleRate(); Chris@45: Chris@45: if (!layer) { Chris@45: Chris@45: frame = RealTime::realTime2Frame Chris@357: (RealTime::frame2RealTime(frame, sr) + m_defaultFfwdRwdStep, sr); Chris@435: if (frame > getMainModel()->getEndFrame()) { Chris@45: frame = getMainModel()->getEndFrame(); Chris@45: } Chris@45: Chris@45: } else { Chris@45: Chris@366: int resolution = 0; Chris@166: if (pane) frame = pane->alignFromReference(frame); Chris@85: if (layer->snapToFeatureFrame(m_paneStack->getCurrentPane(), Chris@85: frame, resolution, Layer::SnapRight)) { Chris@85: if (pane) frame = pane->alignToReference(frame); Chris@85: } else { Chris@45: frame = getMainModel()->getEndFrame(); Chris@45: } Chris@45: } Chris@45: Chris@45: if (frame < 0) frame = 0; Chris@45: Chris@45: if (m_viewManager->getPlaySelectionMode()) { Chris@435: frame = m_viewManager->constrainFrameToSelection(frame); Chris@45: } Chris@45: Chris@45: m_viewManager->setPlaybackFrame(frame); Chris@166: Chris@435: if (frame == getMainModel()->getEndFrame() && Chris@166: m_playSource && Chris@166: m_playSource->isPlaying() && Chris@166: !m_viewManager->getPlayLoopMode()) { Chris@166: stop(); Chris@166: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::ffwdEnd() Chris@45: { Chris@45: if (!getMainModel()) return; Chris@45: Chris@139: if (m_playSource && Chris@139: m_playSource->isPlaying() && Chris@139: !m_viewManager->getPlayLoopMode()) { Chris@139: stop(); Chris@139: } Chris@139: Chris@435: sv_frame_t frame = getMainModel()->getEndFrame(); Chris@45: Chris@45: if (m_viewManager->getPlaySelectionMode()) { Chris@45: frame = m_viewManager->constrainFrameToSelection(frame); Chris@45: } Chris@45: Chris@45: m_viewManager->setPlaybackFrame(frame); Chris@45: } Chris@45: Chris@45: void Chris@166: MainWindowBase::ffwdSimilar() Chris@166: { Chris@166: if (!getMainModel()) return; Chris@166: Chris@166: Layer *layer = getSnapLayer(); Chris@166: if (!layer) { ffwd(); return; } Chris@166: Chris@166: Pane *pane = m_paneStack->getCurrentPane(); Chris@435: sv_frame_t frame = m_viewManager->getPlaybackFrame(); Chris@166: Chris@366: int resolution = 0; Chris@166: if (pane) frame = pane->alignFromReference(frame); Chris@166: if (layer->snapToSimilarFeature(m_paneStack->getCurrentPane(), Chris@166: frame, resolution, Layer::SnapRight)) { Chris@166: if (pane) frame = pane->alignToReference(frame); Chris@166: } else { Chris@166: frame = getMainModel()->getEndFrame(); Chris@166: } Chris@166: Chris@166: if (frame < 0) frame = 0; Chris@166: Chris@166: if (m_viewManager->getPlaySelectionMode()) { Chris@435: frame = m_viewManager->constrainFrameToSelection(frame); Chris@166: } Chris@166: Chris@166: m_viewManager->setPlaybackFrame(frame); Chris@166: Chris@435: if (frame == getMainModel()->getEndFrame() && Chris@166: m_playSource && Chris@166: m_playSource->isPlaying() && Chris@166: !m_viewManager->getPlayLoopMode()) { Chris@166: stop(); Chris@166: } Chris@166: } Chris@166: Chris@166: void Chris@45: MainWindowBase::rewind() Chris@45: { Chris@45: if (!getMainModel()) return; Chris@45: Chris@435: sv_frame_t frame = m_viewManager->getPlaybackFrame(); Chris@45: if (frame > 0) --frame; Chris@45: Chris@85: Pane *pane = m_paneStack->getCurrentPane(); Chris@45: Layer *layer = getSnapLayer(); Chris@435: sv_samplerate_t sr = getMainModel()->getSampleRate(); Chris@45: Chris@45: // when rewinding during playback, we want to allow a period Chris@45: // following a rewind target point at which the rewind will go to Chris@45: // the prior point instead of the immediately neighbouring one Chris@45: if (m_playSource && m_playSource->isPlaying()) { Chris@45: RealTime ct = RealTime::frame2RealTime(frame, sr); Chris@357: ct = ct - RealTime::fromSeconds(0.15); Chris@45: if (ct < RealTime::zeroTime) ct = RealTime::zeroTime; Chris@45: frame = RealTime::realTime2Frame(ct, sr); Chris@45: } Chris@45: Chris@45: if (!layer) { Chris@45: Chris@45: frame = RealTime::realTime2Frame Chris@357: (RealTime::frame2RealTime(frame, sr) - m_defaultFfwdRwdStep, sr); Chris@435: if (frame < getMainModel()->getStartFrame()) { Chris@45: frame = getMainModel()->getStartFrame(); Chris@45: } Chris@45: Chris@45: } else { Chris@45: Chris@366: int resolution = 0; Chris@166: if (pane) frame = pane->alignFromReference(frame); Chris@85: if (layer->snapToFeatureFrame(m_paneStack->getCurrentPane(), Chris@85: frame, resolution, Layer::SnapLeft)) { Chris@85: if (pane) frame = pane->alignToReference(frame); Chris@85: } else { Chris@45: frame = getMainModel()->getStartFrame(); Chris@45: } Chris@45: } Chris@45: Chris@45: if (frame < 0) frame = 0; Chris@45: Chris@45: if (m_viewManager->getPlaySelectionMode()) { Chris@435: frame = m_viewManager->constrainFrameToSelection(frame); Chris@45: } Chris@45: Chris@45: m_viewManager->setPlaybackFrame(frame); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::rewindStart() Chris@45: { Chris@45: if (!getMainModel()) return; Chris@45: Chris@435: sv_frame_t frame = getMainModel()->getStartFrame(); Chris@45: Chris@45: if (m_viewManager->getPlaySelectionMode()) { Chris@45: frame = m_viewManager->constrainFrameToSelection(frame); Chris@45: } Chris@45: Chris@45: m_viewManager->setPlaybackFrame(frame); Chris@45: } Chris@45: Chris@166: void Chris@166: MainWindowBase::rewindSimilar() Chris@166: { Chris@166: if (!getMainModel()) return; Chris@166: Chris@166: Layer *layer = getSnapLayer(); Chris@166: if (!layer) { rewind(); return; } Chris@166: Chris@166: Pane *pane = m_paneStack->getCurrentPane(); Chris@435: sv_frame_t frame = m_viewManager->getPlaybackFrame(); Chris@166: Chris@366: int resolution = 0; Chris@166: if (pane) frame = pane->alignFromReference(frame); Chris@166: if (layer->snapToSimilarFeature(m_paneStack->getCurrentPane(), Chris@166: frame, resolution, Layer::SnapLeft)) { Chris@166: if (pane) frame = pane->alignToReference(frame); Chris@166: } else { Chris@166: frame = getMainModel()->getStartFrame(); Chris@166: } Chris@166: Chris@166: if (frame < 0) frame = 0; Chris@166: Chris@166: if (m_viewManager->getPlaySelectionMode()) { Chris@435: frame = m_viewManager->constrainFrameToSelection(frame); Chris@166: } Chris@166: Chris@166: m_viewManager->setPlaybackFrame(frame); Chris@166: } Chris@166: Chris@45: Layer * Chris@45: MainWindowBase::getSnapLayer() const Chris@45: { Chris@45: Pane *pane = m_paneStack->getCurrentPane(); Chris@636: if (!pane) return nullptr; Chris@45: Chris@45: Layer *layer = pane->getSelectedLayer(); Chris@45: Chris@45: if (!dynamic_cast(layer) && Chris@45: !dynamic_cast(layer) && Chris@194: !dynamic_cast(layer) && Chris@45: !dynamic_cast(layer)) { Chris@45: Chris@636: layer = nullptr; Chris@45: Chris@45: for (int i = pane->getLayerCount(); i > 0; --i) { Chris@45: Layer *l = pane->getLayer(i-1); Chris@45: if (dynamic_cast(l)) { Chris@45: layer = l; Chris@45: break; Chris@45: } Chris@45: } Chris@45: } Chris@45: Chris@45: return layer; Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::stop() Chris@45: { Chris@516: if (m_recordTarget && Chris@516: m_recordTarget->isRecording()) { Chris@477: m_recordTarget->stopRecording(); Chris@477: } Chris@516: Chris@516: if (!m_playSource) return; Chris@516: Chris@45: m_playSource->stop(); Chris@45: Chris@611: SVCERR << "MainWindowBase::stop: suspending" << endl; Chris@611: Chris@487: if (m_audioIO) m_audioIO->suspend(); Chris@509: else if (m_playTarget) m_playTarget->suspend(); Chris@487: Chris@45: if (m_paneStack && m_paneStack->getCurrentPane()) { Chris@45: updateVisibleRangeDisplay(m_paneStack->getCurrentPane()); Chris@45: } else { Chris@45: m_myStatusMessage = ""; Chris@378: getStatusLabel()->setText(""); Chris@45: } Chris@45: } Chris@45: Chris@45: MainWindowBase::AddPaneCommand::AddPaneCommand(MainWindowBase *mw) : Chris@45: m_mw(mw), Chris@636: m_pane(nullptr), Chris@636: m_prevCurrentPane(nullptr), Chris@45: m_added(false) Chris@45: { Chris@45: } Chris@45: Chris@45: MainWindowBase::AddPaneCommand::~AddPaneCommand() Chris@45: { Chris@45: if (m_pane && !m_added) { Chris@595: m_mw->m_paneStack->deletePane(m_pane); Chris@45: } Chris@45: } Chris@45: Chris@45: QString Chris@45: MainWindowBase::AddPaneCommand::getName() const Chris@45: { Chris@45: return tr("Add Pane"); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::AddPaneCommand::execute() Chris@45: { Chris@45: if (!m_pane) { Chris@595: m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); Chris@595: m_pane = m_mw->m_paneStack->addPane(); Chris@45: Chris@45: connect(m_pane, SIGNAL(contextHelpChanged(const QString &)), Chris@45: m_mw, SLOT(contextHelpChanged(const QString &))); Chris@45: } else { Chris@595: m_mw->m_paneStack->showPane(m_pane); Chris@45: } Chris@45: Chris@45: m_mw->m_paneStack->setCurrentPane(m_pane); Chris@45: m_added = true; Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::AddPaneCommand::unexecute() Chris@45: { Chris@45: m_mw->m_paneStack->hidePane(m_pane); Chris@45: m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); Chris@45: m_added = false; Chris@45: } Chris@45: Chris@45: MainWindowBase::RemovePaneCommand::RemovePaneCommand(MainWindowBase *mw, Pane *pane) : Chris@45: m_mw(mw), Chris@45: m_pane(pane), Chris@636: m_prevCurrentPane(nullptr), Chris@45: m_added(true) Chris@45: { Chris@45: } Chris@45: Chris@45: MainWindowBase::RemovePaneCommand::~RemovePaneCommand() Chris@45: { Chris@45: if (m_pane && !m_added) { Chris@595: m_mw->m_paneStack->deletePane(m_pane); Chris@45: } Chris@45: } Chris@45: Chris@45: QString Chris@45: MainWindowBase::RemovePaneCommand::getName() const Chris@45: { Chris@45: return tr("Remove Pane"); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::RemovePaneCommand::execute() Chris@45: { Chris@45: m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); Chris@45: m_mw->m_paneStack->hidePane(m_pane); Chris@45: m_added = false; Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::RemovePaneCommand::unexecute() Chris@45: { Chris@45: m_mw->m_paneStack->showPane(m_pane); Chris@45: m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); Chris@45: m_added = true; Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::deleteCurrentPane() Chris@45: { Chris@45: CommandHistory::getInstance()->startCompoundOperation Chris@595: (tr("Delete Pane"), true); Chris@45: Chris@45: Pane *pane = m_paneStack->getCurrentPane(); Chris@45: if (pane) { Chris@595: while (pane->getLayerCount() > 0) { Chris@595: Layer *layer = pane->getLayer(0); Chris@595: if (layer) { Chris@595: m_document->removeLayerFromView(pane, layer); Chris@595: } else { Chris@595: break; Chris@595: } Chris@595: } Chris@595: Chris@595: RemovePaneCommand *command = new RemovePaneCommand(this, pane); Chris@595: CommandHistory::getInstance()->addCommand(command); Chris@45: } Chris@45: Chris@45: CommandHistory::getInstance()->endCompoundOperation(); Chris@45: Chris@45: updateMenuStates(); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::deleteCurrentLayer() Chris@45: { Chris@45: Pane *pane = m_paneStack->getCurrentPane(); Chris@45: if (pane) { Chris@595: Layer *layer = pane->getSelectedLayer(); Chris@595: if (layer) { Chris@595: m_document->removeLayerFromView(pane, layer); Chris@595: } Chris@45: } Chris@45: updateMenuStates(); Chris@45: } Chris@45: Chris@45: void Chris@123: MainWindowBase::editCurrentLayer() Chris@123: { Chris@636: Layer *layer = nullptr; Chris@123: Pane *pane = m_paneStack->getCurrentPane(); Chris@123: if (pane) layer = pane->getSelectedLayer(); Chris@123: if (!layer) return; Chris@123: Chris@684: auto tabular = ModelById::getAs(layer->getModel()); Chris@124: if (!tabular) { Chris@124: //!!! how to prevent this function from being active if not Chris@124: //appropriate model type? or will we ultimately support Chris@124: //tabular display for all editable models? Chris@233: SVDEBUG << "NOTE: Not a tabular model" << endl; Chris@124: return; Chris@124: } Chris@124: Chris@123: if (m_layerDataDialogMap.find(layer) != m_layerDataDialogMap.end()) { Chris@126: if (!m_layerDataDialogMap[layer].isNull()) { Chris@126: m_layerDataDialogMap[layer]->show(); Chris@126: m_layerDataDialogMap[layer]->raise(); Chris@126: return; Chris@126: } Chris@123: } Chris@123: Chris@125: QString title = layer->getLayerPresentationName(); Chris@125: Chris@684: ModelDataTableDialog *dialog = new ModelDataTableDialog Chris@684: (layer->getModel(), title, this); Chris@128: dialog->setAttribute(Qt::WA_DeleteOnClose); Chris@128: Chris@128: connectLayerEditDialog(dialog); Chris@123: Chris@128: m_layerDataDialogMap[layer] = dialog; Chris@128: m_viewDataDialogMap[pane].insert(dialog); Chris@128: Chris@128: dialog->show(); Chris@128: } Chris@128: Chris@128: void Chris@128: MainWindowBase::connectLayerEditDialog(ModelDataTableDialog *dialog) Chris@128: { Chris@123: connect(m_viewManager, Chris@435: SIGNAL(globalCentreFrameChanged(sv_frame_t)), Chris@123: dialog, Chris@435: SLOT(userScrolledToFrame(sv_frame_t))); Chris@127: Chris@127: connect(m_viewManager, Chris@435: SIGNAL(playbackFrameChanged(sv_frame_t)), Chris@127: dialog, Chris@435: SLOT(playbackScrolledToFrame(sv_frame_t))); Chris@127: Chris@123: connect(dialog, Chris@435: SIGNAL(scrollToFrame(sv_frame_t)), Chris@123: m_viewManager, Chris@435: SLOT(setGlobalCentreFrame(sv_frame_t))); Chris@129: Chris@129: connect(dialog, Chris@435: SIGNAL(scrollToFrame(sv_frame_t)), Chris@129: m_viewManager, Chris@435: SLOT(setPlaybackFrame(sv_frame_t))); Chris@128: } Chris@123: Chris@123: void Chris@73: MainWindowBase::previousPane() Chris@73: { Chris@73: if (!m_paneStack) return; Chris@73: Chris@73: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@73: if (!currentPane) return; Chris@73: Chris@73: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@73: if (m_paneStack->getPane(i) == currentPane) { Chris@73: if (i == 0) return; Chris@73: m_paneStack->setCurrentPane(m_paneStack->getPane(i-1)); Chris@73: updateMenuStates(); Chris@73: return; Chris@73: } Chris@73: } Chris@73: } Chris@73: Chris@73: void Chris@73: MainWindowBase::nextPane() Chris@73: { Chris@73: if (!m_paneStack) return; Chris@73: Chris@73: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@73: if (!currentPane) return; Chris@73: Chris@73: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@73: if (m_paneStack->getPane(i) == currentPane) { Chris@73: if (i == m_paneStack->getPaneCount()-1) return; Chris@73: m_paneStack->setCurrentPane(m_paneStack->getPane(i+1)); Chris@73: updateMenuStates(); Chris@73: return; Chris@73: } Chris@73: } Chris@73: } Chris@73: Chris@73: void Chris@73: MainWindowBase::previousLayer() Chris@73: { Chris@73: if (!m_paneStack) return; Chris@73: Chris@73: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@73: if (!currentPane) return; Chris@73: Chris@403: int count = currentPane->getLayerCount(); Chris@403: if (count == 0) return; Chris@403: Chris@73: Layer *currentLayer = currentPane->getSelectedLayer(); Chris@403: Chris@403: if (!currentLayer) { Chris@403: // The pane itself is current Chris@403: m_paneStack->setCurrentLayer Chris@403: (currentPane, currentPane->getFixedOrderLayer(count-1)); Chris@403: } else { Chris@403: for (int i = 0; i < count; ++i) { Chris@403: if (currentPane->getFixedOrderLayer(i) == currentLayer) { Chris@403: if (i == 0) { Chris@403: m_paneStack->setCurrentLayer Chris@636: (currentPane, nullptr); // pane Chris@403: } else { Chris@403: m_paneStack->setCurrentLayer Chris@403: (currentPane, currentPane->getFixedOrderLayer(i-1)); Chris@403: } Chris@403: break; Chris@403: } Chris@73: } Chris@73: } Chris@403: Chris@403: updateMenuStates(); Chris@73: } Chris@73: Chris@73: void Chris@73: MainWindowBase::nextLayer() Chris@73: { Chris@73: if (!m_paneStack) return; Chris@73: Chris@73: Pane *currentPane = m_paneStack->getCurrentPane(); Chris@73: if (!currentPane) return; Chris@73: Chris@403: int count = currentPane->getLayerCount(); Chris@403: if (count == 0) return; Chris@403: Chris@73: Layer *currentLayer = currentPane->getSelectedLayer(); Chris@403: Chris@403: if (!currentLayer) { Chris@403: // The pane itself is current Chris@403: m_paneStack->setCurrentLayer Chris@403: (currentPane, currentPane->getFixedOrderLayer(0)); Chris@403: } else { Chris@403: for (int i = 0; i < count; ++i) { Chris@403: if (currentPane->getFixedOrderLayer(i) == currentLayer) { Chris@403: if (i == currentPane->getLayerCount()-1) { Chris@403: m_paneStack->setCurrentLayer Chris@636: (currentPane, nullptr); // pane Chris@403: } else { Chris@403: m_paneStack->setCurrentLayer Chris@403: (currentPane, currentPane->getFixedOrderLayer(i+1)); Chris@403: } Chris@403: break; Chris@403: } Chris@73: } Chris@73: } Chris@403: Chris@403: updateMenuStates(); Chris@73: } Chris@73: Chris@73: void Chris@435: MainWindowBase::playbackFrameChanged(sv_frame_t frame) Chris@45: { Chris@45: if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; Chris@45: Chris@187: updatePositionStatusDisplays(); Chris@187: Chris@45: RealTime now = RealTime::frame2RealTime Chris@45: (frame, getMainModel()->getSampleRate()); Chris@45: Chris@45: if (now.sec == m_lastPlayStatusSec) return; Chris@45: Chris@45: RealTime then = RealTime::frame2RealTime Chris@45: (m_playSource->getPlayEndFrame(), getMainModel()->getSampleRate()); Chris@45: Chris@45: QString nowStr; Chris@45: QString thenStr; Chris@45: QString remainingStr; Chris@45: Chris@45: if (then.sec > 10) { Chris@45: nowStr = now.toSecText().c_str(); Chris@45: thenStr = then.toSecText().c_str(); Chris@45: remainingStr = (then - now).toSecText().c_str(); Chris@45: m_lastPlayStatusSec = now.sec; Chris@45: } else { Chris@45: nowStr = now.toText(true).c_str(); Chris@45: thenStr = then.toText(true).c_str(); Chris@45: remainingStr = (then - now).toText(true).c_str(); Chris@45: } Chris@45: Chris@45: m_myStatusMessage = tr("Playing: %1 of %2 (%3 remaining)") Chris@45: .arg(nowStr).arg(thenStr).arg(remainingStr); Chris@45: Chris@378: getStatusLabel()->setText(m_myStatusMessage); Chris@45: } Chris@45: Chris@45: void Chris@486: MainWindowBase::recordDurationChanged(sv_frame_t frame, sv_samplerate_t rate) Chris@486: { Chris@486: RealTime duration = RealTime::frame2RealTime(frame, rate); Chris@486: QString durStr = duration.toSecText().c_str(); Chris@486: Chris@486: m_myStatusMessage = tr("Recording: %1").arg(durStr); Chris@486: Chris@486: getStatusLabel()->setText(m_myStatusMessage); Chris@486: } Chris@486: Chris@486: void Chris@435: MainWindowBase::globalCentreFrameChanged(sv_frame_t ) Chris@45: { Chris@45: if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; Chris@636: Pane *p = nullptr; Chris@45: if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; Chris@45: if (!p->getFollowGlobalPan()) return; Chris@45: updateVisibleRangeDisplay(p); Chris@45: } Chris@45: Chris@45: void Chris@435: MainWindowBase::viewCentreFrameChanged(View *v, sv_frame_t frame) Chris@45: { Chris@233: // SVDEBUG << "MainWindowBase::viewCentreFrameChanged(" << v << "," << frame << ")" << endl; Chris@123: Chris@123: if (m_viewDataDialogMap.find(v) != m_viewDataDialogMap.end()) { Chris@123: for (DataDialogSet::iterator i = m_viewDataDialogMap[v].begin(); Chris@123: i != m_viewDataDialogMap[v].end(); ++i) { Chris@127: (*i)->userScrolledToFrame(frame); Chris@123: } Chris@123: } Chris@45: if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; Chris@636: Pane *p = nullptr; Chris@45: if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; Chris@45: if (v == p) updateVisibleRangeDisplay(p); Chris@45: } Chris@45: Chris@45: void Chris@624: MainWindowBase::viewZoomLevelChanged(View *v, ZoomLevel, bool ) Chris@45: { Chris@45: if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; Chris@636: Pane *p = nullptr; Chris@45: if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; Chris@45: if (v == p) updateVisibleRangeDisplay(p); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::layerAdded(Layer *) Chris@45: { Chris@233: // SVDEBUG << "MainWindowBase::layerAdded(" << layer << ")" << endl; Chris@45: updateMenuStates(); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::layerRemoved(Layer *) Chris@45: { Chris@233: // SVDEBUG << "MainWindowBase::layerRemoved(" << layer << ")" << endl; Chris@45: updateMenuStates(); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::layerAboutToBeDeleted(Layer *layer) Chris@45: { Chris@233: // SVDEBUG << "MainWindowBase::layerAboutToBeDeleted(" << layer << ")" << endl; Chris@123: Chris@128: removeLayerEditDialog(layer); Chris@123: Chris@47: if (m_timeRulerLayer && (layer == m_timeRulerLayer)) { Chris@595: // cerr << "(this is the time ruler layer)" << endl; Chris@636: m_timeRulerLayer = nullptr; Chris@45: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::layerInAView(Layer *layer, bool inAView) Chris@45: { Chris@233: // SVDEBUG << "MainWindowBase::layerInAView(" << layer << "," << inAView << ")" << endl; Chris@128: Chris@128: if (!inAView) removeLayerEditDialog(layer); Chris@45: Chris@45: // Check whether we need to add or remove model from play source Chris@684: ModelId modelId = layer->getModel(); Chris@684: if (!modelId.isNone()) { Chris@45: if (inAView) { Chris@684: m_playSource->addModel(modelId); Chris@45: } else { Chris@45: bool found = false; Chris@45: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@45: Pane *pane = m_paneStack->getPane(i); Chris@45: if (!pane) continue; Chris@45: for (int j = 0; j < pane->getLayerCount(); ++j) { Chris@45: Layer *pl = pane->getLayer(j); Chris@183: if (pl && Chris@183: !dynamic_cast(pl) && Chris@684: (pl->getModel() == modelId)) { Chris@45: found = true; Chris@45: break; Chris@45: } Chris@45: } Chris@45: if (found) break; Chris@45: } Chris@173: if (!found) { Chris@684: m_playSource->removeModel(modelId); Chris@173: } Chris@45: } Chris@45: } Chris@45: Chris@45: updateMenuStates(); Chris@45: } Chris@45: Chris@45: void Chris@128: MainWindowBase::removeLayerEditDialog(Layer *layer) Chris@128: { Chris@128: if (m_layerDataDialogMap.find(layer) != m_layerDataDialogMap.end()) { Chris@128: Chris@128: ModelDataTableDialog *dialog = m_layerDataDialogMap[layer]; Chris@128: Chris@128: for (ViewDataDialogMap::iterator vi = m_viewDataDialogMap.begin(); Chris@128: vi != m_viewDataDialogMap.end(); ++vi) { Chris@128: vi->second.erase(dialog); Chris@128: } Chris@128: Chris@128: m_layerDataDialogMap.erase(layer); Chris@128: delete dialog; Chris@128: } Chris@128: } Chris@128: Chris@128: void Chris@684: MainWindowBase::modelAdded(ModelId model) Chris@45: { Chris@233: // SVDEBUG << "MainWindowBase::modelAdded(" << model << ")" << endl; Chris@684: std::cerr << "\nAdding model " << model << " to playsource " << std::endl; Chris@45: m_playSource->addModel(model); Chris@45: } Chris@45: Chris@45: void Chris@684: MainWindowBase::mainModelChanged(ModelId modelId) Chris@45: { Chris@233: // SVDEBUG << "MainWindowBase::mainModelChanged(" << model << ")" << endl; Chris@45: updateDescriptionLabel(); Chris@684: auto model = ModelById::getAs(modelId); Chris@45: if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate()); Chris@475: if (model && !(m_playTarget || m_audioIO) && Chris@475: (m_soundOptions & WithAudioOutput)) { Chris@475: createAudioIO(); Chris@360: } Chris@45: } Chris@45: Chris@45: void Chris@55: MainWindowBase::paneDeleteButtonClicked(Pane *pane) Chris@55: { Chris@55: bool found = false; Chris@55: for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { Chris@55: if (m_paneStack->getPane(i) == pane) { Chris@55: found = true; Chris@55: break; Chris@55: } Chris@55: } Chris@55: if (!found) { Chris@233: SVDEBUG << "MainWindowBase::paneDeleteButtonClicked: Unknown pane " Chris@229: << pane << endl; Chris@55: return; Chris@55: } Chris@55: Chris@55: CommandHistory::getInstance()->startCompoundOperation Chris@595: (tr("Delete Pane"), true); Chris@55: Chris@55: while (pane->getLayerCount() > 0) { Chris@637: Layer *layer = pane->getLayer(pane->getLayerCount() - 1); Chris@55: if (layer) { Chris@55: m_document->removeLayerFromView(pane, layer); Chris@55: } else { Chris@55: break; Chris@55: } Chris@55: } Chris@55: Chris@55: RemovePaneCommand *command = new RemovePaneCommand(this, pane); Chris@55: CommandHistory::getInstance()->addCommand(command); Chris@55: Chris@55: CommandHistory::getInstance()->endCompoundOperation(); Chris@55: Chris@55: updateMenuStates(); Chris@55: } Chris@55: Chris@55: void Chris@684: MainWindowBase::alignmentComplete(ModelId alignmentModelId) Chris@429: { Chris@684: cerr << "MainWindowBase::alignmentComplete(" << alignmentModelId << ")" << endl; Chris@429: } Chris@429: Chris@429: void Chris@45: MainWindowBase::pollOSC() Chris@45: { Chris@45: if (!m_oscQueue || m_oscQueue->isEmpty()) return; Chris@233: SVDEBUG << "MainWindowBase::pollOSC: have " << m_oscQueue->getMessagesAvailable() << " messages" << endl; Chris@45: Chris@45: if (m_openingAudioFile) return; Chris@45: Chris@45: OSCMessage message = m_oscQueue->readMessage(); Chris@45: Chris@45: if (message.getTarget() != 0) { Chris@45: return; //!!! for now -- this class is target 0, others not handled yet Chris@45: } Chris@45: Chris@45: handleOSCMessage(message); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::inProgressSelectionChanged() Chris@45: { Chris@636: Pane *currentPane = nullptr; Chris@45: if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); justin@331: if (currentPane) { justin@331: //cerr << "JTEST: mouse event on selection pane" << endl; justin@331: updateVisibleRangeDisplay(currentPane); justin@331: } Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::contextHelpChanged(const QString &s) Chris@45: { Chris@378: QLabel *lab = getStatusLabel(); Chris@375: Chris@45: if (s == "" && m_myStatusMessage != "") { Chris@378: if (lab->text() != m_myStatusMessage) { Chris@378: lab->setText(m_myStatusMessage); Chris@375: } Chris@45: return; Chris@45: } Chris@375: Chris@378: lab->setText(s); Chris@45: } Chris@45: Chris@45: void Chris@45: MainWindowBase::openHelpUrl(QString url) Chris@45: { Chris@45: // This method mostly lifted from Qt Assistant source code Chris@45: Chris@45: QProcess *process = new QProcess(this); Chris@45: connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); Chris@45: Chris@45: QStringList args; Chris@45: Chris@45: #ifdef Q_OS_MAC Chris@45: args.append(url); Chris@45: process->start("open", args); Chris@45: #else Chris@45: #ifdef Q_OS_WIN32 Chris@599: std::string pfiles; Chris@599: (void)getEnvUtf8("ProgramFiles", pfiles); Chris@599: QString command = Chris@599: QString::fromStdString(pfiles) + Chris@599: QString("\\Internet Explorer\\IEXPLORE.EXE"); Chris@358: Chris@358: args.append(url); Chris@358: process->start(command, args); Chris@45: #else Chris@45: if (!qgetenv("KDE_FULL_SESSION").isEmpty()) { Chris@45: args.append("exec"); Chris@45: args.append(url); Chris@45: process->start("kfmclient", args); Chris@45: } else if (!qgetenv("BROWSER").isEmpty()) { Chris@45: args.append(url); Chris@45: process->start(qgetenv("BROWSER"), args); Chris@45: } else { Chris@45: args.append(url); Chris@45: process->start("firefox", args); Chris@45: } Chris@45: #endif Chris@45: #endif Chris@45: } Chris@45: Chris@483: void Chris@483: MainWindowBase::openLocalFolder(QString path) Chris@483: { Chris@483: QDir d(path); Chris@483: if (d.exists()) { Chris@483: QStringList args; Chris@483: QString path = d.canonicalPath(); Chris@483: #if defined Q_OS_WIN32 Chris@483: // Although the Win32 API is quite happy to have Chris@483: // forward slashes as directory separators, Windows Chris@483: // Explorer is not Chris@483: path = path.replace('/', '\\'); Chris@483: args << path; Chris@483: QProcess::execute("c:/windows/explorer.exe", args); Chris@483: #else Chris@483: args << path; Chris@605: QProcess process; Chris@605: QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); Chris@606: env.insert("LD_LIBRARY_PATH", ""); Chris@605: process.setProcessEnvironment(env); Chris@605: process.start( Chris@483: #if defined Q_OS_MAC Chris@483: "/usr/bin/open", Chris@483: #else Chris@483: "/usr/bin/xdg-open", Chris@483: #endif Chris@483: args); Chris@608: process.waitForFinished(); Chris@483: #endif Chris@483: } Chris@483: } Chris@483: