Mema
Memory Matrix — multi-channel audio matrix monitor and router
Loading...
Searching...
No Matches
Main.cpp
Go to the documentation of this file.
1/* Copyright (c) 2024, Christian Ahrens
2 *
3 * This file is part of Mema <https://github.com/ChristianAhrens/Mema>
4 *
5 * This tool is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License version 3.0 as published
7 * by the Free Software Foundation.
8 *
9 * This tool is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12 * details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this tool; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#include <JuceHeader.h>
20
21#include "Mema.h"
22#include "MemaUIComponent.h"
24
25#include <AppConfigurationBase.h>
26
27#if JUCE_MAC
28#include <signal.h>
29#endif
30
31//==============================================================================
32class MemaMacMainMenuMenuBarModel : public juce::MenuBarModel
33{
34public:
35 //==============================================================================
36 void addMenu (int topLevelMenuIndex, const String& menuName, const juce::PopupMenu& popupMenu)
37 {
38 m_popupMenus[topLevelMenuIndex] = popupMenu;
39 m_popupMenuNames[topLevelMenuIndex] = menuName;
40 }
41
42 juce::String getMenuNameForIndex(int topLevelMenuIndex)
43 {
44 jassert(m_popupMenuNames.count(topLevelMenuIndex) != 0);
45
46 return m_popupMenuNames[topLevelMenuIndex];
47 }
48
49 juce::PopupMenu& getMenuRefForIndex(int topLevelMenuIndex)
50 {
51 jassert(m_popupMenus.count(topLevelMenuIndex) != 0);
52
53 return m_popupMenus[topLevelMenuIndex];
54 }
55
56 //==============================================================================
57 juce::StringArray getMenuBarNames() override
58 {
59 juce::StringArray menuBarNames;
60 for (auto const& menuName : m_popupMenuNames)
61 menuBarNames.add(menuName.second);
62
63 return menuBarNames;
64 }
65
66 juce::PopupMenu getMenuForIndex (int topLevelMenuIndex, const String& menuName) override
67 {
68 ignoreUnused(menuName);
69
70 if (m_popupMenus.count(topLevelMenuIndex) == 0) { jassertfalse; return {}; }
71 if (m_popupMenuNames.count(topLevelMenuIndex) == 0) { jassertfalse; return {}; }
72
73 return m_popupMenus[topLevelMenuIndex];
74 }
75
76 void menuItemSelected (int menuItemID, int topLevelMenuIndex) override
77 {
79 onMenuItemSelected(menuItemID, topLevelMenuIndex);
80 }
81
82 //==============================================================================
83 std::function<void(int, int)> onMenuItemSelected;
84
85private:
86 //==============================================================================
87 std::map<int, juce::PopupMenu> m_popupMenus;
88 std::map<int, juce::String> m_popupMenuNames;
89};
90
91//==============================================================================
92class MemaApplication : public juce::JUCEApplication
93{
94public:
95 //==============================================================================
99
100 Mema::Mema::deleteInstance();
101 };
102
103 const juce::String getApplicationName() override { return ProjectInfo::projectName; }
104 const juce::String getApplicationVersion() override { return ProjectInfo::versionString; }
105 bool moreThanOneInstanceAllowed() override { return false; }
106
107 //==============================================================================
108 void initialise (const juce::String& commandLine) override
109 {
110 auto isHeadless = commandLine.contains("--headless");
111
112#if JUCE_MAC
113 // Ignore SIGPIPE globally, to prevent occasional unexpected app
114 // termination when Mema.Mo instances disconnect while sending by
115 // writing to socket is ongoing
116 signal(SIGPIPE, SIG_IGN);
117#endif
118
119 if (!isHeadless)
120 {
121 // a single instance of tooltip window is required and used by JUCE everywhere a tooltip is required.
122 m_toolTipWindowInstance = std::make_unique<TooltipWindow>();
123
124 m_taskbarComponent = std::make_unique<MemaTaskbarComponent>([=](juce::Point<int> mousePosition) { showUiAsCalloutBox(mousePosition); });
125 m_taskbarComponent->setName(getApplicationName() + " taskbar icon");
126 }
127
128 Mema::Mema::getInstance();
129
130 if (!isHeadless)
131 {
132#if JUCE_MAC
133 m_macMainMenu = std::make_unique<MemaMacMainMenuMenuBarModel>();
134 auto optionsPopupMenu = juce::PopupMenu();
135 optionsPopupMenu.addItem("Show as standalone window", true, false, [=]() {
136 if (auto currentProcEditor = Mema::Mema::getInstance()->getMemaProcessorEditor())
137 if (auto callout = currentProcEditor->findParentComponentOfClass<juce::CallOutBox>())
138 callout->exitModalState(0);
139 juce::MessageManager::callAsync([=]() { showUiAsStandaloneWindow(); });
140 });
141 optionsPopupMenu.addSeparator();
142 optionsPopupMenu.addItem("Load config...", true, false, [=]() {
143 Mema::Mema::getInstance()->triggerPromptLoadConfig();
144 });
145 optionsPopupMenu.addItem("Save config...", true, false, [=]() {
146 Mema::Mema::getInstance()->triggerPromptSaveConfig();
147 });
148 m_macMainMenu->addMenu(0, "Options", optionsPopupMenu);
149
150 juce::MenuBarModel::setMacMainMenu(m_macMainMenu.get());
151#elif JUCE_LINUX
153#endif
154 }
155 }
156
157 void shutdown() override
158 {
159#if JUCE_MAC
160 juce::MenuBarModel::setMacMainMenu(nullptr);
161#endif
162 }
163
164 //==============================================================================
165 std::unique_ptr<Mema::MemaUIComponent> createAndConnectMemaUIComponent()
166 {
167 if (!Mema::Mema::getInstanceWithoutCreating())
168 return {};
169
170 auto memaUIComponent = std::make_unique<Mema::MemaUIComponent>().release();
171 Mema::Mema::getInstance()->onEditorSizeChangeRequested = [memaUIComponent, this](juce::Rectangle<int> requestedSize) {
172 m_lastRequestedEditorSize = requestedSize;
173 jassert(memaUIComponent);
174 if (memaUIComponent) memaUIComponent->handleEditorSizeChangeRequest(requestedSize);
175 };
176 Mema::Mema::getInstance()->onCpuUsageUpdate = [=](int loadPercent) {
177 jassert(memaUIComponent);
178 if (memaUIComponent) memaUIComponent->updateCpuUsageBar(loadPercent);
179 };
180 Mema::Mema::getInstance()->onNetworkUsageUpdate = [=](std::map<int, std::pair<double, bool>> netLoads) {
181 jassert(memaUIComponent);
182 if (memaUIComponent) memaUIComponent->updateNetworkUsage(netLoads);
183 };
184 Mema::Mema::getInstance()->onServiceDiscoveryTopologyUpdate = [=](const JUCEAppBasics::SessionServiceTopology& sessionServiceTopology) {
185 jassert(memaUIComponent);
186 if (memaUIComponent) memaUIComponent->updateSessionServiceTopology(sessionServiceTopology);
187 };
188 memaUIComponent->setEditorComponent(Mema::Mema::getInstance()->getMemaProcessorEditor());
189 memaUIComponent->setVisible(m_isMainComponentVisible);
190 memaUIComponent->addToDesktop(juce::ComponentPeer::windowHasDropShadow);
191 memaUIComponent->setTopLeftPosition(m_taskbarComponent->getX(), 50);
192 memaUIComponent->setName(ProjectInfo::projectName);
193 memaUIComponent->onToggleStandaloneWindow = [=](bool standalone) {
194 if (standalone)
195 {
196 if (auto callout = memaUIComponent->findParentComponentOfClass<juce::CallOutBox>())
197 callout->exitModalState(0);
198 juce::MessageManager::callAsync([=]() { showUiAsStandaloneWindow(); });
199 }
200 else
201 {
202 if (m_memaUIComponent)
203 m_memaUIComponent->setStandaloneWindow(false);
205 }
206 };
207 memaUIComponent->onLookAndFeelChanged = [=]() {
208 if (Mema::Mema::getInstanceWithoutCreating()) Mema::Mema::getInstance()->propagateLookAndFeelChanged();
209 };
210 memaUIComponent->onAudioSetupMenuClicked = [=]() {
211 if (Mema::Mema::getInstanceWithoutCreating())
212 {
213 juce::PopupMenu setupMenu;
214 setupMenu.addCustomItem(1, std::make_unique<CustomAboutItem>(Mema::Mema::getInstance()->getDeviceSetupComponent(), juce::Rectangle<int>(300, 350)), nullptr, "Audio Device Setup");
215 setupMenu.showMenuAsync(juce::PopupMenu::Options());
216 }
217 };
218 memaUIComponent->onDeleted = [this]() {
219 if (Mema::Mema::getInstanceWithoutCreating())
220 {
221 Mema::Mema::getInstance()->clearUICallbacks();
222 }
223
224 m_memaUIComponent.release();
225 m_memaUIComponent = nullptr;
226 };
227 memaUIComponent->onSettingsChanged = [=]() {
228 jassert(memaUIComponent);
229 if (memaUIComponent && Mema::Mema::getInstanceWithoutCreating())
230 {
231 Mema::Mema::getInstance()->setUIConfigState(memaUIComponent->createStateXml());
232 JUCEAppBasics::AppConfigurationBase::getInstance()->triggerConfigurationDump();
233 }
234 };
235 memaUIComponent->onPaletteStyleChange = [=](const JUCEAppBasics::CustomLookAndFeel::PaletteStyle& paletteStyle) {
236 m_lookAndFeel = std::make_unique<JUCEAppBasics::CustomLookAndFeel>(paletteStyle);
237 juce::Desktop::getInstance().setDefaultLookAndFeel(m_lookAndFeel.get());
238 };
239 memaUIComponent->onLoadConfig = [=]() {
240 Mema::Mema::getInstance()->triggerPromptLoadConfig();
241 };
242 memaUIComponent->onSaveConfig = [=]() {
243 Mema::Mema::getInstance()->triggerPromptSaveConfig();
244 };
245
246 memaUIComponent->handleEditorSizeChangeRequest(m_lastRequestedEditorSize);
247 memaUIComponent->lookAndFeelChanged();
248 memaUIComponent->setStateXml(Mema::Mema::getInstance()->getUIConfigState().get());
249 memaUIComponent->resized();
250 memaUIComponent->grabKeyboardFocus();
251
252 return std::unique_ptr<Mema::MemaUIComponent>(memaUIComponent);
253 }
254
256 {
257 if (Mema::Mema::getInstanceWithoutCreating())
258 {
259 Mema::Mema::getInstance()->onServiceDiscoveryTopologyUpdate = nullptr;
260 Mema::Mema::getInstance()->onEditorSizeChangeRequested = nullptr;
261 Mema::Mema::getInstance()->onCpuUsageUpdate = nullptr;
262 Mema::Mema::getInstance()->onNetworkUsageUpdate = nullptr;
263 }
264
265 m_memaUIComponent.reset();
266 }
267
268 void showUiAsCalloutBox(const juce::Point<int>& positionToPointTo)
269 {
270 // in case the ui is currently shown as standalone window, clean that up first
272
273 auto const display = juce::Desktop::getInstance().getDisplays().getPrimaryDisplay();
274 jassert(display);
275 auto position = display->userArea.getConstrainedPoint(positionToPointTo);
276
277 // On OSX, there can be problems launching a menu when we're not the foreground
278 // process, so just in case, we'll first make our process active,
279 // and bring our windows to the front.
280 juce::Process::makeForegroundProcess();
281
282 juce::CallOutBox::launchAsynchronously(createAndConnectMemaUIComponent(), { position, position }, nullptr);
283 }
284
286 {
287 m_memaUIComponent.reset();
288 m_memaUIComponent = createAndConnectMemaUIComponent();
289 jassert(m_memaUIComponent);
290 m_memaUIComponent->setStandaloneWindow(true);
291
292 auto showPosition = juce::Desktop::getInstance().getMousePosition();
293 auto const display = juce::Desktop::getInstance().getDisplays().getPrimaryDisplay();
294 if (nullptr != display && nullptr != m_memaUIComponent)
295 {
296 if (display->userArea.getHeight() < showPosition.getY() + m_memaUIComponent->getHeight())
297 showPosition.setY(showPosition.getY() - m_memaUIComponent->getHeight() - 30);
298 if (display->userArea.getWidth() < showPosition.getX() + m_memaUIComponent->getWidth())
299 showPosition.setX(showPosition.getX() - m_memaUIComponent->getWidth() - 30);
300 }
301 m_memaUIComponent->setTopLeftPosition(showPosition);
302 }
303
304 // Just add a simple icon to the Window system tray area or Mac menu bar..
305 struct MemaTaskbarComponent : public juce::SystemTrayIconComponent
306 {
307 MemaTaskbarComponent(std::function<void(juce::Point<int>)> callback) : onMouseDownWithPosition(callback)
308 {
309 setIconImage(juce::ImageFileFormat::loadFrom(BinaryData::grid_4x4_24dp_png, BinaryData::grid_4x4_24dp_pngSize),
310 juce::ImageFileFormat::loadFrom(BinaryData::grid_4x4_24dp_png, BinaryData::grid_4x4_24dp_pngSize));
311 setIconTooltip(JUCEApplication::getInstance()->getApplicationName());
312 }
313
314 void mouseDown(const juce::MouseEvent&) override
315 {
316 if (onMouseDownWithPosition)
317 onMouseDownWithPosition(juce::Desktop::getMousePosition());
318 }
319
320 private:
321 std::function<void(juce::Point<int>)> onMouseDownWithPosition;
322 };
323
324private:
325 bool m_isMainComponentVisible = false;
326 juce::Rectangle<int> m_lastRequestedEditorSize;
327
328 std::unique_ptr<Mema::MemaUIComponent> m_memaUIComponent;
329 std::unique_ptr<juce::Component> m_taskbarComponent;
330 std::unique_ptr<juce::TooltipWindow> m_toolTipWindowInstance;
331 std::unique_ptr<juce::LookAndFeel> m_lookAndFeel;
332
333#if JUCE_MAC
334 std::unique_ptr<MemaMacMainMenuMenuBarModel> m_macMainMenu;
335#endif
336
337 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MemaApplication)
338};
339
340//==============================================================================
341// This macro generates the main() routine that launches the app.
342START_JUCE_APPLICATION (MemaApplication)
void disconnectAndDeleteMemaUIComponent()
Definition Main.cpp:255
void shutdown() override
Definition Main.cpp:157
const juce::String getApplicationName() override
Definition Main.cpp:103
const juce::String getApplicationVersion() override
Definition Main.cpp:104
void showUiAsCalloutBox(const juce::Point< int > &positionToPointTo)
Definition Main.cpp:268
void showUiAsStandaloneWindow()
Definition Main.cpp:285
std::unique_ptr< Mema::MemaUIComponent > createAndConnectMemaUIComponent()
Definition Main.cpp:165
bool moreThanOneInstanceAllowed() override
Definition Main.cpp:105
void initialise(const juce::String &commandLine) override
Definition Main.cpp:108
juce::String getMenuNameForIndex(int topLevelMenuIndex)
Definition Main.cpp:42
void menuItemSelected(int menuItemID, int topLevelMenuIndex) override
Definition Main.cpp:76
juce::PopupMenu & getMenuRefForIndex(int topLevelMenuIndex)
Definition Main.cpp:49
juce::PopupMenu getMenuForIndex(int topLevelMenuIndex, const String &menuName) override
Definition Main.cpp:66
juce::StringArray getMenuBarNames() override
Definition Main.cpp:57
void addMenu(int topLevelMenuIndex, const String &menuName, const juce::PopupMenu &popupMenu)
Definition Main.cpp:36
std::function< void(int, int)> onMenuItemSelected
Definition Main.cpp:83
void mouseDown(const juce::MouseEvent &) override
Definition Main.cpp:314
MemaTaskbarComponent(std::function< void(juce::Point< int >)> callback)
Definition Main.cpp:307