#include "MainWindow.hpp" #include #include #include #include #include #include #include #include #include #include "ADSREditor.hpp" #include "Common.hpp" #include "CurveEditor.hpp" #include "KeymapEditor.hpp" #include "LayersEditor.hpp" #include "MIDIReader.hpp" #include "NewSoundMacroDialog.hpp" #include "SampleEditor.hpp" #include "SongGroupEditor.hpp" #include "SoundGroupEditor.hpp" #include "SoundGroupEditor.hpp" #include "SoundMacroEditor.hpp" #include "StudioSetupWidget.hpp" #include #include #include #include #include MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) , m_navIt(m_navList.begin()) , m_treeDelegate(*this, this) , m_mainMessenger(this) , m_openDirectoryDialog(this) , m_openFileDialog(this) , m_newFileDialog(this) , m_undoStack(new QUndoStack(this)) , m_backgroundThread(this) { m_backgroundThread.start(); m_newFileDialog.setAcceptMode(QFileDialog::AcceptSave); m_newFileDialog.setFileMode(QFileDialog::AnyFile); m_newFileDialog.setOption(QFileDialog::ShowDirsOnly, false); m_openDirectoryDialog.setAcceptMode(QFileDialog::AcceptOpen); m_openDirectoryDialog.setFileMode(QFileDialog::Directory); m_openDirectoryDialog.setOption(QFileDialog::ShowDirsOnly, true); m_openFileDialog.setAcceptMode(QFileDialog::AcceptOpen); m_openFileDialog.setFileMode(QFileDialog::ExistingFile); m_openFileDialog.setOption(QFileDialog::ShowDirsOnly, false); m_ui.setupUi(this); m_ui.splitter->setCollapsible(1, false); QPalette palette = m_ui.projectOutlineFilter->palette(); palette.setColor(QPalette::Base, palette.color(QPalette::Window)); m_ui.projectOutlineFilter->setPalette(palette); m_ui.projectOutline->setItemDelegate(&m_treeDelegate); connect(m_ui.projectOutline, &QTreeView::activated, this, &MainWindow::outlineItemActivated); m_goBack = new QAction(m_ui.backButton->icon(), tr("Go Back"), this); m_goBack->setEnabled(false); connect(m_goBack, &QAction::triggered, this, &MainWindow::goBack); m_goBack->setShortcut(QKeySequence::Back); m_ui.backButton->setDefaultAction(m_goBack); m_goForward = new QAction(m_ui.forwardButton->icon(), tr("Go Forward"), this); m_goForward->setEnabled(false); connect(m_goForward, &QAction::triggered, this, &MainWindow::goForward); m_goForward->setShortcut(QKeySequence::Forward); m_ui.forwardButton->setDefaultAction(m_goForward); connectMessenger(&m_mainMessenger, Qt::DirectConnection); m_ui.statusbar->connectKillClicked(this, &MainWindow::killSounds); m_ui.statusbar->connectFXPressed(this, &MainWindow::fxPressed); m_ui.statusbar->setVolumeValue(70); m_ui.statusbar->connectVolumeSlider(this, &MainWindow::volumeChanged); m_ui.statusbar->connectASlider(this, &MainWindow::auxAChanged); m_ui.statusbar->connectBSlider(this, &MainWindow::auxBChanged); m_ui.keyboardContents->setStatusFocus(new StatusBarFocus(m_ui.statusbar)); m_ui.velocitySlider->setStatusFocus(new StatusBarFocus(m_ui.statusbar)); m_ui.modulationSlider->setStatusFocus(new StatusBarFocus(m_ui.statusbar)); m_ui.pitchSlider->setStatusFocus(new StatusBarFocus(m_ui.statusbar)); connect(m_ui.keyboardContents, &KeyboardWidget::notePressed, this, &MainWindow::notePressed); connect(m_ui.keyboardContents, &KeyboardWidget::noteReleased, this, &MainWindow::noteReleased); connect(m_ui.velocitySlider, qOverload(&VelocitySlider::valueChanged), this, &MainWindow::velocityChanged); connect(m_ui.modulationSlider, qOverload(&ModulationSlider::valueChanged), this, &MainWindow::modulationChanged); connect(m_ui.pitchSlider, qOverload(&PitchSlider::valueChanged), this, &MainWindow::pitchChanged); m_ui.actionNew_Project->setShortcut(QKeySequence::New); connect(m_ui.actionNew_Project, &QAction::triggered, this, &MainWindow::newAction); m_ui.actionOpen_Project->setShortcut(QKeySequence::Open); connect(m_ui.actionOpen_Project, &QAction::triggered, this, &MainWindow::openAction); m_ui.actionSave_Project->setShortcut(QKeySequence::Save); connect(m_ui.actionSave_Project, &QAction::triggered, this, &MainWindow::saveAction); connect(m_ui.actionRevert_Project, &QAction::triggered, this, &MainWindow::revertAction); connect(m_ui.actionReload_Sample_Data, &QAction::triggered, this, &MainWindow::reloadSampleDataAction); connect(m_ui.actionImport_Groups, &QAction::triggered, this, &MainWindow::importAction); connect(m_ui.actionImport_Songs, &QAction::triggered, this, &MainWindow::importSongsAction); connect(m_ui.actionExport_GameCube_Groups, &QAction::triggered, this, &MainWindow::exportAction); connect(m_ui.actionImport_C_Headers, &QAction::triggered, this, &MainWindow::importHeadersAction); connect(m_ui.actionExport_C_Headers, &QAction::triggered, this, &MainWindow::exportHeadersAction); m_ui.actionQuit->setShortcut(QKeySequence::Quit); connect(m_ui.actionQuit, &QAction::triggered, this, &MainWindow::close); for (int i = 0; i < MaxRecentFiles; ++i) { m_recentFileActs[i] = new QAction(this); m_recentFileActs[i]->setVisible(false); m_ui.menuRecent_Projects->addAction(m_recentFileActs[i]); connect(m_recentFileActs[i], &QAction::triggered, this, &MainWindow::openRecentFileAction); } m_ui.menuRecent_Projects->addSeparator(); m_clearRecentFileAct = new QAction(tr("Clear Recent Projects"), this); connect(m_clearRecentFileAct, &QAction::triggered, this, &MainWindow::clearRecentFilesAction); m_ui.menuRecent_Projects->addAction(m_clearRecentFileAct); updateRecentFileActions(); connect(m_undoStack, &QUndoStack::cleanChanged, this, &MainWindow::cleanChanged); QAction* undoAction = m_undoStack->createUndoAction(this); undoAction->setShortcut(QKeySequence::Undo); undoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); m_ui.menuEdit->insertAction(m_ui.actionCut, undoAction); QAction* redoAction = m_undoStack->createRedoAction(this); redoAction->setShortcut(QKeySequence::Redo); redoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-redo"))); m_ui.menuEdit->insertAction(m_ui.actionCut, redoAction); m_ui.menuEdit->insertSeparator(m_ui.actionCut); m_ui.actionCut->setShortcut(QKeySequence::Cut); m_ui.actionCopy->setShortcut(QKeySequence::Copy); m_ui.actionPaste->setShortcut(QKeySequence::Paste); m_ui.actionDelete->setShortcut(QKeySequence::Delete); onFocusChanged(nullptr, this); connect(m_ui.actionAbout_Amuse, &QAction::triggered, this, &MainWindow::aboutAmuseAction); connect(m_ui.actionAbout_Qt, &QAction::triggered, this, &MainWindow::aboutQtAction); QGridLayout* faceLayout = new QGridLayout; QSvgWidget* faceSvg = new QSvgWidget(QStringLiteral(":/bg/FaceGrey.svg")); faceSvg->setGeometry(0, 0, 256, 256); faceSvg->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); faceLayout->addWidget(faceSvg); m_faceSvg = new QWidget; m_faceSvg->setLayout(faceLayout); m_ui.editorContents->addWidget(m_faceSvg); m_songGroupEditor = new SongGroupEditor; m_ui.editorContents->addWidget(m_songGroupEditor); m_soundGroupEditor = new SoundGroupEditor; m_ui.editorContents->addWidget(m_soundGroupEditor); m_soundMacroEditor = new SoundMacroEditor; m_ui.editorContents->addWidget(m_soundMacroEditor); m_adsrEditor = new ADSREditor; m_ui.editorContents->addWidget(m_adsrEditor); m_curveEditor = new CurveEditor; m_ui.editorContents->addWidget(m_curveEditor); m_keymapEditor = new KeymapEditor; m_ui.editorContents->addWidget(m_keymapEditor); m_layersEditor = new LayersEditor; m_ui.editorContents->addWidget(m_layersEditor); m_sampleEditor = new SampleEditor; m_ui.editorContents->addWidget(m_sampleEditor); m_ui.editorContents->setCurrentWidget(m_faceSvg); m_studioSetup = new StudioSetupWidget(this); connect(m_studioSetup, &StudioSetupWidget::hidden, this, &MainWindow::studioSetupHidden); connect(m_studioSetup, &StudioSetupWidget::shown, this, &MainWindow::studioSetupShown); connect(m_ui.actionNew_Subproject, &QAction::triggered, this, &MainWindow::newSubprojectAction); connect(m_ui.actionNew_SFX_Group, &QAction::triggered, this, &MainWindow::newSFXGroupAction); connect(m_ui.actionNew_Song_Group, &QAction::triggered, this, &MainWindow::newSongGroupAction); connect(m_ui.actionNew_Sound_Macro, &QAction::triggered, this, &MainWindow::newSoundMacroAction); connect(m_ui.actionNew_ADSR, &QAction::triggered, this, &MainWindow::newADSRAction); connect(m_ui.actionNew_Curve, &QAction::triggered, this, &MainWindow::newCurveAction); connect(m_ui.actionNew_Keymap, &QAction::triggered, this, &MainWindow::newKeymapAction); connect(m_ui.actionNew_Layers, &QAction::triggered, this, &MainWindow::newLayersAction); connect(m_ui.menuAudio, &QMenu::aboutToShow, this, &MainWindow::aboutToShowAudioIOMenu); connect(m_ui.menuMIDI, &QMenu::aboutToShow, this, &MainWindow::aboutToShowMIDIIOMenu); connect(qApp, &QApplication::focusChanged, this, &MainWindow::onFocusChanged); connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &MainWindow::onClipboardChanged); m_voxEngine = boo::NewAudioVoiceEngine(); m_voxAllocator = std::make_unique(*m_voxEngine); m_engine = std::make_unique(*m_voxAllocator); m_engine->setVolume(0.7f); m_studioSetup->loadData(m_engine->getDefaultStudio().get()); m_ctrlVals[7] = 127; m_ctrlVals[10] = 64; startTimer(16); } MainWindow::~MainWindow() { m_backgroundThread.quit(); m_backgroundThread.wait(); } void MainWindow::connectMessenger(UIMessenger* messenger, Qt::ConnectionType type) { connect(messenger, qOverload( &UIMessenger::information), this, qOverload( &MainWindow::msgInformation), type); connect(messenger, qOverload( &UIMessenger::question), this, qOverload( &MainWindow::msgQuestion), type); connect(messenger, qOverload( &UIMessenger::question), this, qOverload( &MainWindow::msgQuestion), type); connect(messenger, qOverload( &UIMessenger::warning), this, qOverload( &MainWindow::msgWarning), type); connect(messenger, qOverload( &UIMessenger::critical), this, qOverload( &MainWindow::msgCritical), type); } void MainWindow::updateWindowTitle() { if (!m_projectModel) { setWindowTitle(tr("Amuse[*]")); return; } QDir dir(m_projectModel->path()); if (EditorWidget* w = getEditorWidget()) { ProjectModel::BasePoolObjectNode* objNode = static_cast(w->currentNode()); setWindowTitle(tr("%1/%2/%3[*] - Amuse") .arg(dir.dirName()) .arg(m_projectModel->getGroupNode(objNode)->text()) .arg(objNode->text())); return; } setWindowTitle(tr("%1[*] - Amuse").arg(dir.dirName())); } void MainWindow::updateRecentFileActions() { const QSettings settings; const QStringList files = settings.value(QStringLiteral("recentFileList")).toStringList(); const int numRecentFiles = std::min(files.size(), qsizetype(MaxRecentFiles)); for (int i = 0; i < numRecentFiles; ++i) { const QString text = QStringLiteral("&%1 %2").arg(i + 1).arg(QDir(files[i]).dirName()); m_recentFileActs[i]->setText(text); m_recentFileActs[i]->setData(files[i]); m_recentFileActs[i]->setToolTip(files[i]); m_recentFileActs[i]->setVisible(true); } for (int j = numRecentFiles; j < MaxRecentFiles; ++j) m_recentFileActs[j]->setVisible(false); m_ui.menuRecent_Projects->setEnabled(numRecentFiles > 0); } bool MainWindow::setProjectPath(const QString& path) { QDir dir(path); if (dir.path().isEmpty() || dir.path() == QStringLiteral(".") || dir.path() == QStringLiteral("..")) { QString msg = QString(tr("The directory at '%1' must not be empty.")).arg(path); m_mainMessenger.critical(tr("Directory empty"), msg); return false; } else if (!dir.exists()) { QString msg = QString(tr("The directory at '%1' must exist for the Amuse editor.")).arg(path); m_mainMessenger.critical(tr("Directory does not exist"), msg); return false; } QString testWritePath = dir.filePath(tr("__amuse_test__")); QFile testWriteFile(testWritePath); if (!testWriteFile.open(QFile::ReadWrite)) { QString msg = QString(tr("The directory at '%1' must be writable for the Amuse editor: %2")) .arg(path) .arg(testWriteFile.errorString()); m_mainMessenger.critical(tr("Unable to write to directory"), msg); return false; } testWriteFile.remove(); closeEditor(); if (m_projectModel) m_projectModel->deleteLater(); m_projectModel = new ProjectModel(path, this); m_ui.projectOutlineFilter->clear(); connect(m_ui.projectOutlineFilter, &QLineEdit::textChanged, m_projectModel->getOutlineProxy(), &OutlineFilterProxyModel::setFilterRegExp); m_ui.projectOutline->setModel(m_projectModel->getOutlineProxy()); connect(m_ui.projectOutline->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::onOutlineSelectionChanged); m_openDirectoryDialog.setDirectory(path); m_openFileDialog.setDirectory(path); m_newFileDialog.setDirectory(path); m_ui.actionSave_Project->setEnabled(true); m_ui.actionRevert_Project->setEnabled(true); m_ui.actionReload_Sample_Data->setEnabled(true); m_ui.actionImport_Songs->setEnabled(true); m_ui.actionExport_GameCube_Groups->setEnabled(true); m_ui.actionImport_C_Headers->setEnabled(true); m_ui.actionExport_C_Headers->setEnabled(true); m_ui.actionNew_Subproject->setEnabled(true); setWindowFilePath(path); updateWindowTitle(); updateFocus(); m_undoStack->clear(); m_navList.clear(); m_navIt = m_navList.begin(); updateNavigationButtons(); const QString key = QStringLiteral("recentFileList"); QSettings settings; QStringList files = settings.value(key).toStringList(); files.removeAll(dir.path()); files.prepend(dir.path()); while (files.size() > MaxRecentFiles) { files.removeLast(); } settings.setValue(key, files); settings.sync(); updateRecentFileActions(); return true; } void MainWindow::refreshAudioIO() { QList audioActions = m_ui.menuAudio->actions(); if (audioActions.size() > 2) for (auto it = audioActions.begin() + 2; it != audioActions.end(); ++it) m_ui.menuAudio->removeAction(*it); bool addedDev = false; if (m_voxEngine) { std::string curOut = m_voxEngine->getCurrentAudioOutput(); for (const auto& dev : m_voxEngine->enumerateAudioOutputs()) { QAction* act = m_ui.menuAudio->addAction(QString::fromStdString(dev.second)); act->setCheckable(true); act->setData(QString::fromStdString(dev.first)); if (curOut == dev.first) act->setChecked(true); connect(act, &QAction::triggered, this, &MainWindow::setAudioIO); addedDev = true; } } if (!addedDev) m_ui.menuAudio->addAction(tr("No Audio Devices Found"))->setEnabled(false); } void MainWindow::refreshMIDIIO() { QList midiActions = m_ui.menuMIDI->actions(); if (midiActions.size() > 2) for (auto it = midiActions.begin() + 2; it != midiActions.end(); ++it) m_ui.menuMIDI->removeAction(*it); bool addedDev = false; if (m_voxEngine) { if (m_voxEngine->supportsVirtualMIDIIn()) { QAction* act = m_ui.menuMIDI->addAction(tr("Virtual MIDI-In")); act->setCheckable(true); act->setData(QStringLiteral("")); act->setChecked(static_cast(m_engine->getMIDIReader())->hasVirtualIn()); connect(act, &QAction::triggered, this, &MainWindow::setMIDIIO); addedDev = true; } for (const auto& dev : m_voxEngine->enumerateMIDIInputs()) { QAction* act = m_ui.menuMIDI->addAction(QString::fromStdString(dev.second)); act->setCheckable(true); act->setData(QString::fromStdString(dev.first)); act->setChecked(static_cast(m_engine->getMIDIReader())->hasMIDIIn(dev.first.c_str())); connect(act, &QAction::triggered, this, &MainWindow::setMIDIIO); addedDev = true; } } if (!addedDev) m_ui.menuMIDI->addAction(tr("No MIDI Devices Found"))->setEnabled(false); } void MainWindow::timerEvent(QTimerEvent* ev) { if (m_voxEngine && m_engine) { m_voxEngine->pumpAndMixVoices(); m_peakVoices = std::max(int(m_engine->getNumTotalActiveVoices()), m_peakVoices); if (!(m_timerFireCount % 10)) { /* Rate limit voice counter */ m_ui.statusbar->setVoiceCount(m_peakVoices); m_peakVoices = 0; } if (m_engine->getActiveVoices().empty() && m_uiDisabled) { m_ui.projectOutline->setEnabled(true); m_ui.backButton->setEnabled(true); m_ui.forwardButton->setEnabled(true); if (EditorWidget* w = getEditorWidget()) w->setEditorEnabled(true); m_ui.menubar->setEnabled(true); m_uiDisabled = false; } else if (!m_engine->getActiveVoices().empty() && !m_uiDisabled) { m_ui.projectOutline->setEnabled(false); m_ui.backButton->setEnabled(false); m_ui.forwardButton->setEnabled(false); if (EditorWidget* w = getEditorWidget()) w->setEditorEnabled(false); m_ui.menubar->setEnabled(false); m_uiDisabled = true; } if (SampleEditor* sampleEditor = qobject_cast(m_ui.editorContents->currentWidget())) { const auto& activeVoxs = m_engine->getActiveVoices(); if (activeVoxs.size() && activeVoxs.back()->state() != amuse::VoiceState::Dead) sampleEditor->setSamplePos(activeVoxs.back()->getSamplePos()); else sampleEditor->setSamplePos(-1); } QTableView* songTable = m_songGroupEditor->getSetupListView(); for (int i = 0; i < songTable->model()->rowCount(); ++i) if (MIDIPlayerWidget* player = qobject_cast(songTable->indexWidget(songTable->model()->index(i, 1)))) if (player->sequencer() && player->sequencer()->state() != amuse::SequencerState::Playing) player->stopped(); QTableView* sfxTable = m_soundGroupEditor->getSFXListView(); for (int i = 0; i < sfxTable->model()->rowCount(); ++i) if (SFXPlayerWidget* player = qobject_cast(sfxTable->indexWidget(sfxTable->model()->index(i, 1)))) if (player->voice() && player->voice()->state() != amuse::VoiceState::Playing) player->stopped(); ++m_timerFireCount; } } void MainWindow::setSustain(bool sustain) { if (sustain && m_ctrlVals[64] < 0x40) { m_ui.statusbar->setNormalMessage(tr("SUSTAIN")); for (auto& v : m_engine->getActiveVoices()) v->setPedal(true); m_ctrlVals[64] = 127; } else if (!sustain && m_ctrlVals[64] >= 0x40) { m_ui.statusbar->setNormalMessage({}); for (auto& v : m_engine->getActiveVoices()) v->setPedal(false); m_ctrlVals[64] = 0; } } void MainWindow::keyPressEvent(QKeyEvent* ev) { if (ev->key() == Qt::Key_Shift) setSustain(true); else if (ev->key() == Qt::Key_Escape) killSounds(); } void MainWindow::keyReleaseEvent(QKeyEvent* ev) { if (ev->key() == Qt::Key_Shift) setSustain(false); } void MainWindow::startBackgroundTask(int id, const QString& windowTitle, const QString& label, std::function&& task) { assert(m_backgroundTask == nullptr && "existing background process"); setEnabled(false); m_backgroundTask = new BackgroundTask(id, std::move(task)); m_backgroundTask->moveToThread(&m_backgroundThread); m_backgroundDialog = new QProgressDialog(this); m_backgroundDialog->setWindowTitle(windowTitle); m_backgroundDialog->setLabelText(label); m_backgroundDialog->setMinimumWidth(300); m_backgroundDialog->setAutoClose(false); m_backgroundDialog->setAutoReset(false); m_backgroundDialog->setMinimumDuration(0); m_backgroundDialog->setMinimum(0); m_backgroundDialog->setMaximum(0); m_backgroundDialog->setValue(0); connect(m_backgroundTask, &BackgroundTask::setMinimum, m_backgroundDialog, &QProgressDialog::setMinimum, Qt::QueuedConnection); connect(m_backgroundTask, &BackgroundTask::setMaximum, m_backgroundDialog, &QProgressDialog::setMaximum, Qt::QueuedConnection); connect(m_backgroundTask, &BackgroundTask::setValue, m_backgroundDialog, &QProgressDialog::setValue, Qt::QueuedConnection); connect(m_backgroundTask, &BackgroundTask::setLabelText, m_backgroundDialog, &QProgressDialog::setLabelText, Qt::QueuedConnection); connect(m_backgroundTask, &BackgroundTask::finished, this, &MainWindow::onBackgroundTaskFinished, Qt::QueuedConnection); m_backgroundDialog->open(m_backgroundTask, SLOT(cancel())); connectMessenger(&m_backgroundTask->uiMessenger(), Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(m_backgroundTask, "run", Qt::QueuedConnection); } bool MainWindow::_setEditor(EditorWidget* editor, bool appendNav) { m_interactiveSeq.reset(); if (editor != m_ui.editorContents->currentWidget() && m_ui.editorContents->currentWidget() != m_faceSvg) getEditorWidget()->unloadData(); if (appendNav && m_navIt != m_navList.end()) { m_navList.erase(std::next(m_navIt, 1), m_navList.end()); } if (!editor || !editor->valid()) { m_ui.editorContents->setCurrentWidget(m_faceSvg); updateWindowTitle(); if (appendNav) { m_navIt = m_navList.end(); updateNavigationButtons(); } return false; } m_ui.editorContents->setCurrentWidget(editor); m_ui.editorContents->update(); updateWindowTitle(); ProjectModel::INode* currentNode = editor->currentNode(); if (appendNav) { if (m_navIt == m_navList.end() || *m_navIt != currentNode) m_navIt = m_navList.insert(m_navList.end(), currentNode); updateNavigationButtons(); } recursiveExpandAndSelectOutline(m_projectModel->index(currentNode)); return true; } bool MainWindow::openEditor(ProjectModel::SongGroupNode* node, bool appendNav) { bool ret = _setEditor(m_songGroupEditor->loadData(node) ? m_songGroupEditor : nullptr, appendNav); if (ProjectModel::INode* n = getEditorNode()) { if (n->type() == ProjectModel::INode::Type::SongGroup) { ProjectModel::SongGroupNode* cn = static_cast(n); ProjectModel::GroupNode* gn = m_projectModel->getGroupNode(n); m_interactiveSeq = m_engine->seqPlay(gn->getAudioGroup(), cn->m_id, {}, nullptr); } } return ret; } bool MainWindow::openEditor(ProjectModel::SoundGroupNode* node, bool appendNav) { bool ret = _setEditor(m_soundGroupEditor->loadData(node) ? m_soundGroupEditor : nullptr, appendNav); if (ProjectModel::INode* n = getEditorNode()) { if (n->type() == ProjectModel::INode::Type::SoundGroup) { ProjectModel::SoundGroupNode* cn = static_cast(n); ProjectModel::GroupNode* gn = m_projectModel->getGroupNode(n); m_interactiveSeq = m_engine->seqPlay(gn->getAudioGroup(), cn->m_id, {}, nullptr); } } return ret; } bool MainWindow::openEditor(ProjectModel::SoundMacroNode* node, bool appendNav) { return _setEditor(m_soundMacroEditor->loadData(node) ? m_soundMacroEditor : nullptr, appendNav); } bool MainWindow::openEditor(ProjectModel::ADSRNode* node, bool appendNav) { return _setEditor(m_adsrEditor->loadData(node) ? m_adsrEditor : nullptr, appendNav); } bool MainWindow::openEditor(ProjectModel::CurveNode* node, bool appendNav) { return _setEditor(m_curveEditor->loadData(node) ? m_curveEditor : nullptr, appendNav); } bool MainWindow::openEditor(ProjectModel::KeymapNode* node, bool appendNav) { return _setEditor(m_keymapEditor->loadData(node) ? m_keymapEditor : nullptr, appendNav); } bool MainWindow::openEditor(ProjectModel::LayersNode* node, bool appendNav) { return _setEditor(m_layersEditor->loadData(node) ? m_layersEditor : nullptr, appendNav); } bool MainWindow::openEditor(ProjectModel::SampleNode* node, bool appendNav) { return _setEditor(m_sampleEditor->loadData(node) ? m_sampleEditor : nullptr, appendNav); } bool MainWindow::openEditor(ProjectModel::INode* node, bool appendNav) { if (m_uiDisabled) return false; switch (node->type()) { case ProjectModel::INode::Type::SongGroup: return openEditor(static_cast(node), appendNav); case ProjectModel::INode::Type::SoundGroup: return openEditor(static_cast(node), appendNav); case ProjectModel::INode::Type::SoundMacro: return openEditor(static_cast(node), appendNav); case ProjectModel::INode::Type::ADSR: return openEditor(static_cast(node), appendNav); case ProjectModel::INode::Type::Curve: return openEditor(static_cast(node), appendNav); case ProjectModel::INode::Type::Keymap: return openEditor(static_cast(node), appendNav); case ProjectModel::INode::Type::Layer: return openEditor(static_cast(node), appendNav); case ProjectModel::INode::Type::Sample: return openEditor(static_cast(node), appendNav); default: return false; } } void MainWindow::closeEditor() { _setEditor(nullptr); } ProjectModel::INode* MainWindow::getEditorNode() const { if (EditorWidget* w = getEditorWidget()) return w->currentNode(); return nullptr; } EditorWidget* MainWindow::getEditorWidget() const { if (m_ui.editorContents->currentWidget() != m_faceSvg) return static_cast(m_ui.editorContents->currentWidget()); return nullptr; } amuse::ObjToken MainWindow::startEditorVoice(uint8_t key, uint8_t velocity) { amuse::ObjToken vox; if (ProjectModel::INode* node = getEditorNode()) { amuse::AudioGroupDatabase* group = projectModel()->getGroupNode(node)->getAudioGroup(); if (node->type() == ProjectModel::INode::Type::SoundMacro) { ProjectModel::SoundMacroNode* cNode = static_cast(node); vox = m_engine->macroStart(group, cNode->id(), key, velocity, m_ctrlVals[1]); } else if (node->type() == ProjectModel::INode::Type::Keymap || node->type() == ProjectModel::INode::Type::Layer) { ProjectModel::BasePoolObjectNode* cNode = static_cast(node); vox = m_engine->pageObjectStart(group, cNode->id(), key, velocity, m_ctrlVals[1]); } else if (node->type() == ProjectModel::INode::Type::Sample) { SampleEditor* editor = static_cast(m_ui.editorContents->currentWidget()); vox = m_engine->macroStart(group, editor->soundMacro(), key, velocity, m_ctrlVals[1]); } if (vox) { vox->setPedal(m_ctrlVals[64] >= 0x40); vox->setPitchWheel(m_pitch); vox->installCtrlValues(m_ctrlVals); vox->setReverbVol(m_auxAVol); vox->setAuxBVol(m_auxBVol); } } return vox; } amuse::ObjToken MainWindow::startSFX(amuse::GroupId groupId, amuse::SFXId sfxId) { if (ProjectModel::INode* node = getEditorNode()) { amuse::AudioGroupDatabase* group = projectModel()->getGroupNode(node)->getAudioGroup(); auto ret = m_engine->fxStart(group, groupId, sfxId, 1.f, 0.f); ret->setReverbVol(m_auxAVol); ret->setAuxBVol(m_auxBVol); return ret; } return {}; } amuse::ObjToken MainWindow::startSong(amuse::GroupId groupId, amuse::SongId songId, const unsigned char* arrData) { if (ProjectModel::INode* node = getEditorNode()) { amuse::AudioGroupDatabase* group = projectModel()->getGroupNode(node)->getAudioGroup(); auto ret = m_engine->seqPlay(group, groupId, songId, arrData); for (uint8_t i = 0; i < 16; ++i) { ret->setCtrlValue(i, 0x5b, int8_t(m_auxAVol * 127.f)); ret->setCtrlValue(i, 0x5d, int8_t(m_auxBVol * 127.f)); } return ret; } return {}; } void MainWindow::pushUndoCommand(EditorUndoCommand* cmd) { m_undoStack->push(cmd); } void MainWindow::updateFocus() { onFocusChanged(nullptr, focusWidget()); } void MainWindow::aboutToDeleteNode(ProjectModel::INode* node) { if (*m_navIt == node && m_navIt != m_navList.end()) { m_navList.erase(m_navIt, m_navList.end()); m_navIt = m_navList.end(); } for (auto it = m_navList.begin(); it != m_navList.end();) { if (*it == node) { it = m_navList.erase(it); continue; } ++it; } updateNavigationButtons(); if (getEditorNode() == node) closeEditor(); } bool MainWindow::askAboutSave() { if (!m_projectModel) return true; if (!m_undoStack->isClean()) { QDir dir(m_projectModel->path()); int result = m_mainMessenger.question(tr("Unsaved Changes"), tr("Save Changes in %1?").arg(dir.dirName()), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save); if (result == QMessageBox::Save) { saveAction(); return true; } return result == QMessageBox::Discard; } else { return true; } } void MainWindow::closeEvent(QCloseEvent* ev) { if (askAboutSave()) ev->accept(); else ev->ignore(); } void MainWindow::showEvent(QShowEvent* ev) { m_studioSetup->updateWindowPosition(); } void MainWindow::newAction() { if (!askAboutSave()) return; m_newFileDialog.setWindowTitle(tr("New Project")); m_newFileDialog.open(this, SLOT(_newAction(const QString&))); } void MainWindow::_newAction(const QString& path) { m_newFileDialog.close(); if (path.isEmpty()) return; if (!MkPath(path, m_mainMessenger)) return; if (!setProjectPath(path)) return; m_projectModel->clearProjectData(); bool hasGroups = m_projectModel->ensureModelData(); m_ui.actionImport_Groups->setDisabled(hasGroups); m_ui.actionImport_Songs->setEnabled(hasGroups); } bool MainWindow::openProject(const QString& path) { QDir dir(path); if (dir.path().isEmpty() || dir.path() == QStringLiteral(".") || dir.path() == QStringLiteral("..")) { QString msg = QString(tr("The directory at '%1' must not be empty.")).arg(path); m_mainMessenger.critical(tr("Directory empty"), msg); return false; } else if (!dir.exists()) { QString msg = QString(tr("The directory at '%1' does not exist.")).arg(path); m_mainMessenger.critical(tr("Bad Directory"), msg); return false; } if (QFileInfo(dir, QStringLiteral("!project.yaml")).exists() && QFileInfo(dir, QStringLiteral("!pool.yaml")).exists()) dir.cdUp(); if (m_projectModel && m_projectModel->path() == dir.path()) return true; if (!setProjectPath(dir.path())) return false; ProjectModel* model = m_projectModel; startBackgroundTask(TaskOpen, tr("Opening"), tr("Scanning Project"), [dir, model](BackgroundTask& task) { QStringList childDirs = dir.entryList(QDir::Dirs); for (const auto& chDir : childDirs) { if (task.isCanceled()) return; if (chDir == QStringLiteral("out")) continue; QString chPath = dir.filePath(chDir); if (QFileInfo(chPath, QStringLiteral("!project.yaml")).exists() && QFileInfo(chPath, QStringLiteral("!pool.yaml")).exists()) { task.setLabelText(tr("Opening %1").arg(chDir)); if (!model->openGroupData(chDir, task.uiMessenger())) return; } } model->openSongsData(); }); return true; } void MainWindow::openAction() { if (!askAboutSave()) return; m_openDirectoryDialog.setWindowTitle(tr("Open Project")); m_openDirectoryDialog.open(this, SLOT(_openAction(const QString&))); } void MainWindow::_openAction(const QString& path) { m_openDirectoryDialog.close(); if (path.isEmpty()) return; openProject(path); } void MainWindow::openRecentFileAction() { if (!askAboutSave()) { return; } if (const QAction* action = qobject_cast(sender())) { const QString path = action->data().toString(); if (openProject(path)) { return; } const QString key = QStringLiteral("recentFileList"); QSettings settings; QStringList files = settings.value(key).toStringList(); files.removeAll(path); settings.setValue(key, files); settings.sync(); updateRecentFileActions(); } } void MainWindow::clearRecentFilesAction() { QSettings settings; settings.setValue(QStringLiteral("recentFileList"), QStringList()); settings.sync(); updateRecentFileActions(); } void MainWindow::saveAction() { if (m_projectModel) { m_projectModel->saveToFile(m_mainMessenger); m_undoStack->setClean(); } } void MainWindow::revertAction() { if (m_projectModel && !m_undoStack->isClean()) { QDir dir(m_projectModel->path()); int result = m_mainMessenger.question(tr("Unsaved Changes"), tr("Discard Changes in %1?").arg(dir.dirName()), QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel); if (result == QMessageBox::Cancel) return; } QString path = m_projectModel->path(); closeEditor(); m_undoStack->clear(); m_navList.clear(); m_navIt = m_navList.begin(); updateNavigationButtons(); delete m_projectModel; m_projectModel = nullptr; openProject(path); } void MainWindow::reloadSampleDataAction() { closeEditor(); ProjectModel* model = m_projectModel; if (!m_projectModel) return; QDir dir(m_projectModel->path()); if (!dir.exists()) return; startBackgroundTask(TaskReloadSamples, tr("Reloading Samples"), tr("Scanning Project"), [dir, model](BackgroundTask& task) { QStringList childDirs = dir.entryList(QDir::Dirs); for (const auto& chDir : childDirs) { if (task.isCanceled()) return; if (chDir == QStringLiteral("out")) continue; QString chPath = dir.filePath(chDir); if (QFileInfo(chPath, QStringLiteral("!project.yaml")).exists() && QFileInfo(chPath, QStringLiteral("!pool.yaml")).exists()) { task.setLabelText(tr("Scanning %1").arg(chDir)); if (!model->reloadSampleData(chDir, task.uiMessenger())) return; } } }); } void MainWindow::importAction() { m_openFileDialog.setWindowTitle(tr("Import Project")); m_openFileDialog.open(this, SLOT(_importAction(const QString&))); } void MainWindow::_importAction(const QString& path) { m_openFileDialog.close(); if (path.isEmpty()) return; /* Validate input file */ amuse::ContainerRegistry::Type tp = amuse::ContainerRegistry::DetectContainerType(QStringToUTF8(path).c_str()); if (tp == amuse::ContainerRegistry::Type::Invalid) { QString msg = QString(tr("The file at '%1' could not be interpreted as a MusyX container.")).arg(path); m_mainMessenger.critical(tr("Unsupported MusyX Container"), msg); return; } /* Ask user about sample conversion */ int impMode = m_mainMessenger.question(tr("Sample Import Mode"), tr("Amuse can import samples as WAV files for ease of editing, " "import original compressed data for lossless repacking, or both. " "Exporting the project will prefer whichever version was modified " "most recently."), tr("Import Compressed"), tr("Import WAVs"), tr("Import Both")); switch (impMode) { case 0: case 1: case 2: break; default: return; } ProjectModel::ImportMode importMode = ProjectModel::ImportMode(impMode); /* Special handling for raw groups - gather sibling groups in filesystem */ if (tp == amuse::ContainerRegistry::Type::Raw4) { int scanMode = m_mainMessenger.question(tr("Raw Import Mode"), tr("Would you like to scan for all MusyX group files in this directory?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (scanMode == QMessageBox::Yes) { /* Auto-create project */ if (m_projectModel == nullptr) { QString newName; bool ok = true; while (ok && newName.isEmpty()) newName = QInputDialog::getText(this, tr("Project Name"), tr("What should this project be named?"), QLineEdit::Normal, QString(), &ok); if (!ok) return; QFileInfo fInfo(path); QString newPath = QFileInfo(fInfo.dir(), newName).filePath(); if (!MkPath(fInfo.dir(), newName, m_mainMessenger)) return; if (!setProjectPath(newPath)) return; } ProjectModel* model = m_projectModel; startBackgroundTask( TaskImport, tr("Importing"), tr("Scanning Project"), [model, path, importMode](BackgroundTask& task) { const QDir dir = QFileInfo(path).dir(); const QStringList filters{QStringLiteral("*.proj"), QStringLiteral("*.pro")}; const QStringList files = dir.entryList(filters, QDir::Files); for (const QString& fPath : files) { auto data = amuse::ContainerRegistry::LoadContainer(QStringToUTF8(dir.filePath(fPath)).c_str()); for (auto& p : data) { task.setLabelText(tr("Importing %1").arg(UTF8ToQString(p.first))); if (task.isCanceled()) return; if (!model->importGroupData(UTF8ToQString(p.first), p.second, importMode, task.uiMessenger())) return; } } model->openSongsData(); }); return; } else if (scanMode == QMessageBox::No) { } else { return; } } /* Auto-create project */ if (m_projectModel == nullptr) { QFileInfo fInfo(path); QString newPath = QFileInfo(fInfo.dir(), fInfo.completeBaseName()).filePath(); if (!MkPath(fInfo.dir(), fInfo.completeBaseName(), m_mainMessenger)) return; if (!setProjectPath(newPath)) return; } ProjectModel* model = m_projectModel; startBackgroundTask( TaskImport, tr("Importing"), tr("Scanning Project"), [model, path, importMode](BackgroundTask& task) { /* Handle single container */ auto data = amuse::ContainerRegistry::LoadContainer(QStringToUTF8(path).c_str()); task.setMaximum(int(data.size())); int curVal = 0; for (auto& p : data) { task.setLabelText(tr("Importing %1").arg(UTF8ToQString(p.first))); if (task.isCanceled()) return; if (!model->importGroupData(UTF8ToQString(p.first), p.second, importMode, task.uiMessenger())) return; task.setValue(++curVal); } model->openSongsData(); }); } void MainWindow::importSongsAction() { if (!m_projectModel) return; m_openFileDialog.setWindowTitle(tr("Import Songs")); m_openFileDialog.open(this, SLOT(_importSongsAction(const QString&))); } void MainWindow::_importSongsAction(const QString& path) { m_openFileDialog.close(); if (path.isEmpty()) return; closeEditor(); m_projectModel->importSongsData(path); } void MainWindow::exportAction() { if (!m_projectModel) return; QFileInfo dirInfo(m_projectModel->dir(), QStringLiteral("out")); if (!MkPath(dirInfo.filePath(), m_mainMessenger)) return; QDir dir(dirInfo.filePath()); ProjectModel* model = m_projectModel; startBackgroundTask(BackgroundTaskId::TaskExport, tr("Exporting"), tr("Scanning Project"), [model, dir](BackgroundTask& task) { QStringList groupList = model->getGroupList(); task.setMaximum(groupList.size()); int curVal = 0; for (QString group : groupList) { task.setLabelText(tr("Exporting %1").arg(group)); if (task.isCanceled()) return; if (!model->exportGroup(dir.path(), group, task.uiMessenger())) return; task.setValue(++curVal); } }); } void MainWindow::importHeadersAction() { if (!m_projectModel) return; int confirm = m_mainMessenger.warning(tr("Import C Headers"), tr("

Importing names from C headers depends on up-to-date, " "consistent names relative to the sound group data.

" "

Headers are imported on a per-subproject basis from " "a single directory. Headers must be named with the form " "<subproject>.h.

" "

Group, Song and SFX definitions are matched according to the following forms:" "

#define GRP<name> <id>\n#define SNG<name> <id>\n"
                                 "#define SFX<name> <id>

" "

This operation cannot be undone! It is recommended to " "make a backup of the project directory before proceeding.

" "

Continue?

"), QMessageBox::Yes | QMessageBox::No); if (confirm == QMessageBox::No) return; m_openDirectoryDialog.setWindowTitle(tr("Import C Headers")); m_openDirectoryDialog.open(this, SLOT(_importHeadersAction(const QString&))); } void MainWindow::_importHeadersAction(const QString& path) { m_openDirectoryDialog.close(); if (path.isEmpty()) return; closeEditor(); for (QString group : m_projectModel->getGroupList()) m_projectModel->importHeader(path, group, m_mainMessenger); m_projectModel->updateNodeNames(); /* Samples may have been renamed.. stay consistent! */ saveAction(); } void MainWindow::exportHeadersAction() { if (!m_projectModel) return; m_openDirectoryDialog.setWindowTitle(tr("Export C Headers")); m_openDirectoryDialog.open(this, SLOT(_exportHeadersAction(const QString&))); } void MainWindow::_exportHeadersAction(const QString& path) { m_openDirectoryDialog.close(); if (path.isEmpty()) return; bool yesToAll = false; for (QString group : m_projectModel->getGroupList()) if (!m_projectModel->exportHeader(path, group, yesToAll, m_mainMessenger)) return; } bool TreeDelegate::editorEvent(QEvent* event, QAbstractItemModel* __model, const QStyleOptionViewItem& option, const QModelIndex& index) { QSortFilterProxyModel* _model = static_cast(__model); ProjectModel* model = static_cast(_model->sourceModel()); ProjectModel::INode* node = model->node(_model->mapToSource(index)); if (!node || !(node->flags() & Qt::ItemIsEditable)) return false; AmuseItemEditFlags flags = node->editFlags(); if (event->type() == QEvent::MouseButtonPress) { QMouseEvent* ev = static_cast(event); if (ev->button() == Qt::RightButton) { ContextMenu* menu = new ContextMenu; if (node->type() == ProjectModel::INode::Type::Group) { QAction* exportGroupAction = new QAction(tr("Export GameCube Group"), menu); exportGroupAction->setData(QVariant::fromValue((void*)node)); connect(exportGroupAction, &QAction::triggered, this, &TreeDelegate::doExportGroup); menu->addAction(exportGroupAction); menu->addSeparator(); } QAction* findUsagesAction = new QAction(tr("Find Usages"), menu); findUsagesAction->setData(QVariant::fromValue((void*)node)); findUsagesAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); connect(findUsagesAction, &QAction::triggered, this, &TreeDelegate::doFindUsages); menu->addAction(findUsagesAction); menu->addSeparator(); QAction* cutAction = new QAction(tr("Cut"), menu); cutAction->setData(index); cutAction->setEnabled(flags & AmuseItemCut); cutAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-cut"))); cutAction->setShortcut(QKeySequence::Cut); connect(cutAction, &QAction::triggered, this, &TreeDelegate::doCut); menu->addAction(cutAction); QAction* copyAction = new QAction(tr("Copy"), menu); copyAction->setData(index); copyAction->setEnabled(flags & AmuseItemCopy); copyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); copyAction->setShortcut(QKeySequence::Copy); connect(copyAction, &QAction::triggered, this, &TreeDelegate::doCopy); menu->addAction(copyAction); QAction* pasteAction = new QAction(tr("Paste"), menu); pasteAction->setData(index); pasteAction->setEnabled(g_MainWindow->m_clipboardAmuseData && flags & AmuseItemPaste); pasteAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste"))); pasteAction->setShortcut(QKeySequence::Paste); connect(pasteAction, &QAction::triggered, this, &TreeDelegate::doPaste); menu->addAction(pasteAction); QAction* dupeAction = new QAction(tr("Duplicate"), menu); dupeAction->setData(index); connect(dupeAction, &QAction::triggered, this, &TreeDelegate::doDuplicate); menu->addAction(dupeAction); QAction* deleteAction = new QAction(tr("Delete"), menu); deleteAction->setData(index); deleteAction->setEnabled(flags & AmuseItemDelete); deleteAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); deleteAction->setShortcut(QKeySequence::Delete); connect(deleteAction, &QAction::triggered, this, &TreeDelegate::doDelete); menu->addAction(deleteAction); QAction* renameAction = new QAction(tr("Rename"), menu); renameAction->setData(index); renameAction->setShortcut(Qt::Key_F2); connect(renameAction, &QAction::triggered, this, &TreeDelegate::doRename); menu->addAction(renameAction); menu->popup(ev->globalPosition().toPoint()); } } return false; } void TreeDelegate::doExportGroup() { if (!m_window.m_projectModel) return; QAction* act = qobject_cast(sender()); if (ProjectModel::INode* node = reinterpret_cast(act->data().value())) { if (node->type() != ProjectModel::INode::Type::Group) return; QString groupName = static_cast(node)->name(); ProjectModel* model = m_window.m_projectModel; QFileInfo dirInfo(model->dir(), QStringLiteral("out")); if (!MkPath(dirInfo.filePath(), m_window.m_mainMessenger)) return; QDir dir(dirInfo.filePath()); m_window.startBackgroundTask(BackgroundTaskId::TaskExport, tr("Exporting"), tr("Exporting %1").arg(groupName), [model, dir, groupName](BackgroundTask& task) { model->exportGroup(dir.path(), groupName, task.uiMessenger()); }); } } void TreeDelegate::doFindUsages() { QAction* act = qobject_cast(sender()); if (ProjectModel::INode* node = reinterpret_cast(act->data().value())) m_window.findUsages(node); } void TreeDelegate::doCut() { QAction* act = qobject_cast(sender()); if (!m_window.m_projectModel) return; m_window.m_projectModel->cut(m_window.m_projectModel->getOutlineProxy()->mapToSource(act->data().toModelIndex())); } void TreeDelegate::doCopy() { QAction* act = qobject_cast(sender()); if (!m_window.m_projectModel) return; m_window.m_projectModel->copy(m_window.m_projectModel->getOutlineProxy()->mapToSource(act->data().toModelIndex())); } void TreeDelegate::doPaste() { QAction* act = qobject_cast(sender()); if (!m_window.m_projectModel) return; m_window.m_projectModel->paste(m_window.m_projectModel->getOutlineProxy()->mapToSource(act->data().toModelIndex())); } void TreeDelegate::doDuplicate() { QAction* act = qobject_cast(sender()); if (!m_window.m_projectModel) return; QModelIndex newIdx = m_window.m_projectModel->duplicate( m_window.m_projectModel->getOutlineProxy()->mapToSource(act->data().toModelIndex())); if (newIdx.isValid()) { newIdx = m_window.m_projectModel->getOutlineProxy()->mapFromSource(newIdx); m_window.m_ui.projectOutline->edit(newIdx); } } void TreeDelegate::doDelete() { QAction* act = qobject_cast(sender()); if (!m_window.m_projectModel) return; m_window.m_projectModel->del(m_window.m_projectModel->getOutlineProxy()->mapToSource(act->data().toModelIndex())); } void TreeDelegate::doRename() { QAction* act = qobject_cast(sender()); m_window.m_ui.projectOutline->edit(act->data().toModelIndex()); } QString MainWindow::getGroupName(ProjectModel::GroupNode* group) const { if (group) return group->text(); return {}; } ProjectModel::GroupNode* MainWindow::getSelectedGroupNode() const { if (!m_projectModel) return nullptr; if (!m_ui.projectOutline->currentIndex().isValid()) return nullptr; return m_projectModel->getGroupNode( m_projectModel->node(m_projectModel->getOutlineProxy()->mapToSource(m_ui.projectOutline->currentIndex()))); } QString MainWindow::getSelectedGroupName() const { return getGroupName(getSelectedGroupNode()); } void MainWindow::_recursiveExpandOutline(const QModelIndex& filterIndex) const { if (filterIndex.isValid()) { _recursiveExpandOutline(filterIndex.parent()); m_ui.projectOutline->expand(filterIndex); } } void MainWindow::recursiveExpandAndSelectOutline(const QModelIndex& index) const { QModelIndex filterIndex = m_projectModel->getOutlineProxy()->mapFromSource(index); _recursiveExpandOutline(filterIndex); if (filterIndex.isValid()) m_ui.projectOutline->setCurrentIndex(filterIndex); } void MainWindow::newSubprojectAction() { QString newName; bool ok = true; while (ok && newName.isEmpty()) { newName = QInputDialog::getText(this, tr("New Subproject"), tr("What should this subproject be named?"), QLineEdit::Normal, QString(), &ok); } if (!ok) { return; } m_projectModel->newSubproject(std::move(newName)); } void MainWindow::newSFXGroupAction() { ProjectModel::GroupNode* group = getSelectedGroupNode(); m_projectModel->setIdDatabases(group); const QString groupName = getGroupName(group); QString newName; bool ok = true; while (ok && newName.isEmpty()) { newName = QInputDialog::getText( this, tr("New SFX Group"), tr("What should the new SFX group in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(amuse::GroupId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Group)), &ok); } if (!ok) { return; } ProjectModel::SoundGroupNode* node = m_projectModel->newSoundGroup(group, std::move(newName)); if (node) { openEditor(node); } } void MainWindow::newSongGroupAction() { ProjectModel::GroupNode* group = getSelectedGroupNode(); m_projectModel->setIdDatabases(group); const QString groupName = getGroupName(group); QString newName; bool ok = true; while (ok && newName.isEmpty()) { newName = QInputDialog::getText( this, tr("New Song Group"), tr("What should the new Song group in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(amuse::GroupId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Group)), &ok); } if (!ok) { return; } ProjectModel::SongGroupNode* node = m_projectModel->newSongGroup(group, std::move(newName)); if (node) { openEditor(node); } } void MainWindow::newSoundMacroAction() { ProjectModel::GroupNode* group = getSelectedGroupNode(); m_projectModel->setIdDatabases(group); const QString groupName = getGroupName(group); QString newName; const SoundMacroTemplateEntry* templ = nullptr; int result = QDialog::Accepted; while (result == QDialog::Accepted && newName.isEmpty()) { NewSoundMacroDialog dialog(groupName, this); dialog.setWindowModality(Qt::WindowModal); result = dialog.exec(); newName = dialog.getName(); templ = dialog.getSelectedTemplate(); } if (result == QDialog::Rejected) { return; } ProjectModel::SoundMacroNode* node = m_projectModel->newSoundMacro(group, std::move(newName), templ); if (node) { openEditor(node); } } void MainWindow::newADSRAction() { ProjectModel::GroupNode* group = getSelectedGroupNode(); m_projectModel->setIdDatabases(group); const QString groupName = getGroupName(group); QString newName; bool ok = true; while (ok && newName.isEmpty()) { newName = QInputDialog::getText( this, tr("New ADSR"), tr("What should the new ADSR in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(amuse::TableId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Table)), &ok); } if (!ok) { return; } ProjectModel::ADSRNode* node = m_projectModel->newADSR(group, std::move(newName)); if (node) { openEditor(node); } } void MainWindow::newCurveAction() { ProjectModel::GroupNode* group = getSelectedGroupNode(); m_projectModel->setIdDatabases(group); const QString groupName = getGroupName(group); QString newName; bool ok = true; while (ok && newName.isEmpty()) { newName = QInputDialog::getText( this, tr("New Curve"), tr("What should the new Curve in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(amuse::TableId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Table)), &ok); } if (!ok) { return; } ProjectModel::CurveNode* node = m_projectModel->newCurve(group, std::move(newName)); if (node) { openEditor(node); } } void MainWindow::newKeymapAction() { ProjectModel::GroupNode* group = getSelectedGroupNode(); m_projectModel->setIdDatabases(group); const QString groupName = getGroupName(group); QString newName; bool ok = true; while (ok && newName.isEmpty()) { newName = QInputDialog::getText( this, tr("New Keymap"), tr("What should the new Keymap in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(amuse::KeymapId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Keymap)), &ok); } if (!ok) { return; } ProjectModel::KeymapNode* node = m_projectModel->newKeymap(group, std::move(newName)); if (node) { openEditor(node); } } void MainWindow::newLayersAction() { ProjectModel::GroupNode* group = getSelectedGroupNode(); m_projectModel->setIdDatabases(group); const QString groupName = getGroupName(group); QString newName; bool ok = true; while (ok && newName.isEmpty()) { newName = QInputDialog::getText( this, tr("New Layers"), tr("What should the new Layers in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(amuse::LayersId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Layer)), &ok); } if (!ok) { return; } ProjectModel::LayersNode* node = m_projectModel->newLayers(group, std::move(newName)); if (node) { openEditor(node); } } void MainWindow::updateNavigationButtons() { m_goForward->setDisabled(m_navIt == m_navList.end() || std::next(m_navIt, 1) == m_navList.end()); m_goBack->setDisabled(m_navIt == m_navList.begin()); } void MainWindow::goForward() { if (m_navIt == m_navList.end() || std::next(m_navIt, 1) == m_navList.end()) return; ++m_navIt; openEditor(*m_navIt, false); updateNavigationButtons(); } void MainWindow::goBack() { if (m_navIt == m_navList.begin()) return; --m_navIt; openEditor(*m_navIt, false); updateNavigationButtons(); } void MainWindow::findUsages(ProjectModel::INode* node) { m_ui.projectOutlineFilter->setText(QStringLiteral("usages:") + node->name()); } void MainWindow::aboutToShowAudioIOMenu() { refreshAudioIO(); } void MainWindow::aboutToShowMIDIIOMenu() { refreshMIDIIO(); } void MainWindow::setAudioIO() { QByteArray devName = qobject_cast(sender())->data().toString().toUtf8(); if (m_voxEngine) m_voxEngine->setCurrentAudioOutput(devName.data()); } void MainWindow::setMIDIIO(bool checked) { QAction* action = qobject_cast(sender()); QByteArray devName = action->data().toString().toUtf8(); if (m_voxEngine) { MIDIReader* mr = static_cast(m_engine->getMIDIReader()); if (devName == "") { mr->setVirtualIn(checked); action->setChecked(mr->hasVirtualIn()); } else { if (checked) mr->addMIDIIn(devName.data()); else mr->removeMIDIIn(devName.data()); action->setChecked(mr->hasMIDIIn(devName.data())); } } } void MainWindow::aboutAmuseAction() { #ifdef Q_OS_MAC static QPointer oldMsgBox; if (oldMsgBox) { oldMsgBox->show(); oldMsgBox->raise(); oldMsgBox->activateWindow(); return; } #endif QString translatedTextAboutAmuseCaption; translatedTextAboutAmuseCaption = QMessageBox::tr("

About Amuse

"); QString translatedTextAboutAmuseText; translatedTextAboutAmuseText = QMessageBox::tr( "

Amuse is an alternate editor and runtime library for MusyX sound groups.

" "

MusyX originally served as a widely-deployed audio system for " "developing games on the Nintendo 64, GameCube, and GameBoy Advance.

" "

Amuse is available under the MIT license.
" "Please see " "https://gitlab.axiodl.com/AxioDL/amuse/blob/master/LICENSE for futher information.

" "

Copyright (C) 2015-2018 Antidote / Jackoalan.

" "

MusyX is a trademark of Factor 5, LLC.

" "

Nintendo 64, GameCube, and GameBoy Advance are trademarks of Nintendo Co., Ltd.

"); QMessageBox* msgBox = new QMessageBox(this); msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setWindowTitle(tr("About Amuse")); msgBox->setText(translatedTextAboutAmuseCaption); msgBox->setInformativeText(translatedTextAboutAmuseText); msgBox->setIconPixmap(windowIcon().pixmap(64, 64)); // should perhaps be a style hint #ifdef Q_OS_MAC oldMsgBox = msgBox; #if 0 // ### doesn't work until close button is enabled in title bar //msgBox->d_func()->autoAddOkButton = false; #else // msgBox->d_func()->buttonBox->setCenterButtons(true); #endif msgBox->show(); #else msgBox->exec(); #endif } void MainWindow::aboutQtAction() { QMessageBox::aboutQt(this); } void MainWindow::notePressed(int key) { if (m_engine) { if (m_lastSound) m_lastSound->keyOff(); m_lastSound = startEditorVoice(key, m_velocity); } } void MainWindow::noteReleased() { if (m_lastSound) { m_lastSound->keyOff(); m_lastSound.reset(); } } void MainWindow::velocityChanged(int vel) { m_velocity = vel; } void MainWindow::modulationChanged(int mod) { m_ctrlVals[1] = int8_t(mod); for (auto& v : m_engine->getActiveVoices()) v->setCtrlValue(1, m_ctrlVals[1]); } void MainWindow::pitchChanged(int pitch) { m_pitch = pitch / 2048.f; for (auto& v : m_engine->getActiveVoices()) v->setPitchWheel(m_pitch); } void MainWindow::killSounds() { for (auto& v : m_engine->getActiveVoices()) v->kill(); } void MainWindow::fxPressed() { if (m_studioSetup->isVisible()) m_studioSetup->hide(); else m_studioSetup->show(); } void MainWindow::volumeChanged(int vol) { m_engine->setVolume(vol / 100.f); } void MainWindow::auxAChanged(int vol) { m_auxAVol = vol / 100.f; for (auto& vox : m_engine->getActiveVoices()) vox->setReverbVol(m_auxAVol); for (auto& seq : m_engine->getActiveSequencers()) for (uint8_t i = 0; i < 16; ++i) seq->setCtrlValue(i, 0x5b, int8_t(m_auxAVol * 127.f)); } void MainWindow::auxBChanged(int vol) { m_auxBVol = vol / 100.f; for (auto& vox : m_engine->getActiveVoices()) vox->setAuxBVol(m_auxBVol); for (auto& seq : m_engine->getActiveSequencers()) for (uint8_t i = 0; i < 16; ++i) seq->setCtrlValue(i, 0x5d, int8_t(m_auxBVol * 127.f)); } void MainWindow::outlineCutAction() { if (!m_projectModel) return; m_projectModel->cut(m_projectModel->getOutlineProxy()->mapToSource(m_ui.projectOutline->currentIndex())); } void MainWindow::outlineCopyAction() { if (!m_projectModel) return; m_projectModel->copy(m_projectModel->getOutlineProxy()->mapToSource(m_ui.projectOutline->currentIndex())); } void MainWindow::outlinePasteAction() { if (!m_projectModel) return; m_projectModel->paste(m_projectModel->getOutlineProxy()->mapToSource(m_ui.projectOutline->currentIndex())); } void MainWindow::outlineDeleteAction() { if (!m_projectModel) return; m_projectModel->del(m_projectModel->getOutlineProxy()->mapToSource(m_ui.projectOutline->currentIndex())); } void MainWindow::onFocusChanged(QWidget* old, QWidget* now) { disconnect(m_cutConn); disconnect(m_copyConn); disconnect(m_pasteConn); disconnect(m_deleteConn); disconnect(m_canEditConn); if (QLineEdit* le = qobject_cast(now)) { m_cutConn = connect(m_ui.actionCut, &QAction::triggered, le, &QLineEdit::cut); m_ui.actionCut->setEnabled(le->hasSelectedText()); m_copyConn = connect(m_ui.actionCopy, &QAction::triggered, le, &QLineEdit::copy); m_ui.actionCopy->setEnabled(le->hasSelectedText()); m_pasteConn = connect(m_ui.actionPaste, &QAction::triggered, le, &QLineEdit::paste); m_ui.actionPaste->setEnabled(true); m_deleteConn = connect(m_ui.actionDelete, &QAction::triggered, this, &MainWindow::onTextDelete); m_ui.actionDelete->setEnabled(true); m_canEditConn = connect(le, &QLineEdit::selectionChanged, this, &MainWindow::onTextSelect); return; } if (now == m_ui.projectOutline || m_ui.projectOutline->isAncestorOf(now)) { AmuseItemEditFlags editFlags = outlineEditFlags(); if (!m_clipboardAmuseData) editFlags = AmuseItemEditFlags(editFlags & ~AmuseItemPaste); setItemEditFlags(editFlags); if (m_projectModel) { m_cutConn = connect(m_ui.actionCut, &QAction::triggered, this, &MainWindow::outlineCutAction); m_copyConn = connect(m_ui.actionCopy, &QAction::triggered, this, &MainWindow::outlineCopyAction); m_pasteConn = connect(m_ui.actionPaste, &QAction::triggered, this, &MainWindow::outlinePasteAction); m_deleteConn = connect(m_ui.actionDelete, &QAction::triggered, this, &MainWindow::outlineDeleteAction); } } else if (now == m_ui.editorContents || m_ui.editorContents->isAncestorOf(now)) { if (EditorWidget* editor = getEditorWidget()) { setItemEditFlags(editor->itemEditFlags()); m_cutConn = connect(m_ui.actionCut, &QAction::triggered, editor, &EditorWidget::itemCutAction); m_copyConn = connect(m_ui.actionCopy, &QAction::triggered, editor, &EditorWidget::itemCopyAction); m_pasteConn = connect(m_ui.actionPaste, &QAction::triggered, editor, &EditorWidget::itemPasteAction); m_deleteConn = connect(m_ui.actionDelete, &QAction::triggered, editor, &EditorWidget::itemDeleteAction); } else { setItemEditFlags(AmuseItemNone); } } } void MainWindow::onClipboardChanged() { if (QClipboard* cb = qobject_cast(sender())) { if (const QMimeData* md = cb->mimeData()) { for (const QString& str : md->formats()) { if (str.startsWith(QStringLiteral("application/x-amuse-"))) { m_clipboardAmuseData = true; updateFocus(); return; } } } } m_clipboardAmuseData = false; updateFocus(); } void MainWindow::outlineItemActivated(const QModelIndex& index) { ProjectModel::INode* node = m_projectModel->node(m_projectModel->getOutlineProxy()->mapToSource(index)); if (!node) return; openEditor(node); } void MainWindow::setItemEditFlags(AmuseItemEditFlags flags) { m_ui.actionCut->setEnabled(flags & AmuseItemCut); m_ui.actionCopy->setEnabled(flags & AmuseItemCopy); m_ui.actionPaste->setEnabled(flags & AmuseItemPaste); m_ui.actionDelete->setEnabled(flags & AmuseItemDelete); } void MainWindow::setItemNewEnabled(bool enabled) { m_ui.actionNew_SFX_Group->setEnabled(enabled); m_ui.actionNew_Song_Group->setEnabled(enabled); m_ui.actionNew_Sound_Macro->setEnabled(enabled); m_ui.actionNew_ADSR->setEnabled(enabled); m_ui.actionNew_Curve->setEnabled(enabled); m_ui.actionNew_Keymap->setEnabled(enabled); m_ui.actionNew_Layers->setEnabled(enabled); } AmuseItemEditFlags MainWindow::outlineEditFlags() { if (!m_projectModel) return AmuseItemNone; QModelIndex curIndex = m_ui.projectOutline->currentIndex(); if (!curIndex.isValid()) return AmuseItemNone; return m_projectModel->editFlags(m_projectModel->getOutlineProxy()->mapToSource(curIndex)); } void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { if (!m_projectModel) return; setItemNewEnabled(m_ui.projectOutline->currentIndex().isValid()); if (selected.indexes().empty()) setItemEditFlags(AmuseItemNone); else setItemEditFlags( m_projectModel->editFlags(m_projectModel->getOutlineProxy()->mapToSource(selected.indexes().front()))); } void MainWindow::onTextSelect() { if (QLineEdit* le = qobject_cast(sender())) { m_ui.actionCut->setEnabled(le->hasSelectedText()); m_ui.actionCopy->setEnabled(le->hasSelectedText()); } } void MainWindow::onTextDelete() { if (QLineEdit* le = qobject_cast(focusWidget())) { le->del(); } } void MainWindow::cleanChanged(bool clean) { setWindowModified(!clean); } void MainWindow::studioSetupHidden() { m_ui.statusbar->setFXDown(false); } void MainWindow::studioSetupShown() { m_ui.statusbar->setFXDown(true); } void MainWindow::onBackgroundTaskFinished(int id) { m_backgroundDialog->close(); m_backgroundDialog->reset(); m_backgroundDialog->deleteLater(); m_backgroundDialog = nullptr; m_backgroundTask->deleteLater(); m_backgroundTask = nullptr; if (id == TaskExport) { if (m_mainMessenger.question(tr("Export Complete"), tr("%1?").arg(ShowInGraphicalShellString())) == QMessageBox::Yes) { QFileInfo dirInfo(m_projectModel->dir(), QStringLiteral("out")); QDir dir(dirInfo.filePath()); QStringList entryList = dir.entryList(QDir::Files); ShowInGraphicalShell(this, entryList.empty() ? dirInfo.filePath() : QFileInfo(dir, entryList.first()).filePath()); } } else { bool hasGroups = m_projectModel->ensureModelData(); m_ui.actionImport_Groups->setDisabled(hasGroups); m_ui.actionImport_Songs->setEnabled(hasGroups); } setEnabled(true); } static int ShowOldMessageBox(QWidget* parent, QMessageBox::Icon icon, const QString& title, const QString& text, const QString& button0Text, const QString& button1Text, const QString& button2Text, int defaultButtonNumber, int escapeButtonNumber) { QMessageBox messageBox(icon, title, text, QMessageBox::NoButton, parent); messageBox.setWindowModality(Qt::WindowModal); QString myButton0Text = button0Text; if (myButton0Text.isEmpty()) myButton0Text = QDialogButtonBox::tr("OK"); messageBox.addButton(myButton0Text, QMessageBox::ActionRole); if (!button1Text.isEmpty()) messageBox.addButton(button1Text, QMessageBox::ActionRole); if (!button2Text.isEmpty()) messageBox.addButton(button2Text, QMessageBox::ActionRole); const QList& buttonList = messageBox.buttons(); messageBox.setDefaultButton(static_cast(buttonList.value(defaultButtonNumber))); messageBox.setEscapeButton(buttonList.value(escapeButtonNumber)); return messageBox.exec(); } static QMessageBox::StandardButton ShowNewMessageBox(QWidget* parent, QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { QMessageBox msgBox(icon, title, text, QMessageBox::NoButton, parent); msgBox.setWindowModality(Qt::WindowModal); QDialogButtonBox* buttonBox = msgBox.findChild(); Q_ASSERT(buttonBox != 0); uint mask = QMessageBox::FirstButton; while (mask <= QMessageBox::LastButton) { uint sb = buttons & mask; mask <<= 1; if (!sb) continue; QPushButton* button = msgBox.addButton((QMessageBox::StandardButton)sb); // Choose the first accept role as the default if (msgBox.defaultButton()) continue; if ((defaultButton == QMessageBox::NoButton && buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) || (defaultButton != QMessageBox::NoButton && sb == uint(defaultButton))) msgBox.setDefaultButton(button); } if (msgBox.exec() == -1) return QMessageBox::Cancel; return msgBox.standardButton(msgBox.clickedButton()); } QMessageBox::StandardButton MainWindow::msgInformation(const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { return ShowNewMessageBox(this, QMessageBox::Information, title, text, buttons, defaultButton); } int MainWindow::msgQuestion(const QString& title, const QString& text, const QString& button0Text, const QString& button1Text, const QString& button2Text, int defaultButtonNumber, int escapeButtonNumber) { return ShowOldMessageBox(this, QMessageBox::Question, title, text, button0Text, button1Text, button2Text, defaultButtonNumber, escapeButtonNumber); } QMessageBox::StandardButton MainWindow::msgQuestion(const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { return ShowNewMessageBox(this, QMessageBox::Question, title, text, buttons, defaultButton); } QMessageBox::StandardButton MainWindow::msgWarning(const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { return ShowNewMessageBox(this, QMessageBox::Warning, title, text, buttons, defaultButton); } QMessageBox::StandardButton MainWindow::msgCritical(const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { return ShowNewMessageBox(this, QMessageBox::Critical, title, text, buttons, defaultButton); }