--- qsampler/trunk/src/qsamplerMainForm.cpp 2007/11/23 18:15:33 1515 +++ qsampler/trunk/src/qsamplerMainForm.cpp 2021/01/07 16:18:02 3849 @@ -1,8 +1,8 @@ // qsamplerMainForm.cpp // /**************************************************************************** - Copyright (C) 2004-2007, rncbc aka Rui Nuno Capela. All rights reserved. - Copyright (C) 2007, Christian Schoenebeck + Copyright (C) 2004-2021, rncbc aka Rui Nuno Capela. All rights reserved. + Copyright (C) 2007-2019 Christian Schoenebeck This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -33,13 +33,19 @@ #include "qsamplerInstrumentListForm.h" #include "qsamplerDeviceForm.h" #include "qsamplerOptionsForm.h" +#include "qsamplerDeviceStatusForm.h" + +#include "qsamplerPaletteForm.h" + +#include + +#include +#include #include -#include #include #include -#include #include #include #include @@ -55,19 +61,34 @@ #include #include +#include -#ifdef HAVE_SIGNAL_H -#include +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +#include +#endif + +#if QT_VERSION < QT_VERSION_CHECK(4, 5, 0) +namespace Qt { +const WindowFlags WindowCloseButtonHint = WindowFlags(0x08000000); +} #endif #ifdef CONFIG_LIBGIG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" #include +#pragma GCC diagnostic pop #endif -// Needed for lroundf() -#include +// Deprecated QTextStreamFunctions/Qt namespaces workaround. +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) +#define endl Qt::endl +#endif -#ifndef CONFIG_ROUND +// Needed for lroundf() +#ifdef CONFIG_ROUND +#include +#else static inline long lroundf ( float x ) { if (x >= 0.0f) @@ -77,6 +98,57 @@ } #endif + +// All winsock apps needs this. +#if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) +static WSADATA _wsaData; +#undef HAVE_SIGNAL_H +#endif + + +//------------------------------------------------------------------------- +// LADISH Level 1 support stuff. + +#if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H) + +#include + +#include +#include +#include +#include + +// File descriptor for SIGUSR1 notifier. +static int g_fdSigusr1[2] = { -1, -1 }; + +// Unix SIGUSR1 signal handler. +static void qsampler_sigusr1_handler ( int /* signo */ ) +{ + char c = 1; + + (::write(g_fdSigusr1[0], &c, sizeof(c)) > 0); +} + +// File descriptor for SIGTERM notifier. +static int g_fdSigterm[2] = { -1, -1 }; + +// Unix SIGTERM signal handler. +static void qsampler_sigterm_handler ( int /* signo */ ) +{ + char c = 1; + + (::write(g_fdSigterm[0], &c, sizeof(c)) > 0); +} + +#endif // HAVE_SIGNAL_H + + +//------------------------------------------------------------------------- +// QSampler -- namespace + + +namespace QSampler { + // Timer constant stuff. #define QSAMPLER_TIMER_MSECS 200 @@ -87,32 +159,28 @@ #define QSAMPLER_STATUS_SESSION 3 // Current session modification state. -// All winsock apps needs this. -#if defined(WIN32) -static WSADATA _wsaData; -#endif +// Specialties for thread-callback comunication. +#define QSAMPLER_LSCP_EVENT QEvent::Type(QEvent::User + 1) //------------------------------------------------------------------------- -// qsamplerCustomEvent -- specialty for callback comunication. - -#define QSAMPLER_CUSTOM_EVENT QEvent::Type(QEvent::User + 0) +// QSampler::LscpEvent -- specialty for LSCP callback comunication. -class qsamplerCustomEvent : public QEvent +class LscpEvent : public QEvent { public: // Constructor. - qsamplerCustomEvent(lscp_event_t event, const char *pchData, int cchData) - : QEvent(QSAMPLER_CUSTOM_EVENT) + LscpEvent(lscp_event_t event, const char *pchData, int cchData) + : QEvent(QSAMPLER_LSCP_EVENT) { m_event = event; m_data = QString::fromUtf8(pchData, cchData); } // Accessors. - lscp_event_t event() { return m_event; } - QString& data() { return m_data; } + lscp_event_t event() { return m_event; } + const QString& data() { return m_data; } private: @@ -124,12 +192,30 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- Main window form implementation. +// QSampler::Workspace -- Main window workspace (MDI Area) decl. -namespace QSampler { +class Workspace : public QMdiArea +{ +public: + + Workspace(MainForm *pMainForm) : QMdiArea(pMainForm) {} + +protected: + + void resizeEvent(QResizeEvent *) + { + MainForm *pMainForm = static_cast (parentWidget()); + if (pMainForm) + pMainForm->channelsArrangeAuto(); + } +}; + + +//------------------------------------------------------------------------- +// QSampler::MainForm -- Main window form implementation. // Kind of singleton reference. -MainForm* MainForm::g_pMainForm = NULL; +MainForm *MainForm::g_pMainForm = nullptr; MainForm::MainForm ( QWidget *pParent ) : QMainWindow(pParent) @@ -140,29 +226,78 @@ g_pMainForm = this; // Initialize some pointer references. - m_pOptions = NULL; + m_pOptions = nullptr; // All child forms are to be created later, not earlier than setup. - m_pMessages = NULL; - m_pInstrumentListForm = NULL; - m_pDeviceForm = NULL; + m_pMessages = nullptr; + m_pInstrumentListForm = nullptr; + m_pDeviceForm = nullptr; // We'll start clean. m_iUntitled = 0; + m_iDirtySetup = 0; m_iDirtyCount = 0; - m_pServer = NULL; - m_pClient = NULL; + m_pServer = nullptr; + m_pClient = nullptr; m_iStartDelay = 0; m_iTimerDelay = 0; m_iTimerSlot = 0; -#ifdef HAVE_SIGNAL_H +#if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H) + // Set to ignore any fatal "Broken pipe" signals. ::signal(SIGPIPE, SIG_IGN); -#endif + + // LADISH Level 1 suport. + + // Initialize file descriptors for SIGUSR1 socket notifier. + ::socketpair(AF_UNIX, SOCK_STREAM, 0, g_fdSigusr1); + m_pSigusr1Notifier + = new QSocketNotifier(g_fdSigusr1[1], QSocketNotifier::Read, this); + + QObject::connect(m_pSigusr1Notifier, + SIGNAL(activated(int)), + SLOT(handle_sigusr1())); + + // Install SIGUSR1 signal handler. + struct sigaction sigusr1; + sigusr1.sa_handler = qsampler_sigusr1_handler; + sigemptyset(&sigusr1.sa_mask); + sigusr1.sa_flags = 0; + sigusr1.sa_flags |= SA_RESTART; + ::sigaction(SIGUSR1, &sigusr1, nullptr); + + // Initialize file descriptors for SIGTERM socket notifier. + ::socketpair(AF_UNIX, SOCK_STREAM, 0, g_fdSigterm); + m_pSigtermNotifier + = new QSocketNotifier(g_fdSigterm[1], QSocketNotifier::Read, this); + + QObject::connect(m_pSigtermNotifier, + SIGNAL(activated(int)), + SLOT(handle_sigterm())); + + // Install SIGTERM signal handler. + struct sigaction sigterm; + sigterm.sa_handler = qsampler_sigterm_handler; + sigemptyset(&sigterm.sa_mask); + sigterm.sa_flags = 0; + sigterm.sa_flags |= SA_RESTART; + ::sigaction(SIGTERM, &sigterm, nullptr); + ::sigaction(SIGQUIT, &sigterm, nullptr); + + // Ignore SIGHUP/SIGINT signals. + ::signal(SIGHUP, SIG_IGN); + ::signal(SIGINT, SIG_IGN); + +#else // HAVE_SIGNAL_H + + m_pSigusr1Notifier = nullptr; + m_pSigtermNotifier = nullptr; + +#endif // !HAVE_SIGNAL_H #ifdef CONFIG_VOLUME // Make some extras into the toolbar... @@ -183,13 +318,10 @@ QObject::connect(m_pVolumeSlider, SIGNAL(valueChanged(int)), SLOT(volumeChanged(int))); - //m_ui.channelsToolbar->setHorizontallyStretchable(true); - //m_ui.channelsToolbar->setStretchableWidget(m_pVolumeSlider); m_ui.channelsToolbar->addWidget(m_pVolumeSlider); // Volume spin-box m_ui.channelsToolbar->addSeparator(); m_pVolumeSpinBox = new QSpinBox(m_ui.channelsToolbar); - m_pVolumeSpinBox->setMaximumHeight(24); m_pVolumeSpinBox->setSuffix(" %"); m_pVolumeSpinBox->setMinimum(0); m_pVolumeSpinBox->setMaximum(100); @@ -201,12 +333,13 @@ #endif // Make it an MDI workspace. - m_pWorkspace = new QWorkspace(this); - m_pWorkspace->setScrollBarsEnabled(true); + m_pWorkspace = new Workspace(this); + m_pWorkspace->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_pWorkspace->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); // Set the activation connection. QObject::connect(m_pWorkspace, - SIGNAL(windowActivated(QWidget *)), - SLOT(activateStrip(QWidget *))); + SIGNAL(subWindowActivated(QMdiSubWindow *)), + SLOT(activateStrip(QMdiSubWindow *))); // Make it shine :-) setCentralWidget(m_pWorkspace); @@ -235,7 +368,7 @@ m_statusItem[QSAMPLER_STATUS_SESSION] = pLabel; statusBar()->addWidget(pLabel); -#if defined(WIN32) +#if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) WSAStartup(MAKEWORD(1, 1), &_wsaData); #endif @@ -323,6 +456,11 @@ QObject::connect(m_ui.channelsMenu, SIGNAL(aboutToShow()), SLOT(channelsMenuAboutToShow())); +#ifdef CONFIG_VOLUME + QObject::connect(m_ui.channelsToolbar, + SIGNAL(orientationChanged(Qt::Orientation)), + SLOT(channelsToolbarOrientation(Qt::Orientation))); +#endif } // Destructor. @@ -331,10 +469,17 @@ // Do final processing anyway. processServerExit(); -#if defined(WIN32) +#if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) WSACleanup(); #endif +#if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H) + if (m_pSigusr1Notifier) + delete m_pSigusr1Notifier; + if (m_pSigtermNotifier) + delete m_pSigtermNotifier; +#endif + // Finally drop any widgets around... if (m_pDeviceForm) delete m_pDeviceForm; @@ -361,38 +506,45 @@ #endif // Pseudo-singleton reference shut-down. - g_pMainForm = NULL; + g_pMainForm = nullptr; } // Make and set a proper setup options step. -void MainForm::setup ( qsamplerOptions *pOptions ) +void MainForm::setup ( Options *pOptions ) { // We got options? m_pOptions = pOptions; // What style do we create these forms? Qt::WindowFlags wflags = Qt::Window -#if QT_VERSION >= 0x040200 | Qt::CustomizeWindowHint -#endif | Qt::WindowTitleHint | Qt::WindowSystemMenuHint - | Qt::WindowMinMaxButtonsHint; + | Qt::WindowMinMaxButtonsHint + | Qt::WindowCloseButtonHint; if (m_pOptions->bKeepOnTop) wflags |= Qt::Tool; + // Some child forms are to be created right now. - m_pMessages = new qsamplerMessages(this); + m_pMessages = new Messages(this); m_pDeviceForm = new DeviceForm(this, wflags); #ifdef CONFIG_MIDI_INSTRUMENT m_pInstrumentListForm = new InstrumentListForm(this, wflags); #else - viewInstrumentsAction->setEnabled(false); + m_ui.viewInstrumentsAction->setEnabled(false); #endif + + // Setup messages logging appropriately... + m_pMessages->setLogging( + m_pOptions->bMessagesLog, + m_pOptions->sMessagesLogPath); + // Set message defaults... updateMessagesFont(); updateMessagesLimit(); updateMessagesCapture(); + // Set the visibility signal. QObject::connect(m_pMessages, SIGNAL(visibilityChanged(bool)), @@ -419,7 +571,7 @@ } // Try to restore old window positioning and initial visibility. - m_pOptions->loadWidgetGeometry(this); + m_pOptions->loadWidgetGeometry(this, true); m_pOptions->loadWidgetGeometry(m_pInstrumentListForm); m_pOptions->loadWidgetGeometry(m_pDeviceForm); @@ -458,19 +610,18 @@ || m_ui.channelsToolbar->isVisible()); m_pOptions->bStatusbar = statusBar()->isVisible(); // Save the dock windows state. - const QString sDockables = saveState().toBase64().data(); m_pOptions->settings().setValue("/Layout/DockWindows", saveState()); // And the children, and the main windows state,. m_pOptions->saveWidgetGeometry(m_pDeviceForm); m_pOptions->saveWidgetGeometry(m_pInstrumentListForm); - m_pOptions->saveWidgetGeometry(this); + m_pOptions->saveWidgetGeometry(this, true); // Close popup widgets. if (m_pInstrumentListForm) m_pInstrumentListForm->close(); if (m_pDeviceForm) m_pDeviceForm->close(); // Stop client and/or server, gracefully. - stopServer(); + stopServer(true /*interactive*/); } } @@ -480,9 +631,10 @@ void MainForm::closeEvent ( QCloseEvent *pCloseEvent ) { - if (queryClose()) + if (queryClose()) { + DeviceStatusForm::deleteAllInstances(); pCloseEvent->accept(); - else + } else pCloseEvent->ignore(); } @@ -491,7 +643,7 @@ void MainForm::dragEnterEvent ( QDragEnterEvent* pDragEnterEvent ) { // Accept external drags only... - if (pDragEnterEvent->source() == NULL + if (pDragEnterEvent->source() == nullptr && pDragEnterEvent->mimeData()->hasUrls()) { pDragEnterEvent->accept(); } else { @@ -500,7 +652,7 @@ } -void MainForm::dropEvent ( QDropEvent* pDropEvent ) +void MainForm::dropEvent ( QDropEvent *pDropEvent ) { // Accept externally originated drops only... if (pDropEvent->source()) @@ -511,10 +663,11 @@ QListIterator iter(pMimeData->urls()); while (iter.hasNext()) { const QString& sPath = iter.next().toLocalFile(); - if (qsamplerChannel::isInstrumentFile(sPath)) { + // if (Channel::isDlsInstrumentFile(sPath)) { + if (QFileInfo(sPath).exists()) { // Try to create a new channel from instrument file... - qsamplerChannel *pChannel = new qsamplerChannel(); - if (pChannel == NULL) + Channel *pChannel = new Channel(); + if (pChannel == nullptr) return; // Start setting the instrument filename... pChannel->setInstrument(sPath, 0); @@ -545,24 +698,110 @@ // Custome event handler. -void MainForm::customEvent(QEvent* pCustomEvent) +void MainForm::customEvent ( QEvent* pEvent ) { // For the time being, just pump it to messages. - if (pCustomEvent->type() == QSAMPLER_CUSTOM_EVENT) { - qsamplerCustomEvent *pEvent = (qsamplerCustomEvent *) pCustomEvent; - if (pEvent->event() == LSCP_EVENT_CHANNEL_INFO) { - int iChannelID = pEvent->data().toInt(); - ChannelStrip *pChannelStrip = channelStrip(iChannelID); - if (pChannelStrip) - channelStripChanged(pChannelStrip); - } else { - appendMessagesColor(tr("Notify event: %1 data: %2") - .arg(::lscp_event_to_text(pEvent->event())) - .arg(pEvent->data()), "#996699"); + if (pEvent->type() == QSAMPLER_LSCP_EVENT) { + LscpEvent *pLscpEvent = static_cast (pEvent); + switch (pLscpEvent->event()) { + case LSCP_EVENT_CHANNEL_COUNT: + updateAllChannelStrips(true); + break; + case LSCP_EVENT_CHANNEL_INFO: { + const int iChannelID = pLscpEvent->data().toInt(); + ChannelStrip *pChannelStrip = channelStrip(iChannelID); + if (pChannelStrip) + channelStripChanged(pChannelStrip); + break; + } + case LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT: + if (m_pDeviceForm) m_pDeviceForm->refreshDevices(); + DeviceStatusForm::onDevicesChanged(); + updateViewMidiDeviceStatusMenu(); + break; + case LSCP_EVENT_MIDI_INPUT_DEVICE_INFO: { + if (m_pDeviceForm) m_pDeviceForm->refreshDevices(); + const int iDeviceID = pLscpEvent->data().section(' ', 0, 0).toInt(); + DeviceStatusForm::onDeviceChanged(iDeviceID); + break; + } + case LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT: + if (m_pDeviceForm) m_pDeviceForm->refreshDevices(); + break; + case LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO: + if (m_pDeviceForm) m_pDeviceForm->refreshDevices(); + break; + #if CONFIG_EVENT_CHANNEL_MIDI + case LSCP_EVENT_CHANNEL_MIDI: { + const int iChannelID = pLscpEvent->data().section(' ', 0, 0).toInt(); + ChannelStrip *pChannelStrip = channelStrip(iChannelID); + if (pChannelStrip) + pChannelStrip->midiActivityLedOn(); + break; + } + #endif + #if CONFIG_EVENT_DEVICE_MIDI + case LSCP_EVENT_DEVICE_MIDI: { + const int iDeviceID = pLscpEvent->data().section(' ', 0, 0).toInt(); + const int iPortID = pLscpEvent->data().section(' ', 1, 1).toInt(); + DeviceStatusForm *pDeviceStatusForm + = DeviceStatusForm::getInstance(iDeviceID); + if (pDeviceStatusForm) + pDeviceStatusForm->midiArrived(iPortID); + break; + } + #endif + default: + appendMessagesColor(tr("LSCP Event: %1 data: %2") + .arg(::lscp_event_to_text(pLscpEvent->event())) + .arg(pLscpEvent->data()), "#996699"); } } } + +// LADISH Level 1 -- SIGUSR1 signal handler. +void MainForm::handle_sigusr1 (void) +{ +#if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H) + + char c; + + if (::read(g_fdSigusr1[1], &c, sizeof(c)) > 0) + saveSession(false); + +#endif +} + + +void MainForm::handle_sigterm (void) +{ +#if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H) + + char c; + + if (::read(g_fdSigterm[1], &c, sizeof(c)) > 0) + close(); + +#endif +} + + +void MainForm::updateViewMidiDeviceStatusMenu (void) +{ + m_ui.viewMidiDeviceStatusMenu->clear(); + const std::map statusForms + = DeviceStatusForm::getInstances(); + std::map::const_iterator iter + = statusForms.begin(); + for ( ; iter != statusForms.end(); ++iter) { + DeviceStatusForm *pStatusForm = iter->second; + m_ui.viewMidiDeviceStatusMenu->addAction( + pStatusForm->visibleAction()); + } +} + + // Context menu event handler. void MainForm::contextMenuEvent( QContextMenuEvent *pEvent ) { @@ -573,10 +812,10 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- Brainless public property accessors. +// QSampler::MainForm -- Brainless public property accessors. // The global options settings property. -qsamplerOptions *MainForm::options (void) const +Options *MainForm::options (void) const { return m_pOptions; } @@ -597,12 +836,12 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- Session file stuff. +// QSampler::MainForm -- Session file stuff. // Format the displayable session filename. QString MainForm::sessionName ( const QString& sFilename ) { - bool bCompletePath = (m_pOptions && m_pOptions->bCompletePath); + const bool bCompletePath = (m_pOptions && m_pOptions->bCompletePath); QString sSessionName = sFilename; if (sSessionName.isEmpty()) sSessionName = tr("Untitled") + QString::number(m_iUntitled); @@ -626,7 +865,7 @@ m_iUntitled++; // Stabilize form. - m_sFilename = QString::null; + m_sFilename = QString(); m_iDirtyCount = 0; appendMessages(tr("New session: \"%1\".").arg(sessionName(m_sFilename))); stabilizeForm(); @@ -638,12 +877,12 @@ // Open an existing sampler session. bool MainForm::openSession (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return false; // Ask for the filename to open... QString sFilename = QFileDialog::getOpenFileName(this, - QSAMPLER_TITLE ": " + tr("Open Session"), // Caption. + tr("Open Session"), // Caption. m_pOptions->sSessionDir, // Start here. tr("LSCP Session files") + " (*.lscp)" // Filter (LSCP files) ); @@ -664,7 +903,7 @@ // Save current sampler session with another name. bool MainForm::saveSession ( bool bPrompt ) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return false; QString sFilename = m_sFilename; @@ -676,7 +915,7 @@ sFilename = m_pOptions->sSessionDir; // Prompt the guy... sFilename = QFileDialog::getSaveFileName(this, - QSAMPLER_TITLE ": " + tr("Save Session"), // Caption. + tr("Save Session"), // Caption. sFilename, // Start here. tr("LSCP Session files") + " (*.lscp)" // Filter (LSCP files) ); @@ -686,17 +925,20 @@ // Enforce .lscp extension... if (QFileInfo(sFilename).suffix().isEmpty()) sFilename += ".lscp"; + #if 0 // Check if already exists... if (sFilename != m_sFilename && QFileInfo(sFilename).exists()) { if (QMessageBox::warning(this, - QSAMPLER_TITLE ": " + tr("Warning"), + tr("Warning"), tr("The file already exists:\n\n" "\"%1\"\n\n" "Do you want to replace it?") .arg(sFilename), - tr("Replace"), tr("Cancel")) > 0) + QMessageBox::Yes | QMessageBox::No) + == QMessageBox::No) return false; } + #endif } // Save it right away. @@ -712,16 +954,18 @@ // Are we dirty enough to prompt it? if (m_iDirtyCount > 0) { switch (QMessageBox::warning(this, - QSAMPLER_TITLE ": " + tr("Warning"), + tr("Warning"), tr("The current session has been changed:\n\n" "\"%1\"\n\n" "Do you want to save the changes?") .arg(sessionName(m_sFilename)), - tr("Save"), tr("Discard"), tr("Cancel"))) { - case 0: // Save... + QMessageBox::Save | + QMessageBox::Discard | + QMessageBox::Cancel)) { + case QMessageBox::Save: bClose = saveSession(false); // Fall thru.... - case 1: // Discard + case QMessageBox::Discard: break; default: // Cancel. bClose = false; @@ -733,15 +977,18 @@ if (bClose) { // Remove all channel strips from sight... m_pWorkspace->setUpdatesEnabled(false); - QWidgetList wlist = m_pWorkspace->windowList(); - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip *pChannelStrip = (ChannelStrip*) wlist.at(iChannel); + const QList& wlist + = m_pWorkspace->subWindowList(); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) { - qsamplerChannel *pChannel = pChannelStrip->channel(); + Channel *pChannel = pChannelStrip->channel(); if (bForce && pChannel) pChannel->removeChannel(); delete pChannelStrip; } + delete pMdiSubWindow; } m_pWorkspace->setUpdatesEnabled(true); // We're now clean, for sure. @@ -755,7 +1002,7 @@ // Load a session from specific file path. bool MainForm::loadSessionFile ( const QString& sFilename ) { - if (m_pClient == NULL) + if (m_pClient == nullptr) return false; // Open and read from real file. @@ -831,7 +1078,7 @@ // Save current session to specific file path. bool MainForm::saveSessionFile ( const QString& sFilename ) { - if (m_pClient == NULL) + if (m_pClient == nullptr) return false; // Check whether server is apparently OK... @@ -853,13 +1100,11 @@ QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // Write the file. - int iErrors = 0; + int iErrors = 0; QTextStream ts(&file); ts << "# " << QSAMPLER_TITLE " - " << tr(QSAMPLER_SUBTITLE) << endl; - ts << "# " << tr("Version") - << ": " QSAMPLER_VERSION << endl; - ts << "# " << tr("Build") - << ": " __DATE__ " " __TIME__ << endl; + ts << "# " << tr("Version") << ": " CONFIG_BUILD_VERSION << endl; +// ts << "# " << tr("Build") << ": " CONFIG_BUILD_DATE << endl; ts << "#" << endl; ts << "# " << tr("File") << ": " << QFileInfo(sFilename).fileName() << endl; @@ -872,38 +1117,41 @@ // It is assumed that this new kind of device+session file // will be loaded from a complete initialized server... int *piDeviceIDs; - int iDevice; + int i, iDevice; ts << "RESET" << endl; // Audio device mapping. - QMap audioDeviceMap; - piDeviceIDs = qsamplerDevice::getDevices(m_pClient, qsamplerDevice::Audio); - for (iDevice = 0; piDeviceIDs && piDeviceIDs[iDevice] >= 0; iDevice++) { - ts << endl; - qsamplerDevice device(qsamplerDevice::Audio, piDeviceIDs[iDevice]); + QMap audioDeviceMap; iDevice = 0; + piDeviceIDs = Device::getDevices(m_pClient, Device::Audio); + for (i = 0; piDeviceIDs && piDeviceIDs[i] >= 0; ++i) { + Device device(Device::Audio, piDeviceIDs[i]); + // Avoid plug-in driver devices... + if (device.driverName().toUpper() == "PLUGIN") + continue; // Audio device specification... + ts << endl; ts << "# " << device.deviceTypeName() << " " << device.driverName() << " " << tr("Device") << " " << iDevice << endl; ts << "CREATE AUDIO_OUTPUT_DEVICE " << device.driverName(); - qsamplerDeviceParamMap::ConstIterator deviceParam; + DeviceParamMap::ConstIterator deviceParam; for (deviceParam = device.params().begin(); deviceParam != device.params().end(); ++deviceParam) { - const qsamplerDeviceParam& param = deviceParam.value(); + const DeviceParam& param = deviceParam.value(); if (param.value.isEmpty()) ts << "# "; ts << " " << deviceParam.key() << "='" << param.value << "'"; } ts << endl; // Audio channel parameters... int iPort = 0; - QListIterator iter(device.ports()); + QListIterator iter(device.ports()); while (iter.hasNext()) { - qsamplerDevicePort *pPort = iter.next(); - qsamplerDeviceParamMap::ConstIterator portParam; + DevicePort *pPort = iter.next(); + DeviceParamMap::ConstIterator portParam; for (portParam = pPort->params().begin(); portParam != pPort->params().end(); ++portParam) { - const qsamplerDeviceParam& param = portParam.value(); + const DeviceParam& param = portParam.value(); if (param.fix || param.value.isEmpty()) ts << "# "; ts << "SET AUDIO_OUTPUT_CHANNEL_PARAMETER " << iDevice << " " << iPort << " " << portParam.key() @@ -912,40 +1160,43 @@ iPort++; } // Audio device index/id mapping. - audioDeviceMap[device.deviceID()] = iDevice; + audioDeviceMap.insert(device.deviceID(), iDevice++); // Try to keep it snappy :) QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } // MIDI device mapping. - QMap midiDeviceMap; - piDeviceIDs = qsamplerDevice::getDevices(m_pClient, qsamplerDevice::Midi); - for (iDevice = 0; piDeviceIDs && piDeviceIDs[iDevice] >= 0; iDevice++) { - ts << endl; - qsamplerDevice device(qsamplerDevice::Midi, piDeviceIDs[iDevice]); + QMap midiDeviceMap; iDevice = 0; + piDeviceIDs = Device::getDevices(m_pClient, Device::Midi); + for (i = 0; piDeviceIDs && piDeviceIDs[i] >= 0; ++i) { + Device device(Device::Midi, piDeviceIDs[i]); + // Avoid plug-in driver devices... + if (device.driverName().toUpper() == "PLUGIN") + continue; // MIDI device specification... + ts << endl; ts << "# " << device.deviceTypeName() << " " << device.driverName() << " " << tr("Device") << " " << iDevice << endl; ts << "CREATE MIDI_INPUT_DEVICE " << device.driverName(); - qsamplerDeviceParamMap::ConstIterator deviceParam; + DeviceParamMap::ConstIterator deviceParam; for (deviceParam = device.params().begin(); deviceParam != device.params().end(); ++deviceParam) { - const qsamplerDeviceParam& param = deviceParam.value(); + const DeviceParam& param = deviceParam.value(); if (param.value.isEmpty()) ts << "# "; ts << " " << deviceParam.key() << "='" << param.value << "'"; } ts << endl; // MIDI port parameters... int iPort = 0; - QListIterator iter(device.ports()); + QListIterator iter(device.ports()); while (iter.hasNext()) { - qsamplerDevicePort *pPort = iter.next(); - qsamplerDeviceParamMap::ConstIterator portParam; + DevicePort *pPort = iter.next(); + DeviceParamMap::ConstIterator portParam; for (portParam = pPort->params().begin(); portParam != pPort->params().end(); ++portParam) { - const qsamplerDeviceParam& param = portParam.value(); + const DeviceParam& param = portParam.value(); if (param.fix || param.value.isEmpty()) ts << "# "; ts << "SET MIDI_INPUT_PORT_PARAMETER " << iDevice << " " << iPort << " " << portParam.key() @@ -954,7 +1205,7 @@ iPort++; } // MIDI device index/id mapping. - midiDeviceMap[device.deviceID()] = iDevice; + midiDeviceMap.insert(device.deviceID(), iDevice++); // Try to keep it snappy :) QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } @@ -965,7 +1216,7 @@ QMap midiInstrumentMap; int *piMaps = ::lscp_list_midi_instrument_maps(m_pClient); for (int iMap = 0; piMaps && piMaps[iMap] >= 0; iMap++) { - int iMidiMap = piMaps[iMap]; + const int iMidiMap = piMaps[iMap]; const char *pszMapName = ::lscp_get_midi_instrument_map_name(m_pClient, iMidiMap); ts << "# " << tr("MIDI instrument map") << " " << iMap; @@ -1017,80 +1268,90 @@ } ts << endl; // Check for errors... - if (pInstrs == NULL && ::lscp_client_get_errno(m_pClient)) { + if (pInstrs == nullptr && ::lscp_client_get_errno(m_pClient)) { appendMessagesClient("lscp_list_midi_instruments"); iErrors++; } // MIDI strument index/id mapping. - midiInstrumentMap[iMidiMap] = iMap; + midiInstrumentMap.insert(iMidiMap, iMap); } // Check for errors... - if (piMaps == NULL && ::lscp_client_get_errno(m_pClient)) { + if (piMaps == nullptr && ::lscp_client_get_errno(m_pClient)) { appendMessagesClient("lscp_list_midi_instrument_maps"); iErrors++; } #endif // CONFIG_MIDI_INSTRUMENT - // Sampler channel mapping. - QWidgetList wlist = m_pWorkspace->windowList(); - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip* pChannelStrip - = static_cast (wlist.at(iChannel)); + // Sampler channel mapping... + int iChannelID = 0; + const QList& wlist + = m_pWorkspace->subWindowList(); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) { - qsamplerChannel *pChannel = pChannelStrip->channel(); + Channel *pChannel = pChannelStrip->channel(); if (pChannel) { - ts << "# " << tr("Channel") << " " << iChannel << endl; + // Avoid "artifial" plug-in devices... + const int iAudioDevice = pChannel->audioDevice(); + if (!audioDeviceMap.contains(iAudioDevice)) + continue; + const int iMidiDevice = pChannel->midiDevice(); + if (!midiDeviceMap.contains(iMidiDevice)) + continue; + // Go for regular, canonical devices... + ts << "# " << tr("Channel") << " " << iChannelID << endl; ts << "ADD CHANNEL" << endl; if (audioDeviceMap.isEmpty()) { - ts << "SET CHANNEL AUDIO_OUTPUT_TYPE " << iChannel + ts << "SET CHANNEL AUDIO_OUTPUT_TYPE " << iChannelID << " " << pChannel->audioDriver() << endl; } else { - ts << "SET CHANNEL AUDIO_OUTPUT_DEVICE " << iChannel - << " " << audioDeviceMap[pChannel->audioDevice()] << endl; + ts << "SET CHANNEL AUDIO_OUTPUT_DEVICE " << iChannelID + << " " << audioDeviceMap.value(iAudioDevice) << endl; } if (midiDeviceMap.isEmpty()) { - ts << "SET CHANNEL MIDI_INPUT_TYPE " << iChannel + ts << "SET CHANNEL MIDI_INPUT_TYPE " << iChannelID << " " << pChannel->midiDriver() << endl; } else { - ts << "SET CHANNEL MIDI_INPUT_DEVICE " << iChannel - << " " << midiDeviceMap[pChannel->midiDevice()] << endl; + ts << "SET CHANNEL MIDI_INPUT_DEVICE " << iChannelID + << " " << midiDeviceMap.value(iMidiDevice) << endl; } - ts << "SET CHANNEL MIDI_INPUT_PORT " << iChannel + ts << "SET CHANNEL MIDI_INPUT_PORT " << iChannelID << " " << pChannel->midiPort() << endl; - ts << "SET CHANNEL MIDI_INPUT_CHANNEL " << iChannel << " "; + ts << "SET CHANNEL MIDI_INPUT_CHANNEL " << iChannelID << " "; if (pChannel->midiChannel() == LSCP_MIDI_CHANNEL_ALL) ts << "ALL"; else ts << pChannel->midiChannel(); ts << endl; ts << "LOAD ENGINE " << pChannel->engineName() - << " " << iChannel << endl; + << " " << iChannelID << endl; if (pChannel->instrumentStatus() < 100) ts << "# "; ts << "LOAD INSTRUMENT NON_MODAL '" << pChannel->instrumentFile() << "' " - << pChannel->instrumentNr() << " " << iChannel << endl; - qsamplerChannelRoutingMap::ConstIterator audioRoute; + << pChannel->instrumentNr() << " " << iChannelID << endl; + ChannelRoutingMap::ConstIterator audioRoute; for (audioRoute = pChannel->audioRouting().begin(); audioRoute != pChannel->audioRouting().end(); ++audioRoute) { - ts << "SET CHANNEL AUDIO_OUTPUT_CHANNEL " << iChannel + ts << "SET CHANNEL AUDIO_OUTPUT_CHANNEL " << iChannelID << " " << audioRoute.key() << " " << audioRoute.value() << endl; } - ts << "SET CHANNEL VOLUME " << iChannel + ts << "SET CHANNEL VOLUME " << iChannelID << " " << pChannel->volume() << endl; if (pChannel->channelMute()) - ts << "SET CHANNEL MUTE " << iChannel << " 1" << endl; + ts << "SET CHANNEL MUTE " << iChannelID << " 1" << endl; if (pChannel->channelSolo()) - ts << "SET CHANNEL SOLO " << iChannel << " 1" << endl; -#ifdef CONFIG_MIDI_INSTRUMENT - if (pChannel->midiMap() >= 0) { - ts << "SET CHANNEL MIDI_INSTRUMENT_MAP " << iChannel - << " " << midiInstrumentMap[pChannel->midiMap()] << endl; + ts << "SET CHANNEL SOLO " << iChannelID << " 1" << endl; + #ifdef CONFIG_MIDI_INSTRUMENT + const int iMidiMap = pChannel->midiMap(); + if (midiInstrumentMap.contains(iMidiMap)) { + ts << "SET CHANNEL MIDI_INSTRUMENT_MAP " << iChannelID + << " " << midiInstrumentMap.value(iMidiMap) << endl; } -#endif -#ifdef CONFIG_FXSEND - int iChannelID = pChannel->channelID(); + #endif + #ifdef CONFIG_FXSEND int *piFxSends = ::lscp_list_fxsends(m_pClient, iChannelID); for (int iFxSend = 0; piFxSends && piFxSends[iFxSend] >= 0; @@ -1098,7 +1359,7 @@ lscp_fxsend_info_t *pFxSendInfo = ::lscp_get_fxsend_info( m_pClient, iChannelID, piFxSends[iFxSend]); if (pFxSendInfo) { - ts << "CREATE FX_SEND " << iChannel + ts << "CREATE FX_SEND " << iChannelID << " " << pFxSendInfo->midi_controller; if (pFxSendInfo->name) ts << " '" << pFxSendInfo->name << "'"; @@ -1108,24 +1369,26 @@ piRouting && piRouting[iAudioSrc] >= 0; iAudioSrc++) { ts << "SET FX_SEND AUDIO_OUTPUT_CHANNEL " - << iChannel + << iChannelID << " " << iFxSend << " " << iAudioSrc << " " << piRouting[iAudioSrc] << endl; } -#ifdef CONFIG_FXSEND_LEVEL - ts << "SET FX_SEND LEVEL " << iChannel + #ifdef CONFIG_FXSEND_LEVEL + ts << "SET FX_SEND LEVEL " << iChannelID << " " << iFxSend << " " << pFxSendInfo->level << endl; -#endif + #endif } // Check for errors... else if (::lscp_client_get_errno(m_pClient)) { appendMessagesClient("lscp_get_fxsend_info"); iErrors++; } } -#endif + #endif ts << endl; + // Go for next channel... + ++iChannelID; } } // Try to keep it snappy :) @@ -1177,7 +1440,7 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- File Action slots. +// QSampler::MainForm -- File Action slots. // Create a new sampler session. void MainForm::fileNew (void) @@ -1201,7 +1464,7 @@ // Retrive filename index from action data... QAction *pAction = qobject_cast (sender()); if (pAction && m_pOptions) { - int iIndex = pAction->data().toInt(); + const int iIndex = pAction->data().toInt(); if (iIndex >= 0 && iIndex < m_pOptions->recentFiles.count()) { QString sFilename = m_pOptions->recentFiles[iIndex]; // Check if we can safely close the current session... @@ -1231,19 +1494,38 @@ // Reset the sampler instance. void MainForm::fileReset (void) { - if (m_pClient == NULL) + if (m_pClient == nullptr) return; // Ask user whether he/she want's an internal sampler reset... - if (QMessageBox::warning(this, - QSAMPLER_TITLE ": " + tr("Warning"), - tr("Resetting the sampler instance will close\n" - "all device and channel configurations.\n\n" - "Please note that this operation may cause\n" - "temporary MIDI and Audio disruption.\n\n" - "Do you want to reset the sampler engine now?"), - tr("Reset"), tr("Cancel")) > 0) - return; + if (m_pOptions && m_pOptions->bConfirmReset) { + const QString& sTitle = tr("Warning"); + const QString& sText = tr( + "Resetting the sampler instance will close\n" + "all device and channel configurations.\n\n" + "Please note that this operation may cause\n" + "temporary MIDI and Audio disruption.\n\n" + "Do you want to reset the sampler engine now?"); + #if 0 + if (QMessageBox::warning(this, sTitle, sText, + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) + return; + #else + QMessageBox mbox(this); + mbox.setIcon(QMessageBox::Warning); + mbox.setWindowTitle(sTitle); + mbox.setText(sText); + mbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + QCheckBox cbox(tr("Don't ask this again")); + cbox.setChecked(false); + cbox.blockSignals(true); + mbox.addButton(&cbox, QMessageBox::ActionRole); + if (mbox.exec() == QMessageBox::Cancel) + return; + if (cbox.isChecked()) + m_pOptions->bConfirmReset = false; + #endif + } // Trye closing the current session, first... if (!closeSession(true)) @@ -1268,22 +1550,41 @@ // Restart the client/server instance. void MainForm::fileRestart (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; bool bRestart = true; // Ask user whether he/she want's a complete restart... // (if we're currently up and running) - if (bRestart && m_pClient) { - bRestart = (QMessageBox::warning(this, - QSAMPLER_TITLE ": " + tr("Warning"), - tr("New settings will be effective after\n" + if (m_pOptions && m_pOptions->bConfirmRestart) { + const QString& sTitle = tr("Warning"); + const QString& sText = tr( + "New settings will be effective after\n" "restarting the client/server connection.\n\n" "Please note that this operation may cause\n" "temporary MIDI and Audio disruption.\n\n" - "Do you want to restart the connection now?"), - tr("Restart"), tr("Cancel")) == 0); + "Do you want to restart the connection now?"); + #if 0 + if (QMessageBox::warning(this, sTitle, sText, + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) + bRestart = false; + #else + QMessageBox mbox(this); + mbox.setIcon(QMessageBox::Warning); + mbox.setWindowTitle(sTitle); + mbox.setText(sText); + mbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + QCheckBox cbox(tr("Don't ask this again")); + cbox.setChecked(false); + cbox.blockSignals(true); + mbox.addButton(&cbox, QMessageBox::ActionRole); + if (mbox.exec() == QMessageBox::Cancel) + bRestart = false; + else + if (cbox.isChecked()) + m_pOptions->bConfirmRestart = false; + #endif } // Are we still for it? @@ -1305,17 +1606,24 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- Edit Action slots. +// QSampler::MainForm -- Edit Action slots. // Add a new sampler channel. void MainForm::editAddChannel (void) { - if (m_pClient == NULL) + ++m_iDirtySetup; + addChannelStrip(); + --m_iDirtySetup; +} + +void MainForm::addChannelStrip (void) +{ + if (m_pClient == nullptr) return; // Just create the channel instance... - qsamplerChannel *pChannel = new qsamplerChannel(); - if (pChannel == NULL) + Channel *pChannel = new Channel(); + if (pChannel == nullptr) return; // Before we show it up, may be we'll @@ -1333,8 +1641,7 @@ } // Do we auto-arrange? - if (m_pOptions && m_pOptions->bAutoArrange) - channelsArrange(); + channelsArrangeAuto(); // Make that an overall update. m_iDirtyCount++; @@ -1345,27 +1652,51 @@ // Remove current sampler channel. void MainForm::editRemoveChannel (void) { - if (m_pClient == NULL) + ++m_iDirtySetup; + removeChannelStrip(); + --m_iDirtySetup; +} + +void MainForm::removeChannelStrip (void) +{ + if (m_pClient == nullptr) return; - ChannelStrip* pChannelStrip = activeChannelStrip(); - if (pChannelStrip == NULL) + ChannelStrip *pChannelStrip = activeChannelStrip(); + if (pChannelStrip == nullptr) return; - qsamplerChannel *pChannel = pChannelStrip->channel(); - if (pChannel == NULL) + Channel *pChannel = pChannelStrip->channel(); + if (pChannel == nullptr) return; // Prompt user if he/she's sure about this... if (m_pOptions && m_pOptions->bConfirmRemove) { - if (QMessageBox::warning(this, - QSAMPLER_TITLE ": " + tr("Warning"), - tr("About to remove channel:\n\n" + const QString& sTitle = tr("Warning"); + const QString& sText = tr( + "About to remove channel:\n\n" "%1\n\n" "Are you sure?") - .arg(pChannelStrip->windowTitle()), - tr("OK"), tr("Cancel")) > 0) + .arg(pChannelStrip->windowTitle()); + #if 0 + if (QMessageBox::warning(this, sTitle, sText, + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) + return; + #else + QMessageBox mbox(this); + mbox.setIcon(QMessageBox::Warning); + mbox.setWindowTitle(sTitle); + mbox.setText(sText); + mbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + QCheckBox cbox(tr("Don't ask this again")); + cbox.setChecked(false); + cbox.blockSignals(true); + mbox.addButton(&cbox, QMessageBox::ActionRole); + if (mbox.exec() == QMessageBox::Cancel) return; + if (cbox.isChecked()) + m_pOptions->bConfirmRemove = false; + #endif } // Remove the existing sampler channel. @@ -1373,11 +1704,7 @@ return; // Just delete the channel strip. - delete pChannelStrip; - - // Do we auto-arrange? - if (m_pOptions && m_pOptions->bAutoArrange) - channelsArrange(); + destroyChannelStrip(pChannelStrip); // We'll be dirty, for sure... m_iDirtyCount++; @@ -1388,11 +1715,11 @@ // Setup current sampler channel. void MainForm::editSetupChannel (void) { - if (m_pClient == NULL) + if (m_pClient == nullptr) return; - ChannelStrip* pChannelStrip = activeChannelStrip(); - if (pChannelStrip == NULL) + ChannelStrip *pChannelStrip = activeChannelStrip(); + if (pChannelStrip == nullptr) return; // Just invoque the channel strip procedure. @@ -1403,11 +1730,11 @@ // Edit current sampler channel. void MainForm::editEditChannel (void) { - if (m_pClient == NULL) + if (m_pClient == nullptr) return; - ChannelStrip* pChannelStrip = activeChannelStrip(); - if (pChannelStrip == NULL) + ChannelStrip *pChannelStrip = activeChannelStrip(); + if (pChannelStrip == nullptr) return; // Just invoque the channel strip procedure. @@ -1418,11 +1745,11 @@ // Reset current sampler channel. void MainForm::editResetChannel (void) { - if (m_pClient == NULL) + if (m_pClient == nullptr) return; - ChannelStrip* pChannelStrip = activeChannelStrip(); - if (pChannelStrip == NULL) + ChannelStrip *pChannelStrip = activeChannelStrip(); + if (pChannelStrip == nullptr) return; // Just invoque the channel strip procedure. @@ -1433,15 +1760,17 @@ // Reset all sampler channels. void MainForm::editResetAllChannels (void) { - if (m_pClient == NULL) + if (m_pClient == nullptr) return; // Invoque the channel strip procedure, // for all channels out there... m_pWorkspace->setUpdatesEnabled(false); - QWidgetList wlist = m_pWorkspace->windowList(); - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip* pChannelStrip = (ChannelStrip*) wlist.at(iChannel); + const QList& wlist + = m_pWorkspace->subWindowList(); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) pChannelStrip->channelReset(); } @@ -1450,7 +1779,7 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- View Action slots. +// QSampler::MainForm -- View Action slots. // Show/hide the main program window menubar. void MainForm::viewMenubar ( bool bOn ) @@ -1500,7 +1829,7 @@ // Show/hide the MIDI instrument list-view form. void MainForm::viewInstruments (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; if (m_pInstrumentListForm) { @@ -1519,7 +1848,7 @@ // Show/hide the device configurator form. void MainForm::viewDevices (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; if (m_pDeviceForm) { @@ -1538,50 +1867,81 @@ // Show options dialog. void MainForm::viewOptions (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; OptionsForm* pOptionsForm = new OptionsForm(this); if (pOptionsForm) { // Check out some initial nullities(tm)... - ChannelStrip* pChannelStrip = activeChannelStrip(); + ChannelStrip *pChannelStrip = activeChannelStrip(); if (m_pOptions->sDisplayFont.isEmpty() && pChannelStrip) m_pOptions->sDisplayFont = pChannelStrip->displayFont().toString(); if (m_pOptions->sMessagesFont.isEmpty() && m_pMessages) m_pOptions->sMessagesFont = m_pMessages->messagesFont().toString(); // To track down deferred or immediate changes. - QString sOldServerHost = m_pOptions->sServerHost; - int iOldServerPort = m_pOptions->iServerPort; - int iOldServerTimeout = m_pOptions->iServerTimeout; - bool bOldServerStart = m_pOptions->bServerStart; - QString sOldServerCmdLine = m_pOptions->sServerCmdLine; - QString sOldDisplayFont = m_pOptions->sDisplayFont; - bool bOldDisplayEffect = m_pOptions->bDisplayEffect; - int iOldMaxVolume = m_pOptions->iMaxVolume; - QString sOldMessagesFont = m_pOptions->sMessagesFont; - bool bOldKeepOnTop = m_pOptions->bKeepOnTop; - bool bOldStdoutCapture = m_pOptions->bStdoutCapture; - int bOldMessagesLimit = m_pOptions->bMessagesLimit; - int iOldMessagesLimitLines = m_pOptions->iMessagesLimitLines; - bool bOldCompletePath = m_pOptions->bCompletePath; - bool bOldInstrumentNames = m_pOptions->bInstrumentNames; - int iOldMaxRecentFiles = m_pOptions->iMaxRecentFiles; + const QString sOldServerHost = m_pOptions->sServerHost; + const int iOldServerPort = m_pOptions->iServerPort; + const int iOldServerTimeout = m_pOptions->iServerTimeout; + const bool bOldServerStart = m_pOptions->bServerStart; + const QString sOldServerCmdLine = m_pOptions->sServerCmdLine; + const bool bOldMessagesLog = m_pOptions->bMessagesLog; + const QString sOldMessagesLogPath = m_pOptions->sMessagesLogPath; + const QString sOldDisplayFont = m_pOptions->sDisplayFont; + const bool bOldDisplayEffect = m_pOptions->bDisplayEffect; + const int iOldMaxVolume = m_pOptions->iMaxVolume; + const QString sOldMessagesFont = m_pOptions->sMessagesFont; + const bool bOldKeepOnTop = m_pOptions->bKeepOnTop; + const bool bOldStdoutCapture = m_pOptions->bStdoutCapture; + const int bOldMessagesLimit = m_pOptions->bMessagesLimit; + const int iOldMessagesLimitLines = m_pOptions->iMessagesLimitLines; + const bool bOldCompletePath = m_pOptions->bCompletePath; + const bool bOldInstrumentNames = m_pOptions->bInstrumentNames; + const int iOldMaxRecentFiles = m_pOptions->iMaxRecentFiles; + const int iOldBaseFontSize = m_pOptions->iBaseFontSize; + const QString sOldCustomStyleTheme = m_pOptions->sCustomStyleTheme; + const QString sOldCustomColorTheme = m_pOptions->sCustomColorTheme; // Load the current setup settings. pOptionsForm->setup(m_pOptions); // Show the setup dialog... if (pOptionsForm->exec()) { // Warn if something will be only effective on next run. + int iNeedRestart = 0; if (( bOldStdoutCapture && !m_pOptions->bStdoutCapture) || - (!bOldStdoutCapture && m_pOptions->bStdoutCapture) || - ( bOldKeepOnTop && !m_pOptions->bKeepOnTop) || - (!bOldKeepOnTop && m_pOptions->bKeepOnTop)) { - QMessageBox::information(this, - QSAMPLER_TITLE ": " + tr("Information"), - tr("Some settings may be only effective\n" - "next time you start this program."), tr("OK")); + (!bOldStdoutCapture && m_pOptions->bStdoutCapture)) { updateMessagesCapture(); + ++iNeedRestart; + } + if (( bOldKeepOnTop && !m_pOptions->bKeepOnTop) || + (!bOldKeepOnTop && m_pOptions->bKeepOnTop) || + (iOldBaseFontSize != m_pOptions->iBaseFontSize)) { + ++iNeedRestart; + } + // Check whether restart is needed or whether + // custom options maybe set up immediately... + if (m_pOptions->sCustomStyleTheme != sOldCustomStyleTheme) { + if (m_pOptions->sCustomStyleTheme.isEmpty()) { + ++iNeedRestart; + } else { + QApplication::setStyle( + QStyleFactory::create(m_pOptions->sCustomStyleTheme)); + } + } + if (m_pOptions->sCustomColorTheme != sOldCustomColorTheme) { + if (m_pOptions->sCustomColorTheme.isEmpty()) { + ++iNeedRestart; + } else { + QPalette pal; + if (PaletteForm::namedPalette( + &m_pOptions->settings(), m_pOptions->sCustomColorTheme, pal)) + QApplication::setPalette(pal); + } } // Check wheather something immediate has changed. + if (( bOldMessagesLog && !m_pOptions->bMessagesLog) || + (!bOldMessagesLog && m_pOptions->bMessagesLog) || + (sOldMessagesLogPath != m_pOptions->sMessagesLogPath)) + m_pMessages->setLogging( + m_pOptions->bMessagesLog, m_pOptions->sMessagesLogPath); if (( bOldCompletePath && !m_pOptions->bCompletePath) || (!bOldCompletePath && m_pOptions->bCompletePath) || (iOldMaxRecentFiles != m_pOptions->iMaxRecentFiles)) @@ -1602,6 +1962,13 @@ (!bOldMessagesLimit && m_pOptions->bMessagesLimit) || (iOldMessagesLimitLines != m_pOptions->iMessagesLimitLines)) updateMessagesLimit(); + // Show restart needed message... + if (iNeedRestart > 0) { + QMessageBox::information(this, + tr("Information"), + tr("Some settings may be only effective\n" + "next time you start this program.")); + } // And now the main thing, whether we'll do client/server recycling? if ((sOldServerHost != m_pOptions->sServerHost) || (iOldServerPort != m_pOptions->iServerPort) || @@ -1622,34 +1989,29 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- Channels action slots. +// QSampler::MainForm -- Channels action slots. // Arrange channel strips. void MainForm::channelsArrange (void) { // Full width vertical tiling - QWidgetList wlist = m_pWorkspace->windowList(); + const QList& wlist + = m_pWorkspace->subWindowList(); if (wlist.isEmpty()) return; m_pWorkspace->setUpdatesEnabled(false); int y = 0; - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip* pChannelStrip = (ChannelStrip*) wlist.at(iChannel); - /* if (pChannelStrip->testWState(WState_Maximized | WState_Minimized)) { - // Prevent flicker... - pChannelStrip->hide(); - pChannelStrip->showNormal(); - } */ - pChannelStrip->adjustSize(); - int iWidth = m_pWorkspace->width(); - if (iWidth < pChannelStrip->width()) - iWidth = pChannelStrip->width(); - // int iHeight = pChannelStrip->height() - // + pChannelStrip->parentWidget()->baseSize().height(); - int iHeight = pChannelStrip->parentWidget()->frameGeometry().height(); - pChannelStrip->parentWidget()->setGeometry(0, y, iWidth, iHeight); - y += iHeight; + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + pMdiSubWindow->adjustSize(); + const QRect& frameRect + = pMdiSubWindow->frameGeometry(); + int w = m_pWorkspace->width(); + if (w < frameRect.width()) + w = frameRect.width(); + const int h = frameRect.height(); + pMdiSubWindow->setGeometry(0, y, w, h); + y += h; } m_pWorkspace->setUpdatesEnabled(true); @@ -1660,20 +2022,26 @@ // Auto-arrange channel strips. void MainForm::channelsAutoArrange ( bool bOn ) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; // Toggle the auto-arrange flag. m_pOptions->bAutoArrange = bOn; // If on, update whole workspace... - if (m_pOptions->bAutoArrange) + channelsArrangeAuto(); +} + + +void MainForm::channelsArrangeAuto (void) +{ + if (m_pOptions && m_pOptions->bAutoArrange) channelsArrange(); } //------------------------------------------------------------------------- -// qsamplerMainForm -- Help Action slots. +// QSampler::MainForm -- Help Action slots. // Show information about the Qt toolkit. void MainForm::helpAboutQt (void) @@ -1685,59 +2053,61 @@ // Show information about application program. void MainForm::helpAbout (void) { - // Stuff the about box text... - QString sText = "

\n"; - sText += "" QSAMPLER_TITLE " - " + tr(QSAMPLER_SUBTITLE) + "
\n"; - sText += "
\n"; - sText += tr("Version") + ": " QSAMPLER_VERSION "
\n"; - sText += "" + tr("Build") + ": " __DATE__ " " __TIME__ "
\n"; + QStringList list; #ifdef CONFIG_DEBUG - sText += ""; - sText += tr("Debugging option enabled."); - sText += "
"; + list << tr("Debugging option enabled."); #endif #ifndef CONFIG_LIBGIG - sText += ""; - sText += tr("GIG (libgig) file support disabled."); - sText += "
"; + list << tr("GIG (libgig) file support disabled."); #endif #ifndef CONFIG_INSTRUMENT_NAME - sText += ""; - sText += tr("LSCP (liblscp) instrument_name support disabled."); - sText += "
"; + list << tr("LSCP (liblscp) instrument_name support disabled."); #endif #ifndef CONFIG_MUTE_SOLO - sText += ""; - sText += tr("Sampler channel Mute/Solo support disabled."); - sText += "
"; + list << tr("Sampler channel Mute/Solo support disabled."); #endif #ifndef CONFIG_AUDIO_ROUTING - sText += ""; - sText += tr("LSCP (liblscp) audio_routing support disabled."); - sText += "
"; + list << tr("LSCP (liblscp) audio_routing support disabled."); #endif #ifndef CONFIG_FXSEND - sText += ""; - sText += tr("Sampler channel Effect Sends support disabled."); - sText += "
"; + list << tr("Sampler channel Effect Sends support disabled."); #endif #ifndef CONFIG_VOLUME - sText += ""; - sText += tr("Global volume support disabled."); - sText += "
"; + list << tr("Global volume support disabled."); #endif #ifndef CONFIG_MIDI_INSTRUMENT - sText += ""; - sText += tr("MIDI instrument mapping support disabled."); - sText += "
"; + list << tr("MIDI instrument mapping support disabled."); #endif #ifndef CONFIG_EDIT_INSTRUMENT - sText += ""; - sText += tr("Instrument editing support disabled."); - sText += "
"; + list << tr("Instrument editing support disabled."); +#endif +#ifndef CONFIG_EVENT_CHANNEL_MIDI + list << tr("Channel MIDI event support disabled."); +#endif +#ifndef CONFIG_EVENT_DEVICE_MIDI + list << tr("Device MIDI event support disabled."); +#endif +#ifndef CONFIG_MAX_VOICES + list << tr("Runtime max. voices / disk streams support disabled."); #endif + + // Stuff the about box text... + QString sText = "

\n"; + sText += "" QSAMPLER_TITLE " - " + tr(QSAMPLER_SUBTITLE) + "
\n"; sText += "
\n"; - sText += tr("Using") + ": "; + sText += tr("Version") + ": " CONFIG_BUILD_VERSION "
\n"; +// sText += "" + tr("Build") + ": " CONFIG_BUILD_DATE "
\n"; + if (!list.isEmpty()) { + sText += ""; + sText += list.join("
\n"); + sText += "
"; + } + sText += "
\n"; + sText += tr("Using: Qt %1").arg(qVersion()); +#if defined(QT_STATIC) + sText += "-static"; +#endif + sText += ", "; sText += ::lscp_client_package(); sText += " "; sText += ::lscp_client_version(); @@ -1760,12 +2130,12 @@ sText += ""; sText += "

\n"; - QMessageBox::about(this, tr("About") + " " QSAMPLER_TITLE, sText); + QMessageBox::about(this, tr("About"), sText); } //------------------------------------------------------------------------- -// qsamplerMainForm -- Main window stabilization. +// QSampler::MainForm -- Main window stabilization. void MainForm::stabilizeForm (void) { @@ -1773,18 +2143,20 @@ QString sSessionName = sessionName(m_sFilename); if (m_iDirtyCount > 0) sSessionName += " *"; - setWindowTitle(tr(QSAMPLER_TITLE " - [%1]").arg(sSessionName)); + setWindowTitle(sSessionName); // Update the main menu state... - ChannelStrip* pChannelStrip = activeChannelStrip(); - bool bHasClient = (m_pOptions != NULL && m_pClient != NULL); - bool bHasChannel = (bHasClient && pChannelStrip != NULL); + ChannelStrip *pChannelStrip = activeChannelStrip(); + const QList& wlist = m_pWorkspace->subWindowList(); + const bool bHasClient = (m_pOptions != nullptr && m_pClient != nullptr); + const bool bHasChannel = (bHasClient && pChannelStrip != nullptr); + const bool bHasChannels = (bHasClient && wlist.count() > 0); m_ui.fileNewAction->setEnabled(bHasClient); m_ui.fileOpenAction->setEnabled(bHasClient); m_ui.fileSaveAction->setEnabled(bHasClient && m_iDirtyCount > 0); m_ui.fileSaveAsAction->setEnabled(bHasClient); m_ui.fileResetAction->setEnabled(bHasClient); - m_ui.fileRestartAction->setEnabled(bHasClient || m_pServer == NULL); + m_ui.fileRestartAction->setEnabled(bHasClient || m_pServer == nullptr); m_ui.editAddChannelAction->setEnabled(bHasClient); m_ui.editRemoveChannelAction->setEnabled(bHasChannel); m_ui.editSetupChannelAction->setEnabled(bHasChannel); @@ -1794,7 +2166,7 @@ m_ui.editEditChannelAction->setEnabled(false); #endif m_ui.editResetChannelAction->setEnabled(bHasChannel); - m_ui.editResetAllChannelsAction->setEnabled(bHasChannel); + m_ui.editResetAllChannelsAction->setEnabled(bHasChannels); m_ui.viewMessagesAction->setChecked(m_pMessages && m_pMessages->isVisible()); #ifdef CONFIG_MIDI_INSTRUMENT m_ui.viewInstrumentsAction->setChecked(m_pInstrumentListForm @@ -1806,7 +2178,9 @@ m_ui.viewDevicesAction->setChecked(m_pDeviceForm && m_pDeviceForm->isVisible()); m_ui.viewDevicesAction->setEnabled(bHasClient); - m_ui.channelsArrangeAction->setEnabled(bHasChannel); + m_ui.viewMidiDeviceStatusMenu->setEnabled( + DeviceStatusForm::getInstances().size() > 0); + m_ui.channelsArrangeAction->setEnabled(bHasChannels); #ifdef CONFIG_VOLUME // Toolbar widgets are also affected... @@ -1856,7 +2230,7 @@ m_pVolumeSpinBox->setValue(iVolume); // Do it as commanded... - float fVolume = 0.01f * float(iVolume); + const float fVolume = 0.01f * float(iVolume); if (::lscp_set_volume(m_pClient, fVolume) == LSCP_OK) appendMessages(QObject::tr("Volume: %1.").arg(fVolume)); else @@ -1872,7 +2246,7 @@ // Channel change receiver slot. -void MainForm::channelStripChanged(ChannelStrip* pChannelStrip) +void MainForm::channelStripChanged ( ChannelStrip *pChannelStrip ) { // Add this strip to the changed list... if (!m_changedStrips.contains(pChannelStrip)) { @@ -1891,7 +2265,7 @@ void MainForm::updateSession (void) { #ifdef CONFIG_VOLUME - int iVolume = ::lroundf(100.0f * ::lscp_get_volume(m_pClient)); + const int iVolume = ::lroundf(100.0f * ::lscp_get_volume(m_pClient)); m_iVolumeChanging++; m_pVolumeSlider->setValue(iVolume); m_pVolumeSpinBox->setValue(iVolume); @@ -1899,7 +2273,7 @@ #endif #ifdef CONFIG_MIDI_INSTRUMENT // FIXME: Make some room for default instrument maps... - int iMaps = ::lscp_get_midi_instrument_maps(m_pClient); + const int iMaps = ::lscp_get_midi_instrument_maps(m_pClient); if (iMaps < 0) appendMessagesClient("lscp_get_midi_instrument_maps"); else if (iMaps < 1) { @@ -1910,9 +2284,28 @@ } #endif + updateAllChannelStrips(false); + + // Do we auto-arrange? + channelsArrangeAuto(); + + // Remember to refresh devices and instruments... + if (m_pInstrumentListForm) + m_pInstrumentListForm->refreshInstruments(); + if (m_pDeviceForm) + m_pDeviceForm->refreshDevices(); +} + + +void MainForm::updateAllChannelStrips ( bool bRemoveDeadStrips ) +{ + // Skip if setting up a new channel strip... + if (m_iDirtySetup > 0) + return; + // Retrieve the current channel list. int *piChannelIDs = ::lscp_list_channels(m_pClient); - if (piChannelIDs == NULL) { + if (piChannelIDs == nullptr) { if (::lscp_client_get_errno(m_pClient)) { appendMessagesClient("lscp_list_channels"); appendMessagesError( @@ -1921,34 +2314,52 @@ } else { // Try to (re)create each channel. m_pWorkspace->setUpdatesEnabled(false); - for (int iChannel = 0; piChannelIDs[iChannel] >= 0; iChannel++) { + for (int iChannel = 0; piChannelIDs[iChannel] >= 0; ++iChannel) { // Check if theres already a channel strip for this one... if (!channelStrip(piChannelIDs[iChannel])) - createChannelStrip(new qsamplerChannel(piChannelIDs[iChannel])); + createChannelStrip(new Channel(piChannelIDs[iChannel])); + } + // Do we auto-arrange? + channelsArrangeAuto(); + // remove dead channel strips + if (bRemoveDeadStrips) { + const QList& wlist + = m_pWorkspace->subWindowList(); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); + if (pChannelStrip) { + bool bExists = false; + for (int iChannel = 0; piChannelIDs[iChannel] >= 0; ++iChannel) { + Channel *pChannel = pChannelStrip->channel(); + if (pChannel == nullptr) + break; + if (piChannelIDs[iChannel] == pChannel->channelID()) { + // strip exists, don't touch it + bExists = true; + break; + } + } + if (!bExists) + destroyChannelStrip(pChannelStrip); + } + } } m_pWorkspace->setUpdatesEnabled(true); } - // Do we auto-arrange? - if (m_pOptions && m_pOptions->bAutoArrange) - channelsArrange(); - - // Remember to refresh devices and instruments... - if (m_pInstrumentListForm) - m_pInstrumentListForm->refreshInstruments(); - if (m_pDeviceForm) - m_pDeviceForm->refreshDevices(); + stabilizeForm(); } // Update the recent files list and menu. void MainForm::updateRecentFiles ( const QString& sFilename ) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; // Remove from list if already there (avoid duplicates) - int iIndex = m_pOptions->recentFiles.indexOf(sFilename); + const int iIndex = m_pOptions->recentFiles.indexOf(sFilename); if (iIndex >= 0) m_pOptions->recentFiles.removeAt(iIndex); // Put it to front... @@ -1959,7 +2370,7 @@ // Update the recent files list and menu. void MainForm::updateRecentFilesMenu (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; // Time to keep the list under limits. @@ -1987,13 +2398,15 @@ void MainForm::updateInstrumentNames (void) { // Full channel list update... - QWidgetList wlist = m_pWorkspace->windowList(); + const QList& wlist + = m_pWorkspace->subWindowList(); if (wlist.isEmpty()) return; m_pWorkspace->setUpdatesEnabled(false); - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip *pChannelStrip = (ChannelStrip *) wlist.at(iChannel); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) pChannelStrip->updateInstrumentName(true); } @@ -2004,25 +2417,28 @@ // Force update of the channels display font. void MainForm::updateDisplayFont (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; // Check if display font is legal. if (m_pOptions->sDisplayFont.isEmpty()) return; + // Realize it. QFont font; if (!font.fromString(m_pOptions->sDisplayFont)) return; // Full channel list update... - QWidgetList wlist = m_pWorkspace->windowList(); + const QList& wlist + = m_pWorkspace->subWindowList(); if (wlist.isEmpty()) return; m_pWorkspace->setUpdatesEnabled(false); - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip* pChannelStrip = (ChannelStrip*) wlist.at(iChannel); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) pChannelStrip->setDisplayFont(font); } @@ -2034,13 +2450,15 @@ void MainForm::updateDisplayEffect (void) { // Full channel list update... - QWidgetList wlist = m_pWorkspace->windowList(); + const QList& wlist + = m_pWorkspace->subWindowList(); if (wlist.isEmpty()) return; m_pWorkspace->setUpdatesEnabled(false); - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip* pChannelStrip = (ChannelStrip*) wlist.at(iChannel); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) pChannelStrip->setDisplayEffect(m_pOptions->bDisplayEffect); } @@ -2051,7 +2469,7 @@ // Force update of the channels maximum volume setting. void MainForm::updateMaxVolume (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; #ifdef CONFIG_VOLUME @@ -2062,13 +2480,15 @@ #endif // Full channel list update... - QWidgetList wlist = m_pWorkspace->windowList(); + const QList& wlist + = m_pWorkspace->subWindowList(); if (wlist.isEmpty()) return; m_pWorkspace->setUpdatesEnabled(false); - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip* pChannelStrip = (ChannelStrip*) wlist.at(iChannel); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) pChannelStrip->setMaxVolume(m_pOptions->iMaxVolume); } @@ -2077,10 +2497,10 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- Messages window form handlers. +// QSampler::MainForm -- Messages window form handlers. // Messages output methods. -void MainForm::appendMessages( const QString& s ) +void MainForm::appendMessages ( const QString& s ) { if (m_pMessages) m_pMessages->appendMessages(s); @@ -2088,39 +2508,55 @@ statusBar()->showMessage(s, 3000); } -void MainForm::appendMessagesColor( const QString& s, const QString& c ) +void MainForm::appendMessagesColor ( const QString& s, const QColor& rgb ) { if (m_pMessages) - m_pMessages->appendMessagesColor(s, c); + m_pMessages->appendMessagesColor(s, rgb); statusBar()->showMessage(s, 3000); } -void MainForm::appendMessagesText( const QString& s ) +void MainForm::appendMessagesText ( const QString& s ) { if (m_pMessages) m_pMessages->appendMessagesText(s); } -void MainForm::appendMessagesError( const QString& s ) +void MainForm::appendMessagesError ( const QString& s ) { if (m_pMessages) m_pMessages->show(); - appendMessagesColor(s.simplified(), "#ff0000"); + appendMessagesColor(s.simplified(), Qt::red); // Make it look responsive...:) QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - QMessageBox::critical(this, - QSAMPLER_TITLE ": " + tr("Error"), s, tr("Cancel")); + if (m_pOptions && m_pOptions->bConfirmError) { + const QString& sTitle = tr("Error"); + #if 0 + QMessageBox::critical(this, sTitle, sText, QMessageBox::Cancel); + #else + QMessageBox mbox(this); + mbox.setIcon(QMessageBox::Critical); + mbox.setWindowTitle(sTitle); + mbox.setText(s); + mbox.setStandardButtons(QMessageBox::Cancel); + QCheckBox cbox(tr("Don't show this again")); + cbox.setChecked(false); + cbox.blockSignals(true); + mbox.addButton(&cbox, QMessageBox::ActionRole); + if (mbox.exec() && cbox.isChecked()) + m_pOptions->bConfirmError = false; + #endif + } } // This is a special message format, just for client results. void MainForm::appendMessagesClient( const QString& s ) { - if (m_pClient == NULL) + if (m_pClient == nullptr) return; appendMessagesColor(s + QString(": %1 (errno=%2)") @@ -2135,7 +2571,7 @@ // Force update of the messages font. void MainForm::updateMessagesFont (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; if (m_pMessages && !m_pOptions->sMessagesFont.isEmpty()) { @@ -2149,7 +2585,7 @@ // Update messages window line limit. void MainForm::updateMessagesLimit (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; if (m_pMessages) { @@ -2164,7 +2600,7 @@ // Enablement of the messages capture feature. void MainForm::updateMessagesCapture (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; if (m_pMessages) @@ -2173,18 +2609,18 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- MDI channel strip management. +// QSampler::MainForm -- MDI channel strip management. // The channel strip creation executive. -ChannelStrip* MainForm::createChannelStrip ( qsamplerChannel *pChannel ) +ChannelStrip *MainForm::createChannelStrip ( Channel *pChannel ) { - if (m_pClient == NULL || pChannel == NULL) - return NULL; + if (m_pClient == nullptr || pChannel == nullptr) + return nullptr; // Add a new channel itema... ChannelStrip *pChannelStrip = new ChannelStrip(); - if (pChannelStrip == NULL) - return NULL; + if (pChannelStrip == nullptr) + return nullptr; // Set some initial channel strip options... if (m_pOptions) { @@ -2192,21 +2628,25 @@ pChannelStrip->setDisplayEffect(m_pOptions->bDisplayEffect); // We'll need a display font. QFont font; - if (font.fromString(m_pOptions->sDisplayFont)) + if (!m_pOptions->sDisplayFont.isEmpty() && + font.fromString(m_pOptions->sDisplayFont)) pChannelStrip->setDisplayFont(font); // Maximum allowed volume setting. pChannelStrip->setMaxVolume(m_pOptions->iMaxVolume); } // Add it to workspace... - m_pWorkspace->addWindow(pChannelStrip, Qt::FramelessWindowHint); + QMdiSubWindow *pMdiSubWindow + = m_pWorkspace->addSubWindow(pChannelStrip, + Qt::SubWindow | Qt::FramelessWindowHint); + pMdiSubWindow->setAttribute(Qt::WA_DeleteOnClose); // Actual channel strip setup... pChannelStrip->setup(pChannel); QObject::connect(pChannelStrip, - SIGNAL(channelChanged(ChannelStrip*)), - SLOT(channelStripChanged(ChannelStrip*))); + SIGNAL(channelChanged(ChannelStrip *)), + SLOT(channelStripChanged(ChannelStrip *))); // Now we show up us to the world. pChannelStrip->show(); @@ -2219,43 +2659,74 @@ } +void MainForm::destroyChannelStrip ( ChannelStrip *pChannelStrip ) +{ + QMdiSubWindow *pMdiSubWindow + = static_cast (pChannelStrip->parentWidget()); + if (pMdiSubWindow == nullptr) + return; + + // Just delete the channel strip. + delete pChannelStrip; + delete pMdiSubWindow; + + // Do we auto-arrange? + channelsArrangeAuto(); +} + + // Retrieve the active channel strip. -ChannelStrip* MainForm::activeChannelStrip (void) +ChannelStrip *MainForm::activeChannelStrip (void) { - return static_cast (m_pWorkspace->activeWindow()); + QMdiSubWindow *pMdiSubWindow = m_pWorkspace->activeSubWindow(); + if (pMdiSubWindow) + return static_cast (pMdiSubWindow->widget()); + else + return nullptr; } // Retrieve a channel strip by index. -ChannelStrip* MainForm::channelStripAt ( int iChannel ) +ChannelStrip *MainForm::channelStripAt ( int iStrip ) { - QWidgetList wlist = m_pWorkspace->windowList(); + if (!m_pWorkspace) return nullptr; + + const QList& wlist + = m_pWorkspace->subWindowList(); if (wlist.isEmpty()) - return NULL; + return nullptr; + + if (iStrip < 0 || iStrip >= wlist.count()) + return nullptr; - return static_cast (wlist.at(iChannel)); + QMdiSubWindow *pMdiSubWindow = wlist.at(iStrip); + if (pMdiSubWindow) + return static_cast (pMdiSubWindow->widget()); + else + return nullptr; } // Retrieve a channel strip by sampler channel id. -ChannelStrip* MainForm::channelStrip ( int iChannelID ) +ChannelStrip *MainForm::channelStrip ( int iChannelID ) { - QWidgetList wlist = m_pWorkspace->windowList(); + const QList& wlist + = m_pWorkspace->subWindowList(); if (wlist.isEmpty()) - return NULL; + return nullptr; - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip* pChannelStrip - = static_cast (wlist.at(iChannel)); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) { - qsamplerChannel *pChannel = pChannelStrip->channel(); + Channel *pChannel = pChannelStrip->channel(); if (pChannel && pChannel->channelID() == iChannelID) return pChannelStrip; } } // Not found. - return NULL; + return nullptr; } @@ -2266,20 +2737,23 @@ m_ui.channelsMenu->addAction(m_ui.channelsArrangeAction); m_ui.channelsMenu->addAction(m_ui.channelsAutoArrangeAction); - QWidgetList wlist = m_pWorkspace->windowList(); + const QList& wlist + = m_pWorkspace->subWindowList(); if (!wlist.isEmpty()) { m_ui.channelsMenu->addSeparator(); - for (int iChannel = 0; iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip* pChannelStrip - = static_cast (wlist.at(iChannel)); + int iStrip = 0; + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) { QAction *pAction = m_ui.channelsMenu->addAction( pChannelStrip->windowTitle(), this, SLOT(channelsMenuActivated())); pAction->setCheckable(true); pAction->setChecked(activeChannelStrip() == pChannelStrip); - pAction->setData(iChannel); + pAction->setData(iStrip); } + ++iStrip; } } } @@ -2290,10 +2764,10 @@ { // Retrive channel index from action data... QAction *pAction = qobject_cast (sender()); - if (pAction == NULL) + if (pAction == nullptr) return; - ChannelStrip* pChannelStrip = channelStripAt(pAction->data().toInt()); + ChannelStrip *pChannelStrip = channelStripAt(pAction->data().toInt()); if (pChannelStrip) { pChannelStrip->showNormal(); pChannelStrip->setFocus(); @@ -2302,7 +2776,7 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- Timer stuff. +// QSampler::MainForm -- Timer stuff. // Set the pseudo-timer delay schedule. void MainForm::startSchedule ( int iStartDelay ) @@ -2321,7 +2795,7 @@ // Timer slot funtion. void MainForm::timerSlot (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; // Is it the first shot on server start after a few delay? @@ -2343,7 +2817,7 @@ ChannelStrip *pChannelStrip = iter.next(); // If successfull, remove from pending list... if (pChannelStrip->updateChannelInfo()) { - int iChannelStrip = m_changedStrips.indexOf(pChannelStrip); + const int iChannelStrip = m_changedStrips.indexOf(pChannelStrip); if (iChannelStrip >= 0) m_changedStrips.removeAt(iChannelStrip); } @@ -2354,16 +2828,27 @@ if (m_iTimerSlot >= m_pOptions->iAutoRefreshTime) { m_iTimerSlot = 0; // Update the channel stream usage for each strip... - QWidgetList wlist = m_pWorkspace->windowList(); - for (int iChannel = 0; - iChannel < (int) wlist.count(); iChannel++) { - ChannelStrip* pChannelStrip - = (ChannelStrip*) wlist.at(iChannel); + const QList& wlist + = m_pWorkspace->subWindowList(); + foreach (QMdiSubWindow *pMdiSubWindow, wlist) { + ChannelStrip *pChannelStrip + = static_cast (pMdiSubWindow->widget()); if (pChannelStrip && pChannelStrip->isVisible()) pChannelStrip->updateChannelUsage(); } } } + + #if CONFIG_LSCP_CLIENT_CONNECTION_LOST + // If we lost connection to server: Try to automatically reconnect if we + // did not start the server. + // + // TODO: If we started the server, then we might inform the user that + // the server probably crashed and asking user ONCE whether we should + // restart the server. + if (lscp_client_connection_lost(m_pClient) && !m_pServer) + startAutoReconnectClient(); + #endif // CONFIG_LSCP_CLIENT_CONNECTION_LOST } // Register the next timer slot. @@ -2372,12 +2857,12 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- Server stuff. +// QSampler::MainForm -- Server stuff. // Start linuxsampler server... void MainForm::startServer (void) { - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return; // Aren't already a client, are we? @@ -2386,17 +2871,13 @@ // Is the server process instance still here? if (m_pServer) { - switch (QMessageBox::warning(this, - QSAMPLER_TITLE ": " + tr("Warning"), + if (QMessageBox::warning(this, + tr("Warning"), tr("Could not start the LinuxSampler server.\n\n" - "Maybe it ss already started."), - tr("Stop"), tr("Kill"), tr("Cancel"))) { - case 0: + "Maybe it is already started."), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { m_pServer->terminate(); - break; - case 1: m_pServer->kill(); - break; } return; } @@ -2409,23 +2890,21 @@ return; // OK. Let's build the startup process... - m_pServer = new QProcess(this); + m_pServer = new QProcess(); + m_bForceServerStop = true; // Setup stdout/stderr capture... -// if (m_pOptions->bStdoutCapture) { - //m_pServer->setProcessChannelMode( - // QProcess::StandardOutput); - QObject::connect(m_pServer, - SIGNAL(readyReadStandardOutput()), - SLOT(readServerStdout())); - QObject::connect(m_pServer, - SIGNAL(readyReadStandardError()), - SLOT(readServerStdout())); -// } + m_pServer->setProcessChannelMode(QProcess::ForwardedChannels); + QObject::connect(m_pServer, + SIGNAL(readyReadStandardOutput()), + SLOT(readServerStdout())); + QObject::connect(m_pServer, + SIGNAL(readyReadStandardError()), + SLOT(readServerStdout())); // The unforgiveable signal communication... QObject::connect(m_pServer, - SIGNAL(finished(int,QProcess::ExitStatus)), + SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processServerExit())); // Build process arguments... @@ -2446,7 +2925,12 @@ // Show startup results... appendMessages( - tr("Server was started with PID=%1.").arg((long) m_pServer->pid())); + tr("Server was started with PID=%1.") + #if QT_VERSION < QT_VERSION_CHECK(5, 3, 0) + .arg(quint64(m_pServer->pid()))); + #else + .arg(quint64(m_pServer->processId()))); + #endif // Reset (yet again) the timer counters, // but this time is deferred as the user opted. @@ -2456,26 +2940,51 @@ // Stop linuxsampler server... -void MainForm::stopServer (void) +void MainForm::stopServer ( bool bInteractive ) { // Stop client code. stopClient(); + if (m_pServer && bInteractive) { + if (QMessageBox::question(this, + tr("The backend's fate ..."), + tr("You have the option to keep the sampler backend (LinuxSampler)\n" + "running in the background. The sampler would continue to work\n" + "according to your current sampler session and you could alter the\n" + "sampler session at any time by relaunching QSampler.\n\n" + "Do you want LinuxSampler to stop?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes) == QMessageBox::No) { + m_bForceServerStop = false; + } + } + + bool bGraceWait = true; + // And try to stop server. - if (m_pServer) { + if (m_pServer && m_bForceServerStop) { appendMessages(tr("Server is stopping...")); - if (m_pServer->state() == QProcess::Running) + if (m_pServer->state() == QProcess::Running) { + #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) + // Try harder... + m_pServer->kill(); + #else + // Try softly... m_pServer->terminate(); - } + bool bFinished = m_pServer->waitForFinished(QSAMPLER_TIMER_MSECS * 1000); + if (bFinished) bGraceWait = false; + #endif + } + } // Do final processing anyway. + else processServerExit(); // Give it some time to terminate gracefully and stabilize... - QTime t; - t.start(); - while (t.elapsed() < QSAMPLER_TIMER_MSECS) - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - - // Do final processing anyway. - processServerExit(); + if (bGraceWait) { + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < QSAMPLER_TIMER_MSECS) + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + } } @@ -2497,17 +3006,23 @@ if (m_pMessages) m_pMessages->flushStdoutBuffer(); - if (m_pServer) { + if (m_pServer && m_bForceServerStop) { + if (m_pServer->state() != QProcess::NotRunning) { + appendMessages(tr("Server is being forced...")); + // Force final server shutdown... + m_pServer->kill(); + // Give it some time to terminate gracefully and stabilize... + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < QSAMPLER_TIMER_MSECS) + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + } // Force final server shutdown... appendMessages( tr("Server was stopped with exit status %1.") .arg(m_pServer->exitStatus())); - m_pServer->terminate(); - if (!m_pServer->waitForFinished(2000)) - m_pServer->kill(); - // Destroy it. delete m_pServer; - m_pServer = NULL; + m_pServer = nullptr; } // Again, make status visible stable. @@ -2516,31 +3031,31 @@ //------------------------------------------------------------------------- -// qsamplerMainForm -- Client stuff. +// QSampler::MainForm -- Client stuff. // The LSCP client callback procedure. lscp_status_t qsampler_client_callback ( lscp_client_t */*pClient*/, lscp_event_t event, const char *pchData, int cchData, void *pvData ) { MainForm* pMainForm = (MainForm *) pvData; - if (pMainForm == NULL) + if (pMainForm == nullptr) return LSCP_FAILED; // ATTN: DO NOT EVER call any GUI code here, // as this is run under some other thread context. // A custom event must be posted here... QApplication::postEvent(pMainForm, - new qsamplerCustomEvent(event, pchData, cchData)); + new LscpEvent(event, pchData, cchData)); return LSCP_OK; } // Start our almighty client... -bool MainForm::startClient (void) +bool MainForm::startClient (bool bReconnectOnly) { // Have it a setup? - if (m_pOptions == NULL) + if (m_pOptions == nullptr) return false; // Aren't we already started, are we? @@ -2554,13 +3069,19 @@ m_pClient = ::lscp_client_create( m_pOptions->sServerHost.toUtf8().constData(), m_pOptions->iServerPort, qsampler_client_callback, this); - if (m_pClient == NULL) { + if (m_pClient == nullptr) { // Is this the first try? // maybe we need to start a local server... if ((m_pServer && m_pServer->state() == QProcess::Running) - || !m_pOptions->bServerStart) { - appendMessagesError( - tr("Could not connect to server as client.\n\nSorry.")); + || !m_pOptions->bServerStart || bReconnectOnly) + { + // if this method is called from autoReconnectClient() + // then don't bother user with an error message... + if (!bReconnectOnly) { + appendMessagesError( + tr("Could not connect to server as client.\n\nSorry.") + ); + } } else { startServer(); } @@ -2568,6 +3089,7 @@ stabilizeForm(); return false; } + // Just set receive timeout value, blindly. ::lscp_client_set_timeout(m_pClient, m_pOptions->iServerTimeout); appendMessages( @@ -2575,8 +3097,33 @@ .arg(::lscp_client_get_timeout(m_pClient))); // Subscribe to channel info change notifications... + if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_CHANNEL_COUNT) != LSCP_OK) + appendMessagesClient("lscp_client_subscribe(CHANNEL_COUNT)"); if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_CHANNEL_INFO) != LSCP_OK) - appendMessagesClient("lscp_client_subscribe"); + appendMessagesClient("lscp_client_subscribe(CHANNEL_INFO)"); + + DeviceStatusForm::onDevicesChanged(); // initialize + updateViewMidiDeviceStatusMenu(); + if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT) != LSCP_OK) + appendMessagesClient("lscp_client_subscribe(MIDI_INPUT_DEVICE_COUNT)"); + if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_MIDI_INPUT_DEVICE_INFO) != LSCP_OK) + appendMessagesClient("lscp_client_subscribe(MIDI_INPUT_DEVICE_INFO)"); + if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT) != LSCP_OK) + appendMessagesClient("lscp_client_subscribe(AUDIO_OUTPUT_DEVICE_COUNT)"); + if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO) != LSCP_OK) + appendMessagesClient("lscp_client_subscribe(AUDIO_OUTPUT_DEVICE_INFO)"); + +#if CONFIG_EVENT_CHANNEL_MIDI + // Subscribe to channel MIDI data notifications... + if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_CHANNEL_MIDI) != LSCP_OK) + appendMessagesClient("lscp_client_subscribe(CHANNEL_MIDI)"); +#endif + +#if CONFIG_EVENT_DEVICE_MIDI + // Subscribe to channel MIDI data notifications... + if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_DEVICE_MIDI) != LSCP_OK) + appendMessagesClient("lscp_client_subscribe(DEVICE_MIDI)"); +#endif // We may stop scheduling around. stopSchedule(); @@ -2598,11 +3145,14 @@ if (!m_pOptions->sSessionFile.isEmpty()) { // Just load the prabably startup session... if (loadSessionFile(m_pOptions->sSessionFile)) { - m_pOptions->sSessionFile = QString::null; + m_pOptions->sSessionFile = QString(); return true; } } + // send the current / loaded fine tuning settings to the sampler + m_pOptions->sendFineTuningSettings(); + // Make a new session return newSession(); } @@ -2611,7 +3161,7 @@ // Stop client... void MainForm::stopClient (void) { - if (m_pClient == NULL) + if (m_pClient == nullptr) return; // Log prepare here. @@ -2630,9 +3180,20 @@ closeSession(false); // Close us as a client... +#if CONFIG_EVENT_DEVICE_MIDI + ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_DEVICE_MIDI); +#endif +#if CONFIG_EVENT_CHANNEL_MIDI + ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_CHANNEL_MIDI); +#endif + ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO); + ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT); + ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_MIDI_INPUT_DEVICE_INFO); + ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT); ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_CHANNEL_INFO); + ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_CHANNEL_COUNT); ::lscp_client_destroy(m_pClient); - m_pClient = NULL; + m_pClient = nullptr; // Hard-notify instrumnet and device configuration forms, // if visible, that we're running out... @@ -2649,11 +3210,28 @@ } +void MainForm::startAutoReconnectClient (void) +{ + stopClient(); + appendMessages(tr("Trying to reconnect...")); + QTimer::singleShot(QSAMPLER_TIMER_MSECS, this, SLOT(autoReconnectClient())); +} + + +void MainForm::autoReconnectClient (void) +{ + const bool bSuccess = startClient(true); + if (!bSuccess) + QTimer::singleShot(QSAMPLER_TIMER_MSECS, this, SLOT(autoReconnectClient())); +} + + // Channel strip activation/selection. -void MainForm::activateStrip ( QWidget *pWidget ) +void MainForm::activateStrip ( QMdiSubWindow *pMdiSubWindow ) { - ChannelStrip *pChannelStrip - = static_cast (pWidget); + ChannelStrip *pChannelStrip = nullptr; + if (pMdiSubWindow) + pChannelStrip = static_cast (pMdiSubWindow->widget()); if (pChannelStrip) pChannelStrip->setSelected(true); @@ -2661,6 +3239,30 @@ } +// Channel toolbar orientation change. +void MainForm::channelsToolbarOrientation ( Qt::Orientation orientation ) +{ +#ifdef CONFIG_VOLUME + m_pVolumeSlider->setOrientation(orientation); + if (orientation == Qt::Horizontal) { + m_pVolumeSlider->setMinimumHeight(24); + m_pVolumeSlider->setMaximumHeight(32); + m_pVolumeSlider->setMinimumWidth(120); + m_pVolumeSlider->setMaximumWidth(640); + m_pVolumeSpinBox->setMaximumWidth(64); + m_pVolumeSpinBox->setButtonSymbols(QSpinBox::UpDownArrows); + } else { + m_pVolumeSlider->setMinimumHeight(120); + m_pVolumeSlider->setMaximumHeight(480); + m_pVolumeSlider->setMinimumWidth(24); + m_pVolumeSlider->setMaximumWidth(32); + m_pVolumeSpinBox->setMaximumWidth(32); + m_pVolumeSpinBox->setButtonSymbols(QSpinBox::NoButtons); + } +#endif +} + + } // namespace QSampler