Skip to content

Commit 9a39b1d

Browse files
authored
Merge pull request #1137 from kushview/favorites-hide-plugins
ui: add plugin favorites and visibility options
2 parents 5e8b686 + 6b37b19 commit 9a39b1d

11 files changed

Lines changed: 417 additions & 18 deletions

‎include/element/plugins.hpp‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,55 @@ class PluginManager : public juce::ChangeBroadcaster {
127127
/** Get the first node factory by format. e.g. "LV2" */
128128
NodeProvider* getProvider (const juce::String& format) noexcept;
129129

130+
//==============================================================================
131+
/** Returns true if the plugin is hidden from the "Add Plugin" surfaces.
132+
133+
Hidden state is user curation stored separately from the known plugins
134+
list (keyed by juce::PluginDescription::createIdentifierString()) so it
135+
survives plugin rescans.
136+
137+
@param desc the plugin to query
138+
@return true if the plugin is currently hidden
139+
*/
140+
bool isPluginHidden (const juce::PluginDescription& desc) const;
141+
142+
/** Shows or hides a plugin. Persists immediately and broadcasts a change.
143+
144+
@param desc the plugin to update
145+
@param hidden true to hide the plugin from "Add Plugin" surfaces
146+
*/
147+
void setPluginHidden (const juce::PluginDescription& desc, bool hidden);
148+
149+
/** Returns true if the plugin is marked as a favorite.
150+
151+
@param desc the plugin to query
152+
@return true if the plugin is a favorite
153+
*/
154+
bool isPluginFavorite (const juce::PluginDescription& desc) const;
155+
156+
/** Marks or unmarks a plugin as a favorite. Persists immediately and
157+
broadcasts a change.
158+
159+
@param desc the plugin to update
160+
@param favorite true to mark the plugin as a favorite
161+
*/
162+
void setPluginFavorite (const juce::PluginDescription& desc, bool favorite);
163+
164+
/** Returns all known plugin types with hidden ones removed.
165+
166+
This is the shared filter that every "Add Plugin" surface should route
167+
through. The result preserves the order of getKnownPlugins().getTypes().
168+
169+
@return the visible (non-hidden) plugin types
170+
*/
171+
juce::Array<juce::PluginDescription> getVisiblePluginTypes() const;
172+
173+
/** Returns the visible plugin types that are marked as favorites.
174+
175+
@return the favorite plugin types (excludes hidden plugins)
176+
*/
177+
juce::Array<juce::PluginDescription> getFavoritePluginTypes() const;
178+
130179
private:
131180
friend class PluginScanner;
132181
juce::PropertiesFile* props = nullptr;

‎include/element/tags.hpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ EL_TAG (Script)
2727
EL_TAG (Session)
2828

2929
EL_TAG (AudioChannelSet)
30+
EL_TAG (PluginMetadata)
3031
} // namespace types
3132

3233
namespace tags {
@@ -152,6 +153,8 @@ static const juce::Identifier renderMode = "renderMode";
152153
static const juce::Identifier staticPos = "staticPos";
153154

154155
static const juce::Identifier plugin = "plugin";
156+
static const juce::Identifier hidden = "hidden";
157+
static const juce::Identifier favorite = "favorite";
155158

156159
static const juce::Identifier windowOnTop = "windowOnTop";
157160
static const juce::Identifier windowVisible = "windowVisible";

‎src/pluginmanager.cpp‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ static const char* pluginListKey() { return Settings::pluginListKey; }
3636
/* noop. prevent OS error dialogs from child process */
3737
static void pluginScannerCrashHandler (void*) {}
3838
static File pluginsXmlFile() { return DataPath::applicationDataDir().getChildFile ("plugins.xml"); }
39+
static File pluginMetadataXmlFile() { return DataPath::applicationDataDir().getChildFile ("plugin-metadata.xml"); }
3940

4041
static FileSearchPath readSearchPath (const PropertiesFile& props, const String& f)
4142
{
@@ -638,6 +639,53 @@ class PluginManager::Private : public PluginScanner::Listener
638639
unverified.searchForPlugins (props);
639640
}
640641

642+
/** Load hidden/favorite curation from a metadata file (keyed by
643+
PluginDescription::createIdentifierString()). */
644+
void loadPluginMetadata (const File& file)
645+
{
646+
hiddenPlugins.clearQuick();
647+
favoritePlugins.clearQuick();
648+
649+
auto xml = XmlDocument::parse (file);
650+
if (xml == nullptr || ! xml->hasTagName (types::PluginMetadata.toString()))
651+
return;
652+
653+
for (auto* const e : xml->getChildWithTagNameIterator (tags::plugin.toString()))
654+
{
655+
const auto id = e->getStringAttribute (tags::identifier.toString());
656+
if (id.isEmpty())
657+
continue;
658+
if (e->getBoolAttribute (tags::hidden.toString()))
659+
hiddenPlugins.addIfNotAlreadyThere (id);
660+
if (e->getBoolAttribute (tags::favorite.toString()))
661+
favoritePlugins.addIfNotAlreadyThere (id);
662+
}
663+
}
664+
665+
/** Persist hidden/favorite curation. Only non-default entries are written. */
666+
void savePluginMetadata (const File& file)
667+
{
668+
XmlElement xml (types::PluginMetadata.toString());
669+
670+
StringArray ids;
671+
ids.addArray (hiddenPlugins);
672+
ids.addArray (favoritePlugins);
673+
ids.removeDuplicates (false);
674+
675+
for (const auto& id : ids)
676+
{
677+
auto* const e = xml.createNewChildElement (tags::plugin.toString());
678+
e->setAttribute (tags::identifier.toString(), id);
679+
if (hiddenPlugins.contains (id))
680+
e->setAttribute (tags::hidden.toString(), 1);
681+
if (favoritePlugins.contains (id))
682+
e->setAttribute (tags::favorite.toString(), 1);
683+
}
684+
685+
file.getParentDirectory().createDirectory();
686+
xml.writeTo (file);
687+
}
688+
641689
void getUnverifiedPlugins (const String& format, OwnedArray<PluginDescription>& plugs)
642690
{
643691
unverified.getPlugins (plugs, format, allPlugins);
@@ -648,6 +696,7 @@ class PluginManager::Private : public PluginScanner::Listener
648696
PluginManager& owner;
649697
AudioPluginFormatManager formats;
650698
KnownPluginList allPlugins;
699+
StringArray hiddenPlugins, favoritePlugins;
651700
File deadAudioPlugins;
652701
UnverifiedPlugins unverified;
653702
NodeFactory nodes;
@@ -951,6 +1000,7 @@ void PluginManager::restoreUserPlugins (const XmlElement& xml)
9511000
priv->allPlugins.recreateFromXml (xml);
9521001
scanInternalPlugins();
9531002
priv->updateBlacklistedAudioPlugins();
1003+
priv->loadPluginMetadata (detail::pluginMetadataXmlFile());
9541004
if (props == nullptr)
9551005
return;
9561006
}
@@ -1119,4 +1169,58 @@ NodeProvider* PluginManager::getProvider (const String& format) noexcept
11191169
return nullptr;
11201170
}
11211171

1172+
//==============================================================================
1173+
bool PluginManager::isPluginHidden (const PluginDescription& desc) const
1174+
{
1175+
return priv->hiddenPlugins.contains (desc.createIdentifierString());
1176+
}
1177+
1178+
void PluginManager::setPluginHidden (const PluginDescription& desc, bool hidden)
1179+
{
1180+
const auto id = desc.createIdentifierString();
1181+
if (hidden)
1182+
priv->hiddenPlugins.addIfNotAlreadyThere (id);
1183+
else
1184+
priv->hiddenPlugins.removeString (id);
1185+
priv->savePluginMetadata (detail::pluginMetadataXmlFile());
1186+
priv->allPlugins.sendChangeMessage();
1187+
}
1188+
1189+
bool PluginManager::isPluginFavorite (const PluginDescription& desc) const
1190+
{
1191+
return priv->favoritePlugins.contains (desc.createIdentifierString());
1192+
}
1193+
1194+
void PluginManager::setPluginFavorite (const PluginDescription& desc, bool favorite)
1195+
{
1196+
const auto id = desc.createIdentifierString();
1197+
if (favorite)
1198+
priv->favoritePlugins.addIfNotAlreadyThere (id);
1199+
else
1200+
priv->favoritePlugins.removeString (id);
1201+
priv->savePluginMetadata (detail::pluginMetadataXmlFile());
1202+
priv->allPlugins.sendChangeMessage();
1203+
}
1204+
1205+
Array<PluginDescription> PluginManager::getVisiblePluginTypes() const
1206+
{
1207+
Array<PluginDescription> result;
1208+
for (const auto& desc : priv->allPlugins.getTypes())
1209+
if (! priv->hiddenPlugins.contains (desc.createIdentifierString()))
1210+
result.add (desc);
1211+
return result;
1212+
}
1213+
1214+
Array<PluginDescription> PluginManager::getFavoritePluginTypes() const
1215+
{
1216+
Array<PluginDescription> result;
1217+
for (const auto& desc : priv->allPlugins.getTypes())
1218+
{
1219+
const auto id = desc.createIdentifierString();
1220+
if (priv->favoritePlugins.contains (id) && ! priv->hiddenPlugins.contains (id))
1221+
result.add (desc);
1222+
}
1223+
return result;
1224+
}
1225+
11221226
} // namespace element

‎src/ui/block.cpp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,9 @@ void BlockComponent::mouseDown (const MouseEvent& e)
555555
const int result = menu.show();
556556
colorSelector.removeChangeListener (this);
557557

558-
const auto types = plugins.getKnownPlugins().getTypes();
558+
// Must match the array used by NodePopupMenu::addReplaceSubmenu so the
559+
// menu index resolves to the correct plugin.
560+
const auto types = plugins.getVisiblePluginTypes();
559561

560562
if (auto* message = menu.createMessageForResultCode (result))
561563
{

‎src/ui/contextmenus.hpp‎

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,32 @@ class PluginsPopupMenu : public PopupMenu
2424
jassert (cc != nullptr);
2525
plugins = &cc->context().plugins();
2626
jassert (plugins != nullptr);
27-
available = plugins->getKnownPlugins().getTypes();
27+
available = plugins->getVisiblePluginTypes();
28+
favorites = plugins->getFavoritePluginTypes();
2829
}
2930

3031
bool isPluginResultCode (const int resultCode)
3132
{
3233
// clang-format off
33-
return (plugins->getKnownPlugins().getIndexChosenByMenu (available, resultCode) >= 0) ||
34+
return (isPositiveAndBelow (int (resultCode - favoriteResultOffset), favorites.size())) ||
35+
(plugins->getKnownPlugins().getIndexChosenByMenu (available, resultCode) >= 0) ||
3436
(isPositiveAndBelow (int (resultCode - 20000), unverified.size()));
3537
// clang-format on
3638
}
3739

3840
PluginDescription getPluginDescription (int resultCode, bool& verified)
3941
{
4042
jassert (plugins != nullptr);
41-
int index = plugins->getKnownPlugins().getIndexChosenByMenu (available, resultCode);
43+
44+
// Favorites submenu — a subset of the visible/known plugins, so verified.
45+
int index = resultCode - favoriteResultOffset;
46+
if (isPositiveAndBelow (index, favorites.size()))
47+
{
48+
verified = true;
49+
return favorites.getReference (index);
50+
}
51+
52+
index = plugins->getKnownPlugins().getIndexChosenByMenu (available, resultCode);
4253
if (isPositiveAndBelow (index, available.size()))
4354
{
4455
verified = true;
@@ -57,6 +68,16 @@ class PluginsPopupMenu : public PopupMenu
5768
if (hasAddedPlugins)
5869
return;
5970
hasAddedPlugins = true;
71+
72+
if (! favorites.isEmpty())
73+
{
74+
PopupMenu favMenu;
75+
for (int i = 0; i < favorites.size(); ++i)
76+
favMenu.addItem (favoriteResultOffset + i, favorites.getReference (i).name);
77+
addSubMenu ("Favorites", favMenu);
78+
addSeparator();
79+
}
80+
6081
plugins->getKnownPlugins().addToMenu (*this, available, KnownPluginList::sortByManufacturer);
6182

6283
PopupMenu unvMenu;
@@ -89,7 +110,11 @@ class PluginsPopupMenu : public PopupMenu
89110
}
90111

91112
private:
92-
Array<PluginDescription> available;
113+
// Favorites use a private menu-id range so they never collide with the
114+
// main list (which uses KnownPluginList's large menuIdBase) or the
115+
// unverified submenu (which uses the 20000 range).
116+
static constexpr int favoriteResultOffset = 10000;
117+
Array<PluginDescription> available, favorites;
93118
OwnedArray<PluginDescription> unverified;
94119
PluginManager* plugins { nullptr };
95120
bool hasAddedPlugins = false;
@@ -222,7 +247,7 @@ class NodePopupMenu : public PopupMenu
222247
#if ! ELEMENT_SE
223248
PopupMenu menu;
224249
KnownPluginList::addToMenu (menu,
225-
plugins.getKnownPlugins().getTypes(),
250+
plugins.getVisiblePluginTypes(),
226251
KnownPluginList::sortByManufacturer,
227252
node.getFileOrIdentifier().toString());
228253
addSubMenu ("Replace", menu);

‎src/ui/navigationview.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class PluginsNavigationItem : public TreeItemBase
133133
PluginsNavigationItem (PluginManager& pm)
134134
: plugins (pm)
135135
{
136-
types = plugins.getKnownPlugins().getTypes();
136+
types = plugins.getVisiblePluginTypes();
137137
}
138138

139139
~PluginsNavigationItem() {}

0 commit comments

Comments
 (0)