From 9740e6621a76462385b4d44dbf47ff463d52c14c Mon Sep 17 00:00:00 2001 From: Colin Wallace Date: Sun, 19 Apr 2015 03:31:59 +0000 Subject: [PATCH 1/2] Added Messenger class for passing internal messages --- include/Messenger.h | 142 ++++++++++++++++++++++++++++++++++++++++ src/core/CMakeLists.txt | 1 + src/core/Messenger.cpp | 118 +++++++++++++++++++++++++++++++++ 3 files changed, 261 insertions(+) create mode 100644 include/Messenger.h create mode 100644 src/core/Messenger.cpp diff --git a/include/Messenger.h b/include/Messenger.h new file mode 100644 index 00000000000..475d140ee27 --- /dev/null +++ b/include/Messenger.h @@ -0,0 +1,142 @@ +/* + * Messenger.h - class Messenger, a singleton that helps route string-based messages through core <-> gui + * + * Copyright (c) 2015 Colin Wallace + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef MESSENGER_H +#define MESSENGER_H + +#include +#include +#include + +class Message +{ + // This object is passed to MessageReceivers that are set to listen to the relevant message type. +public: + enum MessageType + { + INIT_STATUS, + NUM_MESSAGE_TYPES // KEEP THIS AT THE END OF THE LIST. It's used to determine the number of possible message types. + }; + + Message(const QString &msg, MessageType type); + + const QString& getMessage() const; + MessageType getType() const; +private: + + QString m_msg; + MessageType m_type; +}; + + +class MessageReceiver +{ + // This represents a Messenger callback in a way + // such that static functions, member functions, and non-member functions can all receive messages + friend class Messenger; + virtual void messageReceived(const Message &msg) = 0; +protected: + virtual ~MessageReceiver() {}; +}; + + +class MessageReceiverFuncPtr : public MessageReceiver +{ + // MessageReceiver implementation for C function pointer callback functions +public: + MessageReceiverFuncPtr(void (*receiverFunc)(const Message &msg)); +private: + void (*m_receiverFunc)(const Message &msg); + void messageReceived(const Message &msg); +}; + + +template class MessageReceiverMemberFunc : public MessageReceiver +{ + // MessageReceiver implementation for member callback functions +public: + MessageReceiverMemberFunc(void (T::*memberFunction)(const Message &msg), T *this_) + : m_this(this_), + m_memberFunction(memberFunction) + { + } +private: + T *m_this; + void (T::*m_memberFunction)(const Message &msg); + void messageReceived(const Message &msg) + { + (m_this->*m_memberFunction)(msg); + } +}; + + +class MessageReceiverHandle +{ + // This is used for automatically removing a message handler from the callback list + // when it's owner goes out-of-scope. + // e.g. store the MessageReceiverHandle returned from Messenger::registerHandler as a member variable + // to have the handler removed when the instance is destroyed. + MessageReceiver *m_messageReceiver; + Message::MessageType m_subscriptionType; +public: + MessageReceiverHandle(MessageReceiver *messageReceiver, Message::MessageType subscriptionType); + ~MessageReceiverHandle(); +}; + + +class Messenger +{ + friend class MessageReceiverHandle; +public: + static void broadcast(Message msg); + static void broadcast(const QString &msg, Message::MessageType type); + + static MessageReceiverHandle subscribe(MessageReceiver *receiver, Message::MessageType subscriptionType); + + // subscribe overload for C function pointers + inline static MessageReceiverHandle subscribe(void (*receiverFunc)(const Message &msg), Message::MessageType subscriptionType) + { + return subscribe(new MessageReceiverFuncPtr(receiverFunc), subscriptionType); + } + // subscribe overload for member function pointers + template static MessageReceiverHandle subscribe(void (T::*memberFunction)(const Message &msg), T *this_, + Message::MessageType subscriptionType) + { + return subscribe(new MessageReceiverMemberFunc(memberFunction, this_), subscriptionType); + } + +private: + static void removeReceiver(MessageReceiver *receiver, Message::MessageType subscriptionType); + + // use a semaphore in order to allow many threads to iterate through the message handlers, + // but a call to subscribe must block ALL threads + static QSemaphore accessSemaphore; + static const int maxReaders = 32; // 32 is a mostly arbitrary choice. Any large number should do. + + // store callback pointers in an array of vectors. + // All callbacks listening for a Message::MessageType `type' are contained in receivers[type] + static QVector receivers[Message::NUM_MESSAGE_TYPES]; +}; + +#endif \ No newline at end of file diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ae23ae6d9a0..eaa0b8a5a21 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -33,6 +33,7 @@ set(LMMS_SRCS core/MemoryHelper.cpp core/MemoryManager.cpp core/MeterModel.cpp + core/Messenger.cpp core/Mixer.cpp core/MixerProfiler.cpp core/MixerWorkerThread.cpp diff --git a/src/core/Messenger.cpp b/src/core/Messenger.cpp new file mode 100644 index 00000000000..1ec678e0c4b --- /dev/null +++ b/src/core/Messenger.cpp @@ -0,0 +1,118 @@ +/* + * Messenger.cpp - class Messenger, a singleton that helps route string-based messages through core <-> gui + * + * Copyright (c) 2015 Colin Wallace + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "Messenger.h" + +#include + +QSemaphore Messenger::accessSemaphore(Messenger::maxReaders); +QVector Messenger::receivers[Message::NUM_MESSAGE_TYPES]; + + +Message::Message(const QString &msg, MessageType type) + : m_msg(msg), + m_type(type) +{ +} + +const QString& Message::getMessage() const +{ + return m_msg; +} + +Message::MessageType Message::getType() const +{ + return m_type; +} + + +MessageReceiverFuncPtr::MessageReceiverFuncPtr(void (*receiverFunc)(const Message &msg)) + : m_receiverFunc(receiverFunc) +{ +} + +void MessageReceiverFuncPtr::messageReceived(const Message &msg) +{ + m_receiverFunc(msg); +} + + +MessageReceiverHandle::MessageReceiverHandle(MessageReceiver *messageReceiver, Message::MessageType subscriptionType) + : m_messageReceiver(messageReceiver), + m_subscriptionType(subscriptionType) +{ +} + +MessageReceiverHandle::~MessageReceiverHandle() +{ + // remove the corresponding receiver from the Messenger's callback list + if (m_messageReceiver) + { + Messenger::removeReceiver(m_messageReceiver, m_subscriptionType); + } +} + +MessageReceiverHandle Messenger::subscribe(MessageReceiver *receiver, Message::MessageType subscriptionType) +{ + accessSemaphore.acquire(maxReaders); + receivers[subscriptionType].append(receiver); + accessSemaphore.release(maxReaders); + return MessageReceiverHandle(receiver, subscriptionType); +} + +void Messenger::broadcast(Message msg) +{ + // send the message to all receivers subscribed to the message's type + accessSemaphore.acquire(); + QVector::iterator begin = receivers[msg.getType()].begin(); + QVector::iterator end = receivers[msg.getType()].end(); + for (QVector::iterator i=begin; i != end; ++i) + { + (*i)->messageReceived(msg); + } + accessSemaphore.release(); +} + +void Messenger::broadcast(const QString &msg, Message::MessageType type) +{ + broadcast(Message(msg, type)); +} + +void Messenger::removeReceiver(MessageReceiver *receiver, Message::MessageType subscriptionType) +{ + accessSemaphore.acquire(maxReaders); + int i = receivers[subscriptionType].indexOf(receiver); + if (i == -1) + { + // receiver function is not registered as a callback + qWarning("Messenger::removeReceiver: attempt to remove a non-existent message receiver"); + } + else + { + // remove the receiver from the associated callback vector + delete receivers[subscriptionType][i]; + receivers[subscriptionType].remove(i); + } + accessSemaphore.release(maxReaders); +} \ No newline at end of file From 39389d3901072db17a2d0fcc5f7bebcf593e4e7b Mon Sep 17 00:00:00 2001 From: Colin Wallace Date: Sun, 19 Apr 2015 03:49:06 +0000 Subject: [PATCH 2/2] Provide status messages on splash screen when loading --- include/GuiApplication.h | 6 ++++ src/core/Engine.cpp | 7 ++++ src/gui/GuiApplication.cpp | 70 +++++++++++++++++++++++++++++++------- src/gui/MainWindow.cpp | 4 +++ 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/include/GuiApplication.h b/include/GuiApplication.h index c35994e8162..6909ff98089 100644 --- a/include/GuiApplication.h +++ b/include/GuiApplication.h @@ -27,11 +27,14 @@ #include "export.h" +class QLabel; + class AutomationEditorWindow; class BBEditor; class ControllerRackView; class FxMixerView; class MainWindow; +class Message; class PianoRollWindow; class ProjectNotes; class SongEditorWindow; @@ -54,6 +57,8 @@ class EXPORT GuiApplication ControllerRackView* getControllerRackView() { return m_controllerRackView; } private: + void onInitProgress(const Message &msg); + static GuiApplication* s_instance; MainWindow* m_mainWindow; @@ -64,6 +69,7 @@ class EXPORT GuiApplication PianoRollWindow* m_pianoRoll; ProjectNotes* m_projectNotes; ControllerRackView* m_controllerRackView; + QLabel* m_loadingProgressLabel; }; #define gui GuiApplication::instance() diff --git a/src/core/Engine.cpp b/src/core/Engine.cpp index 5730e977eb0..f19b70f73c7 100644 --- a/src/core/Engine.cpp +++ b/src/core/Engine.cpp @@ -22,12 +22,14 @@ * */ +#include #include "Engine.h" #include "BBTrackContainer.h" #include "ConfigManager.h" #include "FxMixer.h" #include "Ladspa2LMMS.h" +#include "Messenger.h" #include "Mixer.h" #include "PresetPreviewPlayHandle.h" #include "ProjectJournal.h" @@ -53,10 +55,13 @@ QMap Engine::s_pluginFileHandling; void Engine::init() { // generate (load from file) bandlimited wavetables + Messenger::broadcast(QObject::tr("Generating wavetables"), Message::INIT_STATUS); BandLimitedWave::generateWaves(); + Messenger::broadcast(QObject::tr("Locating plugins"), Message::INIT_STATUS); initPluginFileHandling(); + Messenger::broadcast(QObject::tr("Initializing data structures"), Message::INIT_STATUS); s_projectJournal = new ProjectJournal; s_mixer = new Mixer; s_song = new Song; @@ -67,11 +72,13 @@ void Engine::init() s_projectJournal->setJournalling( true ); + Messenger::broadcast(QObject::tr("Opening audio and midi devices"), Message::INIT_STATUS); s_mixer->initDevices(); PresetPreviewPlayHandle::init(); s_dummyTC = new DummyTrackContainer; + Messenger::broadcast(QObject::tr("Launching mixer threads"), Message::INIT_STATUS); s_mixer->startProcessing(); } diff --git a/src/gui/GuiApplication.cpp b/src/gui/GuiApplication.cpp index 78cf609efb6..7e9508b42e0 100644 --- a/src/gui/GuiApplication.cpp +++ b/src/gui/GuiApplication.cpp @@ -35,6 +35,7 @@ #include "FxMixerView.h" #include "InstrumentTrack.h" #include "MainWindow.h" +#include "Messenger.h" #include "PianoRoll.h" #include "ProjectNotes.h" #include "SongEditor.h" @@ -64,27 +65,62 @@ GuiApplication::GuiApplication() // Show splash screen QSplashScreen splashScreen( embed::getIconPixmap( "splash" ) ); splashScreen.show(); - splashScreen.showMessage( MainWindow::tr( "Version %1" ).arg( LMMS_VERSION ), - Qt::AlignRight | Qt::AlignBottom, Qt::white ); + QHBoxLayout layout; + layout.setAlignment(Qt::AlignBottom); + splashScreen.setLayout(&layout); + + // Create a left-aligned label for loading progress + // & a right-aligned label for version info + QLabel loadingProgressLabel; + m_loadingProgressLabel = &loadingProgressLabel; + QLabel versionLabel(MainWindow::tr( "Version %1" ).arg( LMMS_VERSION )); + + loadingProgressLabel.setAlignment(Qt::AlignLeft); + versionLabel.setAlignment(Qt::AlignRight); + + layout.addWidget(&loadingProgressLabel); + layout.addWidget(&versionLabel); + + // may have long gaps between future frames, so force update now + splashScreen.update(); qApp->processEvents(); + MessageReceiverHandle msgReceiverHandle = Messenger::subscribe(&GuiApplication::onInitProgress, this, Message::INIT_STATUS); + // Init central engine which handles all components of LMMS Engine::init(); s_instance = this; - m_mainWindow = new MainWindow; + Messenger::broadcast(QObject::tr("Preparing UI"), Message::INIT_STATUS); + + m_mainWindow = new MainWindow; + + Messenger::broadcast(QObject::tr("Preparing song editor"), Message::INIT_STATUS); + m_songEditor = new SongEditorWindow(Engine::getSong()); + + Messenger::broadcast(QObject::tr("Preparing mixer"), Message::INIT_STATUS); + m_fxMixerView = new FxMixerView; - m_songEditor = new SongEditorWindow(Engine::getSong()); - m_fxMixerView = new FxMixerView; - m_controllerRackView = new ControllerRackView; - m_projectNotes = new ProjectNotes; - m_bbEditor = new BBEditor(Engine::getBBTrackContainer()); - m_pianoRoll = new PianoRollWindow(); - m_automationEditor = new AutomationEditorWindow; + Messenger::broadcast(QObject::tr("Preparing controller rack"), Message::INIT_STATUS); + m_controllerRackView = new ControllerRackView; - m_mainWindow->finalize(); - splashScreen.finish(m_mainWindow); + Messenger::broadcast(QObject::tr("Preparing project notes"), Message::INIT_STATUS); + m_projectNotes = new ProjectNotes; + + Messenger::broadcast(QObject::tr("Preparing beat/bassline editor"), Message::INIT_STATUS); + m_bbEditor = new BBEditor(Engine::getBBTrackContainer()); + + Messenger::broadcast(QObject::tr("Preparing piano roll"), Message::INIT_STATUS); + m_pianoRoll = new PianoRollWindow(); + + Messenger::broadcast(QObject::tr("Preparing automation editor"), Message::INIT_STATUS); + m_automationEditor = new AutomationEditorWindow; + + m_mainWindow->finalize(); + splashScreen.finish(m_mainWindow); + + m_loadingProgressLabel = nullptr; } GuiApplication::~GuiApplication() @@ -92,3 +128,13 @@ GuiApplication::~GuiApplication() InstrumentTrackView::cleanupWindowCache(); s_instance = nullptr; } + +void GuiApplication::onInitProgress(const Message &msg) +{ + Q_ASSERT(m_loadingProgressLabel != nullptr); + + m_loadingProgressLabel->setText(msg.getMessage()); + // must force a UI update and process events, as there may be long gaps between processEvents() calls during init + m_loadingProgressLabel->repaint(); + qApp->processEvents(); +} \ No newline at end of file diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0c62bf6b4bd..4b184aa4e90 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -53,6 +53,7 @@ #include "PluginBrowser.h" #include "SideBar.h" #include "ConfigManager.h" +#include "Messenger.h" #include "Mixer.h" #include "PluginView.h" #include "ProjectNotes.h" @@ -96,7 +97,9 @@ MainWindow::MainWindow() : ConfigManager* confMgr = ConfigManager::inst(); + Messenger::broadcast(tr("Preparing plugin browser"), Message::INIT_STATUS); sideBar->appendTab( new PluginBrowser( splitter ) ); + Messenger::broadcast(tr("Preparing file browsers"), Message::INIT_STATUS); sideBar->appendTab( new FileBrowser( confMgr->userProjectsDir() + "*" + confMgr->factoryProjectsDir(), @@ -150,6 +153,7 @@ MainWindow::MainWindow() : m_workspace = new QMdiArea( splitter ); // Load background + Messenger::broadcast(tr("Loading background artwork"), Message::INIT_STATUS); QString bgArtwork = ConfigManager::inst()->backgroundArtwork(); QImage bgImage; if( !bgArtwork.isEmpty() )