Fixed plugins categories and thumbnails

This commit is contained in:
Eism 2022-06-02 15:00:32 +03:00
parent 33e20a68d8
commit 77b7bd4f15
33 changed files with 164 additions and 82 deletions

View file

@ -27,6 +27,7 @@ MuseScore {
version: "3.5"
description: qsTr("This plugin colors notes in the selection depending on their pitch as per the Boomwhackers convention")
menuPath: "Plugins.Notes.Color Notes"
categoryCode: "color-notes"
property variant colors : [ // "#rrggbb" with rr, gg, and bb being the hex values for red, green, and blue, respectively
"#e21c48", // C

View file

@ -26,6 +26,7 @@ MuseScore {
description: "This plugin adds courtesy accidentals"
menuPath: "Plugins.Accidentals.Add Courtesy Accidentals"
requiresScore: true
categoryCode: "composing-arranging-tools"
// configuration
// This has changed for MuseScore v3

View file

@ -28,6 +28,7 @@ MuseScore {
version: "1.0"
description: "This plugin adds courtesy accidentals"
menuPath: "Plugins.Accidentals.Configure Courtesy Accidentals"
categoryCode: "composing-arranging-tools"
requiresScore: true

View file

@ -25,6 +25,7 @@ MuseScore {
version: "1.0"
description: "This plugin removes courtesy accidentals"
menuPath: "Plugins.Accidentals.Remove Courtesy Accidentals"
categoryCode: "composing-arranging-tools"
//pluginType: "dock"
requiresScore: true

View file

@ -26,6 +26,8 @@ MuseScore {
menuPath: "Plugins.Composing Tools.Mirror Intervals"
description: "Mirrors (inverts) intervals about a given pivot note"
pluginType: "dialog"
categoryCode: "composing-arranging-tools"
width: 250
height: 150

View file

@ -17,6 +17,7 @@ MuseScore {
menuPath: "Plugins.NewRetrograde"
description: "Takes a selection of notes and reverses them."
version: "1.0"
categoryCode: "composing-arranging-tools"
function retrogradeSelection() {
var cursor = curScore.newCursor(); // get the selection

View file

@ -22,6 +22,7 @@ MuseScore {
version: "3.6"
description: "This plugin names notes as per your language setting"
menuPath: "Plugins.Notes." + "Note Names"
categoryCode: "composing-arranging-tools"
// Small note name size is fraction of the full font size.
property real fontSizeMini: 0.7;

View file

@ -27,6 +27,8 @@ MuseScore {
menuPath: "Plugins.Playback.Tuning"
description: "Apply various temperaments and tunings"
pluginType: "dialog"
categoryCode: "playback"
width: 790
height: 544

View file

@ -27,6 +27,8 @@ MuseScore {
menuPath: "Plugins.Playback.Modal_Tuning"
description: "Apply various temperaments and tunings"
pluginType: "dialog"
categoryCode: "playback"
width: 860
height: 722

View file

@ -27,6 +27,8 @@ MuseScore {
menuPath: "Plugins.Playback.Temperaments"
description: "Apply various temperaments and tunings"
pluginType: "dialog"
categoryCode: "playback"
width: 900
height: 722

View file

@ -137,7 +137,7 @@ FocusScope {
for (var i = 0; i < categories.length; ++i) {
var category = categories[i]
result.push({ "text": category, "value": category })
result.push({ "text": category.title, "value": category.code })
}
model = result

View file

@ -621,13 +621,36 @@ MenuItemList AppMenuModel::makePluginsItems()
{
MenuItemList result;
for (const PluginInfo& plugin : pluginsService()->plugins(IPluginsService::Enabled).val) {
MenuItem* pluginItem = makeMenuItem(plugin.codeKey.toStdString(), plugin.name);
if (!pluginItem) {
continue;
}
IPluginsService::CategoryInfoMap categories = pluginsService()->categories();
PluginInfoMap enabledPlugins = pluginsService()->plugins(IPluginsService::Enabled).val;
result << pluginItem;
std::map<std::string, MenuItemList> categoriesMap;
MenuItemList pluginsWithoutCategories;
for (const PluginInfo& plugin : values(enabledPlugins)) {
std::string categoryStr = plugin.categoryCode.toStdString();
if (contains(categories, categoryStr)) {
MenuItemList& items = categoriesMap[categoryStr];
items << makeMenuItem(plugin.codeKey.toStdString(), plugin.name);
} else {
pluginsWithoutCategories << makeMenuItem(plugin.codeKey.toStdString(), plugin.name);
}
}
for (const auto& it : categoriesMap) {
QString categoryTitle = QString::fromStdString(value(categories, it.first, ""));
result << makeMenu(categoryTitle, it.second);
}
std::sort(result.begin(), result.end(), [](const MenuItem& l, const MenuItem& r) {
return l.title() < r.title();
});
std::sort(pluginsWithoutCategories.begin(), pluginsWithoutCategories.end(), [](const MenuItem& l, const MenuItem& r) {
return l.title() < r.title();
});
for (MenuItem* plugin : pluginsWithoutCategories) {
result << plugin;
}
return result;

View file

@ -47,6 +47,8 @@ class MScore;
// @P pluginType QString type may be dialog, dock, or not defined.
// @P dockArea QString where to dock on main screen. left,top,bottom, right(default)
// @P requiresScore bool whether the plugin requires an existing score to run
// @P thumbnailName QString the thumbnail of this plugin
// @P categoryCode QString the code of category this plugin belongs to
// @P division int number of MIDI ticks for 1/4 note (read only)
// @P mscoreVersion int complete version number of MuseScore in the form: MMmmuu (read only)
// @P mscoreMajorVersion int 1st part of the MuseScore version (read only)
@ -63,10 +65,11 @@ class QmlPlugin : public QQuickItem
QString _menuPath;
QString _pluginType;
QString _dockArea;
bool _requiresScore = true;
QString _version;
QString _description;
QString _thumbnailName;
QString _categoryCode;
protected:
QString _filePath; // the path of the source file, without file name
@ -89,11 +92,13 @@ public:
QString description() const { return _description; }
void setFilePath(const QString s) { _filePath = s; }
QString filePath() const { return _filePath; }
void setThumbnailName(const QString& s) { _thumbnailName = s; }
QString thumbnailName() const { return _thumbnailName; }
void setCategoryCode(const QString& s) { _categoryCode = s; }
QString categoryCode() const { return _categoryCode; }
void setPluginType(const QString& s) { _pluginType = s; }
QString pluginType() const { return _pluginType; }
void setDockArea(const QString& s) { _dockArea = s; }
QString dockArea() const { return _dockArea; }
void setRequiresScore(bool b) { _requiresScore = b; }
bool requiresScore() const { return _requiresScore; }

View file

@ -94,6 +94,10 @@ class PluginAPI : public mu::engraving::QmlPlugin
Q_PROPERTY(QString dockArea READ dockArea WRITE setDockArea)
/** Whether the plugin requires an existing score to run, default is `true` */
Q_PROPERTY(bool requiresScore READ requiresScore WRITE setRequiresScore)
/** The name of the thumbnail that should be next to the plugin */
Q_PROPERTY(QString thumbnailName READ thumbnailName WRITE setThumbnailName)
/** The code of the category */
Q_PROPERTY(QString categoryCode READ categoryCode WRITE setCategoryCode)
/**
* \brief Number of MIDI ticks for 1/4 note (read only)
* \see \ref ticklength

View file

@ -21,6 +21,7 @@
*/
#include "pluginsactioncontroller.h"
#include "containers.h"
#include "translation.h"
#include "log.h"
@ -41,7 +42,7 @@ void PluginsActionController::registerPlugins()
{
dispatcher()->unReg(this);
for (const PluginInfo& plugin : service()->plugins().val) {
for (const PluginInfo& plugin : values(service()->plugins().val)) {
dispatcher()->reg(this, plugin.codeKey.toStdString(), [this, codeKey = plugin.codeKey]() {
onPluginTriggered(codeKey);
});
@ -59,7 +60,7 @@ void PluginsActionController::onPluginTriggered(const CodeKey& codeKey)
bool found = false;
QString pluginName;
for (const PluginInfo& plugin : plugins) {
for (const PluginInfo& plugin : values(plugins)) {
if (plugin.codeKey == codeKey) {
enabled = plugin.enabled;
found = true;

View file

@ -29,6 +29,8 @@
#include "ui/uitypes.h"
#include "translation.h"
#include "settings.h"
#include "log.h"

View file

@ -29,6 +29,7 @@
#include "view/pluginview.h"
#include "containers.h"
#include "log.h"
using namespace mu::plugins;
@ -56,17 +57,17 @@ void PluginsService::reloadPlugins()
m_pluginsChanged.notify();
}
mu::RetVal<PluginInfoList> PluginsService::plugins(PluginsStatus status) const
mu::RetVal<PluginInfoMap> PluginsService::plugins(PluginsStatus status) const
{
PluginInfoList result;
PluginInfoMap result;
for (const PluginInfo& plugin: m_plugins) {
for (const PluginInfo& plugin: values(m_plugins)) {
if (isAccepted(plugin.codeKey, status)) {
result << plugin;
result.insert({ plugin.codeKey, plugin });
}
}
return RetVal<PluginInfoList>::make_ok(result);
return RetVal<PluginInfoMap>::make_ok(result);
}
Notification PluginsService::pluginsChanged() const
@ -74,6 +75,15 @@ Notification PluginsService::pluginsChanged() const
return m_pluginsChanged;
}
PluginsService::CategoryInfoMap PluginsService::categories() const
{
return {
{ "composing-arranging-tools", trc("plugin", "Composing/arranging tools") },
{ "color-notes", trc("plugin", "Colour notes") },
{ "playback", trc("plugin", "Playback") }
};
}
bool PluginsService::isAccepted(const CodeKey& codeKey, PluginsStatus status) const
{
switch (status) {
@ -84,11 +94,11 @@ bool PluginsService::isAccepted(const CodeKey& codeKey, PluginsStatus status) co
return false;
}
PluginInfoList PluginsService::readPlugins() const
PluginInfoMap PluginsService::readPlugins() const
{
TRACEFUNC;
PluginInfoList result;
PluginInfoMap result;
io::paths_t pluginsPaths = scanFileSystemForPlugins();
const PluginConfigurationHash& pluginsConfigurationHash = pluginsConfiguration();
@ -104,12 +114,18 @@ PluginInfoList PluginsService::readPlugins() const
info.description = view.description();
info.version = view.version();
info.enabled = pluginsConfigurationHash.value(info.codeKey).enabled;
info.categoryCode = view.categoryCode();
QString thumbnailName = view.thumbnailName();
if (!thumbnailName.isEmpty()) {
info.thumbnailUrl = io::dirpath(pluginPath).toQString() + "/" + thumbnailName;
}
auto sequences = shortcutsRegister()->shortcut(info.codeKey.toStdString()).sequences;
info.shortcuts = Shortcut::sequencesToString(sequences);
if (info.isValid()) {
result << info;
result.insert({ info.codeKey, info });
}
}
@ -171,14 +187,14 @@ mu::Ret PluginsService::setEnable(const CodeKey& codeKey, bool enable)
PluginInfo& PluginsService::pluginInfo(const CodeKey& codeKey)
{
for (PluginInfo& plugin: m_plugins) {
if (plugin.codeKey == codeKey) {
return plugin;
}
auto it = m_plugins.find(codeKey);
if (it == m_plugins.end()) {
static PluginInfo _dummy;
return _dummy;
}
static PluginInfo _dummy;
return _dummy;
return it->second;
}
void PluginsService::registerShortcuts()
@ -189,7 +205,8 @@ void PluginsService::registerShortcuts()
ShortcutList shortcuts;
for (const PluginInfo& plugin : m_plugins) {
for (auto& it : m_plugins) {
PluginInfo& plugin = it.second;
Shortcut shortcut;
shortcut.action = plugin.codeKey.toStdString();
@ -251,9 +268,10 @@ void PluginsService::onShortcutsChanged()
PluginConfigurationHash pluginsConfigurationHash = this->pluginsConfiguration();
PluginInfoList changedPlugins;
std::vector<PluginInfo> changedPlugins;
for (PluginInfo& plugin : m_plugins) {
for (auto& it : m_plugins) {
PluginInfo& plugin = it.second;
const Shortcut shortcut = shortcutsRegister()->shortcut(plugin.codeKey.toStdString());
if (shortcut.sequences.empty() && !pluginsConfigurationHash.contains(plugin.codeKey)) {
continue;

View file

@ -47,9 +47,11 @@ public:
void reloadPlugins() override;
mu::RetVal<PluginInfoList> plugins(PluginsStatus status = PluginsStatus::All) const override;
mu::RetVal<PluginInfoMap> plugins(PluginsStatus status = PluginsStatus::All) const override;
async::Notification pluginsChanged() const override;
CategoryInfoMap categories() const override;
Ret setEnable(const CodeKey& codeKey, bool enable) override;
Ret run(const CodeKey& codeKey) override;
@ -64,14 +66,14 @@ private:
const IPluginsConfiguration::PluginsConfigurationHash& pluginsConfiguration() const;
void setPluginsConfiguration(const IPluginsConfiguration::PluginsConfigurationHash& pluginsConfiguration);
PluginInfoList readPlugins() const;
PluginInfoMap readPlugins() const;
io::paths_t scanFileSystemForPlugins() const;
PluginInfo& pluginInfo(const CodeKey& codeKey);
void registerShortcuts();
mutable PluginInfoList m_plugins;
mutable PluginInfoMap m_plugins;
async::Notification m_pluginsChanged;
async::Channel<PluginInfo> m_pluginChanged;
};

View file

@ -24,6 +24,7 @@
#include "ui/view/iconcodes.h"
#include "context/uicontext.h"
#include "containers.h"
#include "translation.h"
#include "log.h"
@ -46,7 +47,7 @@ const mu::ui::UiActionList& PluginsUiActions::actionsList() const
{
UiActionList result;
for (const PluginInfo& plugin : m_service->plugins().val) {
for (const PluginInfo& plugin : values(m_service->plugins().val)) {
UiAction action;
action.code = codeFromQString(plugin.codeKey);
action.context = mu::context::UiCtxNotationOpened;

View file

@ -42,9 +42,12 @@ public:
virtual void reloadPlugins() = 0;
virtual RetVal<PluginInfoList> plugins(PluginsStatus status = All) const = 0;
virtual RetVal<PluginInfoMap> plugins(PluginsStatus status = All) const = 0;
virtual async::Notification pluginsChanged() const = 0;
using CategoryInfoMap = std::map<std::string /*code*/, std::string /*title*/>;
virtual CategoryInfoMap categories() const = 0;
virtual Ret setEnable(const CodeKey& codeKey, bool enable) = 0;
virtual Ret run(const CodeKey& codeKey) = 0;

View file

@ -4,13 +4,7 @@
<file>qml/MuseScore/Plugins/PluginsPage.qml</file>
<file>qml/MuseScore/Plugins/internal/PluginItem.qml</file>
<file>qml/MuseScore/Plugins/internal/PluginsListView.qml</file>
<file>qml/MuseScore/Plugins/internal/placeholders/placeholder1.jpeg</file>
<file>qml/MuseScore/Plugins/internal/placeholders/placeholder2.jpeg</file>
<file>qml/MuseScore/Plugins/internal/placeholders/placeholder3.jpeg</file>
<file>qml/MuseScore/Plugins/internal/placeholders/placeholder4.jpeg</file>
<file>qml/MuseScore/Plugins/internal/placeholders/placeholder5.jpeg</file>
<file>qml/MuseScore/Plugins/internal/placeholders/placeholder6.jpeg</file>
<file>qml/MuseScore/Plugins/internal/placeholders/placeholder7.jpeg</file>
<file>qml/MuseScore/Plugins/internal/EnablePanel.qml</file>
<file>qml/MuseScore/Plugins/internal/placeholders/placeholder.jpeg</file>
</qresource>
</RCC>

View file

@ -40,7 +40,7 @@ struct PluginInfo
QUrl detailsUrl;
QString name;
QString description;
QString category;
QString categoryCode;
bool enabled = false;
bool hasUpdate = false;
QVersionNumber version;
@ -50,7 +50,7 @@ struct PluginInfo
bool operator==(const PluginInfo& other) const { return other.codeKey == codeKey; }
};
using PluginInfoList = QList<PluginInfo>;
using PluginInfoMap = std::map<CodeKey, PluginInfo>;
}
#endif // MU_PLUGINS_IPLUGINSCONFIGURATION_H

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

View file

@ -22,6 +22,7 @@
#include "pluginsmodel.h"
#include "containers.h"
#include "translation.h"
#include "log.h"
@ -51,36 +52,19 @@ void PluginsModel::load()
beginResetModel();
m_plugins.clear();
// TODO: this is temporary solution and will be changed in future
QList<QString> thumbnailUrlExamples {
"qrc:/qml/MuseScore/Plugins/internal/placeholders/placeholder1.jpeg",
"qrc:/qml/MuseScore/Plugins/internal/placeholders/placeholder2.jpeg",
"qrc:/qml/MuseScore/Plugins/internal/placeholders/placeholder3.jpeg",
"qrc:/qml/MuseScore/Plugins/internal/placeholders/placeholder4.jpeg",
"qrc:/qml/MuseScore/Plugins/internal/placeholders/placeholder5.jpeg",
"qrc:/qml/MuseScore/Plugins/internal/placeholders/placeholder6.jpeg",
"qrc:/qml/MuseScore/Plugins/internal/placeholders/placeholder7.jpeg"
};
QList<QString> categoriesExamples {
"Simplified notation",
"Other",
"Accidentals",
"Notes & Rests",
"Chord symbols"
};
RetVal<PluginInfoList> plugins = service()->plugins();
RetVal<PluginInfoMap> plugins = service()->plugins();
if (!plugins.ret) {
LOGE() << plugins.ret.toString();
}
for (int i = 0; i < plugins.val.size(); ++i) {
plugins.val[i].thumbnailUrl = thumbnailUrlExamples[i % thumbnailUrlExamples.size()];
plugins.val[i].category = categoriesExamples[i % categoriesExamples.size()];
m_plugins << plugins.val[i];
for (const PluginInfo& plugin : values(plugins.val)) {
m_plugins << plugin;
}
std::sort(m_plugins.begin(), m_plugins.end(), [](const PluginInfo& l, const PluginInfo& r) {
return l.name < r.name;
});
Channel<PluginInfo> pluginChanged = service()->pluginChanged();
pluginChanged.onReceive(this, [this](const PluginInfo& plugin) {
updatePlugin(plugin);
@ -105,11 +89,16 @@ QVariant PluginsModel::data(const QModelIndex& index, int role) const
case rDescription:
return plugin.description;
case rThumbnailUrl:
//! TODO
if (plugin.thumbnailUrl.isEmpty()) {
return "qrc:/qml/MuseScore/Plugins/internal/placeholders/placeholder.jpeg";
}
return plugin.thumbnailUrl;
case rEnabled:
return plugin.enabled;
case rCategory:
return plugin.category;
return plugin.categoryCode;
case rVersion:
return plugin.version.toString();
case rShortcuts:
@ -169,15 +158,19 @@ void PluginsModel::reloadPlugins()
service()->reloadPlugins();
}
QStringList PluginsModel::categories() const
QVariantList PluginsModel::categories() const
{
QSet<QString> result;
QVariantList result;
for (const PluginInfo& plugin: m_plugins) {
result << plugin.category;
for (const auto& category: service()->categories()) {
QVariantMap obj;
obj["code"] = QString::fromStdString(category.first);
obj["title"] = QString::fromStdString(category.second);
result << obj;
}
return result.values();
return result;
}
void PluginsModel::updatePlugin(const PluginInfo& plugin)
@ -187,7 +180,7 @@ void PluginsModel::updatePlugin(const PluginInfo& plugin)
PluginInfo tmp = m_plugins[i];
m_plugins[i] = plugin;
m_plugins[i].thumbnailUrl = tmp.thumbnailUrl;
m_plugins[i].category = tmp.category;
m_plugins[i].categoryCode = tmp.categoryCode;
QModelIndex index = createIndex(i, 0);
emit dataChanged(index, index);
return;

View file

@ -22,22 +22,24 @@
#ifndef MU_PLUGINS_PLUGINSMODEL_H
#define MU_PLUGINS_PLUGINSMODEL_H
#include "ipluginsservice.h"
#include "iinteractive.h"
#include "modularity/ioc.h"
#include "async/asyncable.h"
#include <QAbstractListModel>
#include <QList>
#include "async/asyncable.h"
#include "modularity/ioc.h"
#include "iinteractive.h"
#include "ipluginsservice.h"
#include "ipluginsconfiguration.h"
namespace mu::plugins {
class PluginsModel : public QAbstractListModel, public async::Asyncable
{
Q_OBJECT
INJECT(plugins, IPluginsService, service)
INJECT(plugins, framework::IInteractive, interactive)
INJECT(plugins, IPluginsService, service)
INJECT(plugins, IPluginsConfiguration, configuration)
public:
explicit PluginsModel(QObject* parent = nullptr);
@ -51,7 +53,7 @@ public:
Q_INVOKABLE void editShortcut(QString codeKey);
Q_INVOKABLE void reloadPlugins();
Q_INVOKABLE QStringList categories() const;
Q_INVOKABLE QVariantList categories() const;
signals:
void finished();

View file

@ -99,6 +99,24 @@ QVersionNumber PluginView::version() const
return QVersionNumber::fromString(m_qmlPlugin->version());
}
QString PluginView::thumbnailName() const
{
IF_ASSERT_FAILED(m_qmlPlugin) {
return QString();
}
return m_qmlPlugin->thumbnailName();
}
QString PluginView::categoryCode() const
{
IF_ASSERT_FAILED(m_qmlPlugin) {
return QString();
}
return m_qmlPlugin->categoryCode();
}
void PluginView::run()
{
IF_ASSERT_FAILED(m_qmlPlugin && m_component) {

View file

@ -55,6 +55,8 @@ public:
QString name() const;
QString description() const;
QVersionNumber version() const;
QString thumbnailName() const;
QString categoryCode() const;
void run();