Mema
Memory Matrix — multi-channel audio matrix monitor and router
Loading...
Searching...
No Matches
PluginControlComponent.h
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#pragma once
20
21#include <JuceHeader.h>
22
23#include "../MemaProcessor/MemaPluginParameterInfo.h"
24
25#include <FixedFontTextEditor.h>
26
27
28namespace Mema
29{
30
35class CustomPluginListComponentTableModel : public juce::TableListBoxModel
36{
37public:
38 CustomPluginListComponentTableModel(juce::PluginListComponent& c, juce::KnownPluginList& l) : m_owner(c), m_list(l) {}
39
40 int getNumRows() override
41 {
42 return m_list.getNumTypes() + m_list.getBlacklistedFiles().size();
43 }
44
45 void paintRowBackground(juce::Graphics& g, int /*rowNumber*/, int /*width*/, int /*height*/, bool rowIsSelected) override
46 {
47 const auto defaultColour = m_owner.findColour(ListBox::backgroundColourId);
48 const auto c = rowIsSelected ? defaultColour.interpolatedWith(m_owner.findColour(ListBox::textColourId), 0.5f)
49 : defaultColour;
50
51 g.fillAll(c);
52 }
53
54 enum
55 {
60 descCol = 5
61 };
62
63 void paintCell(juce::Graphics& g, int row, int columnId, int width, int height, bool /*rowIsSelected*/) override
64 {
65 juce::String text;
66 bool isBlacklisted = row >= m_list.getNumTypes();
67
68 if (isBlacklisted)
69 {
70 if (columnId == nameCol)
71 text = m_list.getBlacklistedFiles()[row - m_list.getNumTypes()];
72 else if (columnId == descCol)
73 text = TRANS("Deactivated after failing to initialise correctly");
74 }
75 else
76 {
77 auto desc = m_list.getTypes()[row];
78
79 switch (columnId)
80 {
81 case nameCol: text = desc.name; break;
82 case typeCol: text = desc.pluginFormatName; break;
83 case categoryCol: text = desc.category.isNotEmpty() ? desc.category : "-"; break;
84 case manufacturerCol: text = desc.manufacturerName; break;
85 case descCol: text = getPluginDescription(desc); break;
86
87 default: jassertfalse; break;
88 }
89 }
90
91 if (text.isNotEmpty())
92 {
93 const auto defaultTextColour = m_owner.findColour(ListBox::textColourId);
94 g.setColour(isBlacklisted ? Colours::red
95 : columnId == nameCol ? defaultTextColour
96 : defaultTextColour.interpolatedWith(Colours::transparentBlack, 0.3f));
97 g.setFont(m_owner.withDefaultMetrics(juce::FontOptions((float)height * 0.7f, Font::bold)));
98 g.drawFittedText(text, 4, 0, width - 6, height, juce::Justification::centredLeft, 1, 0.9f);
99 }
100 }
101
102 void cellClicked(int rowNumber, int columnId, const juce::MouseEvent& e) override
103 {
104 juce::TableListBoxModel::cellClicked(rowNumber, columnId, e);
105
106 if (rowNumber >= 0 && rowNumber < getNumRows() && e.mods.isPopupMenu())
107 m_owner.createMenuForRow(rowNumber).showMenuAsync(juce::PopupMenu::Options().withDeletionCheck(m_owner));
108 }
109
110 void deleteKeyPressed(int) override
111 {
112 m_owner.removeSelectedPlugins();
113 }
114
115 void sortOrderChanged(int newSortColumnId, bool isForwards) override
116 {
117 switch (newSortColumnId)
118 {
119 case nameCol: m_list.sort(juce::KnownPluginList::sortAlphabetically, isForwards); break;
120 case typeCol: m_list.sort(juce::KnownPluginList::sortByFormat, isForwards); break;
121 case categoryCol: m_list.sort(juce::KnownPluginList::sortByCategory, isForwards); break;
122 case manufacturerCol: m_list.sort(juce::KnownPluginList::sortByManufacturer, isForwards); break;
123 case descCol: break;
124
125 default: jassertfalse; break;
126 }
127 }
128
129 void selectedRowsChanged(int lastRowSelected) override
130 {
131 m_lastRowSelected = lastRowSelected;
132
133 juce::TableListBoxModel::selectedRowsChanged(lastRowSelected);
134
137 }
138
139 static juce::String getPluginDescription(const juce::PluginDescription& desc)
140 {
141 juce::StringArray items;
142
143 if (desc.descriptiveName != desc.name)
144 items.add(desc.descriptiveName);
145
146 items.add(desc.version);
147
148 items.removeEmptyStrings();
149 return items.joinIntoString(" - ");
150 }
151
152 juce::PluginDescription getPluginDescriptionOfSelectedRow()
153 {
154 if (m_lastRowSelected != -1)
155 {
156 auto currentPluginDescriptionListContents = m_list.getTypes();
157 if (currentPluginDescriptionListContents.size() > m_lastRowSelected)
158 return currentPluginDescriptionListContents[m_lastRowSelected];
159 }
160
161 return {};
162 }
163
164 std::function<void(int)> onSelectionChanged;
165
166 //==============================================================================
167 juce::PluginListComponent& m_owner;
168 juce::KnownPluginList& m_list;
170
171
172 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CustomPluginListComponentTableModel)
173};
174
175//==============================================================================
176class Spacing : public juce::Component
177{
178public:
179 Spacing() = default;
180 ~Spacing() = default;
181
182 //==============================================================================
183 void paint(Graphics& g) override
184 {
185 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
186 };
187
188
189 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Spacing)
190};
191
192//==============================================================================
193class PluginListAndSelectComponent : public juce::Component
194{
195public:
197 {
198 addDefaultFormatsToManager(m_formatManager);
199 juce::File deadMansPedalFile;
200 m_pluginListComponent = std::make_unique<juce::PluginListComponent>(m_formatManager, m_pluginList, deadMansPedalFile, nullptr);
201 m_pluginListComponent->getTableListBox().setMultipleSelectionEnabled(false);
202 auto customTableModel = std::make_unique<CustomPluginListComponentTableModel>(*m_pluginListComponent, m_pluginList);
203 customTableModel->onSelectionChanged = [=](int lastRowSelected) {
204 if (m_selectButton)
205 m_selectButton->setEnabled(-1 != lastRowSelected);
206 };
207 m_pluginListComponent->setTableModel(dynamic_cast<juce::TableListBoxModel*>(customTableModel.release()));
208 addAndMakeVisible(m_pluginListComponent.get());
209
210 m_selectButton = std::make_unique<juce::TextButton>("Select", "Accept the current plugin selection as new type to instantiate and close.");
211 m_selectButton->onClick = [=]() {
212 if (m_pluginListComponent)
213 {
214 auto customTableModel = dynamic_cast<CustomPluginListComponentTableModel*>(m_pluginListComponent->getTableListBox().getTableListBoxModel());
215 if (customTableModel && onPluginSelected)
216 onPluginSelected(customTableModel->getPluginDescriptionOfSelectedRow());
217 }
218 removeFromDesktop();
219 };
220 m_selectButton->setEnabled(false);
221 addAndMakeVisible(m_selectButton.get());
222
223 m_cancelButton = std::make_unique<juce::TextButton>("Cancel", "Discard the current plugin selection and close.");
224 m_cancelButton->onClick = [=]() {
225 removeFromDesktop();
226 };
227 addAndMakeVisible(m_cancelButton.get());
228
229 setSize(885, 600);
230 };
232
233 //==============================================================================
234 void paint(Graphics& g) override
235 {
236 // (Our component is opaque, so we must completely fill the background with a solid colour)
237 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
238 };
239 void resized() override
240 {
241 auto bounds = getLocalBounds();
242 m_pluginListComponent->setBounds(bounds);
243
244 bounds = bounds.removeFromBottom(28);
245 m_cancelButton->setBounds(bounds.removeFromRight(80).reduced(2));
246 m_selectButton->setBounds(bounds.removeFromRight(80).reduced(2));
247 };
248
249 //==============================================================================
250 std::function<void(const juce::PluginDescription&)> onPluginSelected;
251
252private:
253 //==============================================================================
254 juce::AudioPluginFormatManager m_formatManager;
255 juce::KnownPluginList m_pluginList;
256
257 std::unique_ptr<juce::PluginListComponent> m_pluginListComponent;
258 std::unique_ptr<juce::TextButton> m_selectButton;
259 std::unique_ptr<juce::TextButton> m_cancelButton;
260
261
262 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginListAndSelectComponent)
263};
264
266class ParameterRowComponent : public juce::Component,
267 public juce::DragAndDropTarget
268{
269public:
270 static constexpr int gripWidth = 20;
271
272 ParameterRowComponent(int paramIdx, const Mema::PluginParameterInfo& info);
273 ~ParameterRowComponent() override = default;
274
275 void resized() override;
276 void paint(juce::Graphics& g) override;
277 void mouseDown(const juce::MouseEvent& e) override;
278 void mouseDrag(const juce::MouseEvent& e) override;
279 void mouseUp(const juce::MouseEvent& e) override;
280
281 // DragAndDropTarget — each row is both a potential drop destination and drag source
282 bool isInterestedInDragSource(const SourceDetails& details) override;
283 void itemDragEnter(const SourceDetails& details) override;
284 void itemDragMove(const SourceDetails& details) override;
285 void itemDragExit(const SourceDetails& details) override;
286 void itemDropped(const SourceDetails& details) override;
287
288 // Widgets – read by PluginControlComponent on dialog OK
289 std::unique_ptr<juce::ToggleButton> toggleButton;
290 std::unique_ptr<juce::ComboBox> typeCombo;
291 std::unique_ptr<JUCEAppBasics::FixedFontTextEditor> stepsEdit;
292
293 int paramIndex = 0;
294
296 std::function<void(int fromParamIndex, int toParamIndex, bool insertBefore)> onRowDropped;
297
298private:
299 bool m_mouseDownInGrip = false;
300 bool m_showInsertionLine = false;
301 bool m_insertionLineAtTop = true;
302
303 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ParameterRowComponent)
304};
305
307class ParameterListComponent : public juce::Component,
308 public juce::DragAndDropContainer
309{
310public:
312 ~ParameterListComponent() override = default;
313
314 void addRow(std::unique_ptr<ParameterRowComponent> row);
315 void layoutRows();
316 std::vector<int> getDisplayOrder() const;
318 void reorderRow(int fromParamIndex, int toParamIndex, bool insertBefore);
319
320private:
321 std::vector<std::unique_ptr<ParameterRowComponent>> m_rows;
322
323 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ParameterListComponent)
324};
325
327class PluginControlComponent : public juce::Component
328{
329public:
332
333 void showPluginsList(juce::Point<int> showPosition);
334 void setPluginEnabled(bool enabled = true);
335 void setPluginPrePost(bool post = false);
336 void setSelectedPlugin(const juce::PluginDescription& pluginDescription);
337 void setParameterInfos(const std::vector<PluginParameterInfo>& infos);
338 const std::map<int, Mema::PluginParameterInfo>& getParameterInfos();
339 void setParameterDisplayOrder(const std::vector<int>& order);
340 const std::vector<int>& getParameterDisplayOrder();
341 void showParameterConfig();
342
343 //==============================================================================
344 void paint (Graphics&) override;
345 void resized() override;
346 void lookAndFeelChanged() override;
347
348 //==============================================================================
349 std::function<void(const juce::PluginDescription&)> onPluginSelected;
350 std::function<void()> onShowPluginEditor;
351 std::function<void(bool)> onPluginEnabledChange;
352 std::function<void(bool)> onPluginPrePostChange;
353 std::function<void()> onPluginParametersStatusChanged;
354 std::function<void()> onClearPlugin;
355
356private:
357 //==============================================================================
358 std::unique_ptr<juce::DrawableButton> m_enableButton;
359 std::unique_ptr<Spacing> m_spacing1;
360 std::unique_ptr<juce::TextButton> m_postButton;
361 std::unique_ptr<Spacing> m_spacing2;
362 std::unique_ptr<juce::TextButton> m_showEditorButton;
363 std::unique_ptr<juce::DrawableButton> m_triggerSelectButton;
364 std::unique_ptr<Spacing> m_spacing3;
365 std::unique_ptr<juce::DrawableButton> m_parameterConfigButton;
366 std::unique_ptr<Spacing> m_spacing4;
367 std::unique_ptr<juce::DrawableButton> m_clearButton;
368
369 std::unique_ptr<PluginListAndSelectComponent> m_pluginSelectionComponent;
370 juce::PluginDescription m_selectedPluginDescription;
371
372 std::map<int, Mema::PluginParameterInfo> m_parameterInfos;
373 std::vector<int> m_parameterDisplayOrder;
374
375 std::unique_ptr<ParameterListComponent> m_messageBoxParameterListComponent;
376 std::unique_ptr<juce::Viewport> m_messageBoxParameterTogglesViewport;
377 std::unique_ptr<juce::AlertWindow> m_messageBox;
378
379
380 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginControlComponent)
381};
382
383} // namespace Mema
CustomPluginListComponentTableModel(juce::PluginListComponent &c, juce::KnownPluginList &l)
void cellClicked(int rowNumber, int columnId, const juce::MouseEvent &e) override
void paintCell(juce::Graphics &g, int row, int columnId, int width, int height, bool) override
static juce::String getPluginDescription(const juce::PluginDescription &desc)
void sortOrderChanged(int newSortColumnId, bool isForwards) override
void selectedRowsChanged(int lastRowSelected) override
void paintRowBackground(juce::Graphics &g, int, int, int, bool rowIsSelected) override
juce::PluginDescription getPluginDescriptionOfSelectedRow()
ParameterRowComponent * getRowForParamIndex(int paramIdx)
std::vector< int > getDisplayOrder() const
void addRow(std::unique_ptr< ParameterRowComponent > row)
~ParameterListComponent() override=default
void reorderRow(int fromParamIndex, int toParamIndex, bool insertBefore)
std::function< void(int fromParamIndex, int toParamIndex, bool insertBefore)> onRowDropped
std::unique_ptr< JUCEAppBasics::FixedFontTextEditor > stepsEdit
void itemDragMove(const SourceDetails &details) override
void itemDropped(const SourceDetails &details) override
std::unique_ptr< juce::ComboBox > typeCombo
void paint(juce::Graphics &g) override
bool isInterestedInDragSource(const SourceDetails &details) override
void itemDragEnter(const SourceDetails &details) override
void mouseDown(const juce::MouseEvent &e) override
void mouseDrag(const juce::MouseEvent &e) override
~ParameterRowComponent() override=default
void itemDragExit(const SourceDetails &details) override
void mouseUp(const juce::MouseEvent &e) override
std::unique_ptr< juce::ToggleButton > toggleButton
void setParameterInfos(const std::vector< PluginParameterInfo > &infos)
void lookAndFeelChanged() override
void setSelectedPlugin(const juce::PluginDescription &pluginDescription)
std::function< void()> onShowPluginEditor
std::function< void(bool)> onPluginPrePostChange
void setPluginEnabled(bool enabled=true)
std::function< void()> onPluginParametersStatusChanged
const std::map< int, Mema::PluginParameterInfo > & getParameterInfos()
void setPluginPrePost(bool post=false)
std::function< void(bool)> onPluginEnabledChange
std::function< void(const juce::PluginDescription &)> onPluginSelected
void setParameterDisplayOrder(const std::vector< int > &order)
void showPluginsList(juce::Point< int > showPosition)
void paint(Graphics &) override
const std::vector< int > & getParameterDisplayOrder()
std::function< void(const juce::PluginDescription &)> onPluginSelected
Spacing()=default
void paint(Graphics &g) override
~Spacing()=default
Metadata describing a single plugin parameter exposed for remote control.