Skip to content

feat(#6716): ✨ added effect categories - #8493

Open
clemetayer wants to merge 10 commits into
LMMS:masterfrom
clemetayer:#6716_effects_groups
Open

feat(#6716): ✨ added effect categories#8493
clemetayer wants to merge 10 commits into
LMMS:masterfrom
clemetayer:#6716_effects_groups

Conversation

@clemetayer

@clemetayer clemetayer commented Aug 2, 2026

Copy link
Copy Markdown

# Changes

  • Added a third "Category" column in the effect browser
  • Added a filter for this new "Category"
  • Changed the existing "Type" buttons to a ComboBox (so it makes sense visually with the new effect category filter)

# Notes for the reviewer(s)

  • The effect categories are arbitrary. I tried to make sense of the various docs available online, but it may be inaccurate.
    • Note : It seems that the Steve Harris plugins documentation is only available online through the Wayback Machine.
  • I tried to assign an effect directly in the constructor of the effect (the first commit) to make it more robust (to warn the developer at compile time that an effect category was forgotten), but I reverted it and went with a simple map of name<->category instead because :
    1. The effect browser uses the EffectKeyList type to display the effect, which is not directly compatible with the Effect type and is also used for other plugin types (instruments, etc.)
    2. When mentioning the issue in the Discord, it seems that users would like to assign their own categories or even have multiple categories for an effect. While this PR does not have these features, it seems that using a name<->category(ies) was the most sensible thing to do.
  • I did not add any translation keys since I am not sure of the best way to proceed.
    • Should each effect category have its own translation ?
    • Is there an automated process to add the translation keys in each language file ?
  • This new feature only works with LADSPA and LMMS effects. LV2 and VST effects are labelled as "Other" for now.

# Screenshot of the feature

EffectsScreenshot

@yohannd1 yohannd1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style review for now. Will do a more in-depth analysis later.

Comment thread include/EffectCategory.h Outdated
private:
static std::unique_ptr<EffectCategory> s_instance;
QStringList* m_categories;
QStringList* getCategoriesFromMap(std::map<QString,QString> map);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
QStringList* getCategoriesFromMap(std::map<QString,QString> map);
QStringList* getCategoriesFromMap(std::map<QString, QString> map);

Comment thread include/EffectCategory.h

// Short-hand function
LMMS_EXPORT EffectCategory* getEffectCategory();
} // namespace lmms

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} // namespace lmms
} // namespace lmms

Comment thread src/core/EffectCategory.cpp Outdated
Comment on lines +287 to +288
EffectCategory* getEffectCategory()
{ return EffectCategory::instance(); }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EffectCategory* getEffectCategory()
{ return EffectCategory::instance(); }
EffectCategory* getEffectCategory() { return EffectCategory::instance(); }

Comment thread src/core/EffectCategory.cpp Outdated

QStringList* EffectCategory::getCategories()
{
if(m_categories == nullptr || m_categories->isEmpty())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(m_categories == nullptr || m_categories->isEmpty())
if (m_categories == nullptr || m_categories->isEmpty())

Comment thread src/core/EffectCategory.cpp Outdated
{
m_categories = getCategoriesFromMap(lmmsEffects);
QStringList* ladspaCategories = getCategoriesFromMap(ladspaEffects);
foreach(QString category, *ladspaCategories)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foreach(QString category, *ladspaCategories)
for (const QString& category : ladspaCategories)

(Not fully sure about this one, please do try it out)

Comment thread src/core/EffectCategory.cpp Outdated
QStringList* EffectCategory::getCategoriesFromMap(std::map<QString,QString> map)
{
auto* categories = new QStringList();
for(auto & it : map)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for(auto & it : map)
for (auto& it : map)

Comment thread src/core/EffectCategory.cpp Outdated
auto* categories = new QStringList();
for(auto & it : map)
{
if(! categories->contains(it.second))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(! categories->contains(it.second))
if (!categories->contains(it.second))

Comment thread src/gui/modals/EffectSelectDialog.cpp Outdated
Comment on lines +255 to +265
auto labelText = QString{"<p>%1%2</p>"
"<p>%3%4</p>"
"<p>%5%6</p>"}
.arg(tr("<b>Name: </b>"), descriptor.displayName, tr("<b>Author: </b>"),
QString::fromUtf8(descriptor.author)
.replace("/dot/", ".")
.replace("/at/", "@")
.toHtmlEscaped(),
tr("<b>Description: </b>"),
qApp->translate("PluginBrowser", descriptor.description).toHtmlEscaped());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this amount of indentation is not recommended, as we use tabs and they account for 8 spaces (pushing too close, or sometimes beyond the 120-character line limit).
I recommend keeping the style the same as what it was before, or at least using a single tab indent ahead of the lines - e.g.:

auto labelText = QString{"<p>%1%2</p>"
	"<p>%3%4</p>"
	// and so on...

Comment thread src/gui/modals/EffectSelectDialog.cpp Outdated
Comment on lines +325 to +330
connect(buttonFilter, &QComboBox::textActivated, this, [this](QString value)
{
m_model.setEffectTypeFilter(value == tr("All") ? "" : value);
updateSelection();
}
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
connect(buttonFilter, &QComboBox::textActivated, this, [this](QString value)
{
m_model.setEffectTypeFilter(value == tr("All") ? "" : value);
updateSelection();
}
);
connect(buttonFilter, &QComboBox::textActivated, this, [this](QString value)
{
m_model.setEffectTypeFilter(value == tr("All") ? "" : value);
updateSelection();
});

Comment thread src/gui/modals/EffectSelectDialog.cpp Outdated
Comment on lines +345 to +350
connect(buttonFilter, &QComboBox::textActivated, this, [this](QString value)
{
m_model.setEffectCategoryFilter(value == tr("All") ? "" : value);
updateSelection();
}
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
connect(buttonFilter, &QComboBox::textActivated, this, [this](QString value)
{
m_model.setEffectCategoryFilter(value == tr("All") ? "" : value);
updateSelection();
}
);
connect(buttonFilter, &QComboBox::textActivated, this, [this](QString value)
{
m_model.setEffectCategoryFilter(value == tr("All") ? "" : value);
updateSelection();
});

@yohannd1

yohannd1 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Oh, also - I've noticed you're using .h includes for Qt classes - I think you can do like the rest of the codebase does, including directly the name of the class.
So, instead of #include <qboxlayout.h> I believe you can use #include <QBoxLayout>.

@yohannd1 yohannd1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few more style adjustments...

Comment thread include/EffectCategory.h Outdated

// Short-hand function
LMMS_EXPORT EffectCategory* getEffectCategory();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
} // namespace lmms

layout->addWidget(label);
layout->addWidget(buttonFilter);
return layout;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}

@yohannd1

yohannd1 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Okay, now a proper review.

Should each effect category have its own translation ?

I believe yes, at least for these, which are predefined. I don't think it's needed for custom categories.

Is there an automated process to add the translation keys in each language file ?

Pretty sure there is, though I don't know the specific details. I do know that translatable strings are implemented through the tr function (more specifically QObject::tr), so wrapping translatable strings in a tr call should suffice it.

Also, a question: are you planning to implement custom (and multiple) categories on this PR? I think it should be implemented together with this, and I believe it won't require too many changes.

For custom categories, what I propose is a "category manager window" which lists all categories and allows you to add custom categories. And I think saving them on a XML (as you mentioned on Discord) will probably be the best bet here. I'd say saving them on the lmmsrc.xml would be alright, but I think this should be discussed a bit more with the maintainers.

As for multiple categories on a plugin (a.k.a. tags), I feel like it's gonna change the filtering logic significantly, from the way the filtering seems to work on Qt tables.

Comment thread src/core/EffectCategory.cpp Outdated
return m_categories;
}

QStringList* EffectCategory::getCategoriesFromMap(std::map<QString, QString> map)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this can be returned as a plain QStringList. Qt does implicit sharing with this class, plus using the pointer entails manually freeing it later (since it's not attached to a QObject), and that doesn't seem to be done at the moment.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's neat, I did not know that ! I made some changes for this. If I can let the computer deal with the memory management, that's for the better.

@clemetayer

clemetayer commented Aug 3, 2026

Copy link
Copy Markdown
Author

Okay, now a proper review.

Should each effect category have its own translation ?

I believe yes, at least for these, which are predefined. I don't think it's needed for custom categories.

Is there an automated process to add the translation keys in each language file ?

Pretty sure there is, though I don't know the specific details. I do know that translatable strings are implemented through the tr function (more specifically QObject::tr), so wrapping translatable strings in a tr call should suffice it.

Also, a question: are you planning to implement custom (and multiple) categories on this PR? I think it should be implemented together with this, and I believe it won't require too many changes.

For custom categories, what I propose is a "category manager window" which lists all categories and allows you to add custom categories. And I think saving them on a XML (as you mentioned on Discord) will probably be the best bet here. I'd say saving them on the lmmsrc.xml would be alright, but I think this should be discussed a bit more with the maintainers.

As for multiple categories on a plugin (a.k.a. tags), I feel like it's gonna change the filtering logic significantly, from the way the filtering seems to work on Qt tables.

Okay, so I added QObject::tr for each category. I tried to add Q_OBJECT in the .h file to make it easier to read, but that did not work, probably because I made my object a singleton ?

For the custom category, I think it would be best if I do that in another PR. I tend to hop between projects, and I might do the other steps later (in a few weeks probably), but I wanted this dev to still be an improvement that could be merged (even if it's not great yet). That might also give users some time to give some feedbacks.

I will probably add the custom category next, but I rather thought to do that with a menu appearing on a right click. I might also add another column dedicated to favorites in the meantime (kind of boolean style column, with a star appearing if it is a favorite with the associated filter) since I'm starting to understand how it works.

After that I will probably try to add the multiple categories to an effect, but I'm having a hard time to make it work UI-wise (should every category appear on the same line ? Is there a limit ? Should it be on multiple lines ? Etc.).

@yohannd1

yohannd1 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Okay, so I added QObject::tr for each category. I tried to add Q_OBJECT in the .h file to make it easier to read, but that did not work, probably because I made my object a singleton ?

Did you mean for using tr(...) instead of QObject::tr(...)? If yes, I think that only works inside methods.
By the way, I'm just now thinking about this - it might be better if the translated names are only when displaying, and the category name is internal. For now it's alright, but I'm imagining using translated versions might make a mess when saving to files, and potentially switching languages.
Maybe having a map of names and their possible translations? Not quite sure how this would work.

For the custom category, I think it would be best if I do that in another PR. [...]

Sadly PRs in LMMS take quite a long time to be merged, due to the small amount of maintainers and free time. This PR won't probably be merged in a while yet.

I will probably add the custom category next, but I rather thought to do that with a menu appearing on a right click. [...]

Oh, this sounds good to me!

After that I will probably try to add the multiple categories to an effect, but I'm having a hard time to make it work UI-wise (should every category appear on the same line ? Is there a limit ? Should it be on multiple lines ? Etc.).

I think they should appear on the same line. It might make things harder to read, yeah. Maybe just adding ... if there's too many, and on hover it could show all columns? (This might already be provided by Qt tables).

@clemetayer

Copy link
Copy Markdown
Author

Did you mean for using tr(...) instead of QObject::tr(...)? If yes, I think that only works inside methods.
By the way, I'm just now thinking about this - it might be better if the translated names are only when displaying, and the category name is internal. For now it's alright, but I'm imagining using translated versions might make a mess when saving to files, and potentially switching languages.
Maybe having a map of names and their possible translations? Not quite sure how this would work.

Yes, that's exactly that, and yes, that makes complete sense now that you mention it.
For the next step, yes, I was thinking of a map or list of some default categories that can be translated. I will probably figure it out once i am working on it.

Sadly PRs in LMMS take quite a long time to be merged, due to the small amount of maintainers and free time. This PR won't probably be merged in a while yet.

Yes, okay, that's fair. I will still work in another dedicated branch once I get back to it and maybe merge it to this one if it is not merged yet then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants