[vlc-devel] [PATCH 05/29] qt: qml: refactor player control handling

Fatih Uzunoglu fuzun54 at outlook.com
Thu Apr 1 23:15:47 UTC 2021


* PlayerControlbarModel class is repurposed
and renamed to "player_controlbar_model.cpp/hpp".
PlayerControlbarModel is now a supermodel that
instantiates and handles three ControlListModel
for its 'left', 'center', and 'right' properties.
ControlListModel is the stripped down version of
the old PlayerControlbarModel.

* ControlbarProfileModel is created during
MainInterface initialization. ControlbarProfileModel
creates and handles toolbar/controlbar profiles
that act as a wrapper of PlayerControlbarModel
instances. Saving and loading from settings
happens in ControlbarProfileModel.

* Default control layout is now defined in
ControlbarProfile class. ControlbarProfile
injects the default configuration
during construction.

* Default profiles are now defined in
ControlbarProfileModel class.
ControlbarProfileModel class automatically
matches defaults based on player identifiers.
---
 modules/gui/qt/Makefile.am                    |  12 +-
 .../qt/dialogs/toolbar/controlbar_profile.cpp | 232 ++++++
 .../qt/dialogs/toolbar/controlbar_profile.hpp |  91 +++
 .../toolbar/controlbar_profile_model.cpp      | 692 ++++++++++++++++++
 .../toolbar/controlbar_profile_model.hpp      | 110 +++
 .../gui/qt/maininterface/main_interface.cpp   |   5 +
 .../gui/qt/maininterface/main_interface.hpp   |   6 +
 modules/gui/qt/maininterface/mainui.cpp       |  12 +-
 modules/gui/qt/player/control_list_model.cpp  | 155 ++++
 ...rolbarmodel.hpp => control_list_model.hpp} |  63 +-
 .../gui/qt/player/player_controlbar_model.cpp |  91 +++
 .../gui/qt/player/player_controlbar_model.hpp |  68 ++
 .../gui/qt/player/playercontrolbarmodel.cpp   | 326 ---------
 modules/gui/qt/player/qml/ControlBar.qml      |  23 +-
 modules/gui/qt/player/qml/ControlButtons.qml  | 128 ++--
 modules/gui/qt/player/qml/MiniPlayer.qml      |   2 +-
 modules/gui/qt/player/qml/Player.qml          |   2 +
 .../gui/qt/player/qml/PlayerButtonsLayout.qml |  86 ++-
 modules/gui/qt/player/qml/TopBar.qml          |   2 +-
 19 files changed, 1604 insertions(+), 502 deletions(-)
 create mode 100644 modules/gui/qt/dialogs/toolbar/controlbar_profile.cpp
 create mode 100644 modules/gui/qt/dialogs/toolbar/controlbar_profile.hpp
 create mode 100644 modules/gui/qt/dialogs/toolbar/controlbar_profile_model.cpp
 create mode 100644 modules/gui/qt/dialogs/toolbar/controlbar_profile_model.hpp
 create mode 100644 modules/gui/qt/player/control_list_model.cpp
 rename modules/gui/qt/player/{playercontrolbarmodel.hpp => control_list_model.hpp} (63%)
 create mode 100644 modules/gui/qt/player/player_controlbar_model.cpp
 create mode 100644 modules/gui/qt/player/player_controlbar_model.hpp
 delete mode 100644 modules/gui/qt/player/playercontrolbarmodel.cpp

diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am
index add710cd4b..5412589ca8 100644
--- a/modules/gui/qt/Makefile.am
+++ b/modules/gui/qt/Makefile.am
@@ -112,6 +112,10 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/dialogs/sout/sout.cpp gui/qt/dialogs/sout/sout.hpp \
 	gui/qt/dialogs/sout/sout_widgets.cpp \
 	gui/qt/dialogs/sout/sout_widgets.hpp \
+	gui/qt/dialogs/toolbar/controlbar_profile.hpp \
+	gui/qt/dialogs/toolbar/controlbar_profile.cpp \
+	gui/qt/dialogs/toolbar/controlbar_profile_model.cpp \
+	gui/qt/dialogs/toolbar/controlbar_profile_model.hpp \
 	gui/qt/dialogs/vlm/vlm.cpp gui/qt/dialogs/vlm/vlm.hpp \
 	gui/qt/dialogs/playlists/playlists.cpp gui/qt/dialogs/playlists/playlists.hpp \
 	gui/qt/maininterface/compositor.hpp \
@@ -193,7 +197,8 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/network/servicesdiscoverymodel.hpp \
 	gui/qt/player/input_models.cpp gui/qt/player/input_models.hpp \
 	gui/qt/player/player_controller.cpp gui/qt/player/player_controller.hpp gui/qt/player/player_controller_p.hpp \
-	gui/qt/player/playercontrolbarmodel.cpp gui/qt/player/playercontrolbarmodel.hpp \
+	gui/qt/player/player_controlbar_model.cpp gui/qt/player/player_controlbar_model.hpp \
+	gui/qt/player/control_list_model.cpp gui/qt/player/control_list_model.hpp \
 	gui/qt/playlist/media.hpp \
 	gui/qt/playlist/playlist_common.cpp \
 	gui/qt/playlist/playlist_common.hpp \
@@ -315,6 +320,8 @@ nodist_libqt_plugin_la_SOURCES = \
 	gui/qt/dialogs/sout/profile_selector.moc.cpp \
 	gui/qt/dialogs/sout/sout.moc.cpp \
 	gui/qt/dialogs/sout/sout_widgets.moc.cpp \
+	gui/qt/dialogs/toolbar/controlbar_profile.moc.cpp \
+	gui/qt/dialogs/toolbar/controlbar_profile_model.moc.cpp \
 	gui/qt/dialogs/playlists/playlists.moc.cpp \
 	gui/qt/maininterface/compositor_dummy.moc.cpp \
 	gui/qt/maininterface/interface_window_handler.moc.cpp \
@@ -351,7 +358,8 @@ nodist_libqt_plugin_la_SOURCES = \
 	gui/qt/network/servicesdiscoverymodel.moc.cpp \
 	gui/qt/player/input_models.moc.cpp \
 	gui/qt/player/player_controller.moc.cpp \
-	gui/qt/player/playercontrolbarmodel.moc.cpp \
+	gui/qt/player/player_controlbar_model.moc.cpp \
+	gui/qt/player/control_list_model.moc.cpp \
 	gui/qt/playlist/playlist_common.moc.cpp \
 	gui/qt/playlist/playlist_controller.moc.cpp \
 	gui/qt/playlist/playlist_item.moc.cpp \
diff --git a/modules/gui/qt/dialogs/toolbar/controlbar_profile.cpp b/modules/gui/qt/dialogs/toolbar/controlbar_profile.cpp
new file mode 100644
index 0000000000..4c3e82fb38
--- /dev/null
+++ b/modules/gui/qt/dialogs/toolbar/controlbar_profile.cpp
@@ -0,0 +1,232 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#include "controlbar_profile.hpp"
+
+#include "player/control_list_model.hpp"
+#include "player/player_controlbar_model.hpp"
+
+
+decltype(ControlbarProfile::m_defaults)
+    ControlbarProfile::m_defaults =
+        {
+            {
+                {
+                    "MainPlayer"
+                },
+                {
+                    {
+                        {
+                            ControlListModel::LANG_BUTTON,
+                            ControlListModel::MENU_BUTTON
+                        },
+                        {
+                            ControlListModel::RANDOM_BUTTON,
+                            ControlListModel::PREVIOUS_BUTTON,
+                            ControlListModel::PLAY_BUTTON,
+                            ControlListModel::NEXT_BUTTON,
+                            ControlListModel::LOOP_BUTTON
+                        },
+                        {
+                            ControlListModel::VOLUME,
+                            ControlListModel::FULLSCREEN_BUTTON
+                        }
+                    }
+                }
+            },
+            {
+                {
+                    "MiniPlayer"
+                },
+                {
+                    {
+                        {
+                            ControlListModel::ARTWORK_INFO
+                        },
+                        {
+                            ControlListModel::RANDOM_BUTTON,
+                            ControlListModel::PREVIOUS_BUTTON,
+                            ControlListModel::PLAY_BUTTON,
+                            ControlListModel::NEXT_BUTTON,
+                            ControlListModel::LOOP_BUTTON
+                        },
+                        {
+                            ControlListModel::VOLUME,
+                            ControlListModel::PLAYER_SWITCH_BUTTON
+                        }
+                    }
+                }
+            }
+        };
+
+
+ControlbarProfile::ControlbarProfile(QObject *parent) : QObject(parent)
+{
+    injectDefaults();
+}
+
+PlayerControlbarModel *ControlbarProfile::newModel(const QString &identifier)
+{
+    if (identifier.isEmpty())
+        return nullptr;
+
+    if (m_models.contains(identifier))
+        return nullptr; // can not allow the same identifier
+
+    const auto model = new PlayerControlbarModel(this);
+
+    connect(model, &PlayerControlbarModel::controlListChanged, this, &ControlbarProfile::generateLinearControlList);
+
+    connect(model, &PlayerControlbarModel::dirtyChanged, this, [this](bool dirty) {
+        if (dirty)
+            ++m_dirty;
+        else
+            --m_dirty;
+
+        emit dirtyChanged( this->dirty() );
+    });
+
+    m_models.insert(identifier, model);
+
+    return model;
+}
+
+PlayerControlbarModel *ControlbarProfile::getModel(const QString &identifier) const
+{
+    if (m_models.contains(identifier))
+    {
+        return m_models[identifier];
+    }
+    else
+    {
+        return nullptr;
+    }
+}
+
+void ControlbarProfile::setModelData(const QString &identifier, const std::array<QVector<int>, 3> &data)
+{
+    auto ptrModel = getModel(identifier);
+
+    if (ptrModel)
+    {
+        ptrModel->loadModels(data);
+    }
+    else
+    {
+        ptrModel = newModel(identifier);
+
+        if (!ptrModel)
+            return;
+
+        ptrModel->loadModels(data);
+    }
+
+    ptrModel->setDirty(true);
+}
+
+std::array<QVector<int>, 3> ControlbarProfile::getModelData(const QString &identifier) const
+{
+    const auto ptrModel = getModel(identifier);
+
+    if (!ptrModel)
+        return {};
+
+    return ptrModel->serializeModels();
+}
+
+void ControlbarProfile::deleteModel(const QString &identifier)
+{
+    if (m_models.contains(identifier))
+    {
+        m_models[identifier]->deleteLater();
+        m_models.remove(identifier);
+    }
+}
+
+void ControlbarProfile::setName(const QString &name)
+{
+    if (name == m_name)
+        return;
+
+    m_name = name;
+
+    emit nameChanged(m_name);
+}
+
+bool ControlbarProfile::dirty() const
+{
+    return (m_dirty > 0);
+}
+
+QString ControlbarProfile::name() const
+{
+    return m_name;
+}
+
+void ControlbarProfile::injectDefaults(bool resetDirty)
+{
+    injectModel(m_defaults);
+
+    if (resetDirty)
+        this->resetDirty(); // defaults normally should not make the profile dirty
+}
+
+void ControlbarProfile::injectModel(const QVector<ControlbarProfile::Configuration> &modelData)
+{
+    m_pauseControlListGeneration = true;
+
+    for (const auto& i : modelData)
+    {
+        setModelData(i.identifier, i.data);
+    }
+
+    m_pauseControlListGeneration = false;
+
+    generateLinearControlList();
+}
+
+void ControlbarProfile::generateLinearControlList()
+{
+    if (m_pauseControlListGeneration)
+        return;
+
+    // Don't bother if there is no receiver (connection):
+    if (receivers(SIGNAL(controlListChanged (const QVector<int>&) )) <= 0)
+        return;
+
+    QVector<int> linearControls;
+
+    for (const auto& i : m_models)
+    {
+        linearControls.append(i->serializeModels()[0] + i->serializeModels()[1] + i->serializeModels()[2]);
+    }
+
+    emit controlListChanged(linearControls);
+}
+
+void ControlbarProfile::resetDirty()
+{
+    if (dirty() == false)
+        return;
+
+    for (auto it = m_models.constBegin(); it != m_models.constEnd(); ++it)
+    {
+        it.value()->setDirty(false);
+    }
+
+    emit dirtyChanged(dirty());
+}
diff --git a/modules/gui/qt/dialogs/toolbar/controlbar_profile.hpp b/modules/gui/qt/dialogs/toolbar/controlbar_profile.hpp
new file mode 100644
index 0000000000..e9c486dd01
--- /dev/null
+++ b/modules/gui/qt/dialogs/toolbar/controlbar_profile.hpp
@@ -0,0 +1,91 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef ControlbarProfile_HPP
+#define ControlbarProfile_HPP
+
+#include <QObject>
+#include <QMap>
+#include <QVector>
+#include <array>
+
+class PlayerControlbarModel;
+
+class ControlbarProfile : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(bool dirty READ dirty RESET resetDirty NOTIFY dirtyChanged)
+    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+
+    friend class ControlbarProfileModel;
+
+public:
+    explicit ControlbarProfile(QObject *parent = nullptr);
+
+    PlayerControlbarModel* newModel(const QString& identifier);
+    Q_INVOKABLE PlayerControlbarModel* getModel(const QString& identifier) const;
+
+    void setModelData(const QString& identifier, const std::array<QVector<int>, 3>& data);
+    std::array<QVector<int>, 3> getModelData(const QString& identifier) const;
+
+    void deleteModel(const QString& identifier);
+
+    Q_INVOKABLE void injectDefaults(bool resetDirty = true);
+
+    bool dirty() const;
+    QString name() const;
+
+public slots:
+    void resetDirty();
+    void setName(const QString& name);
+
+private:
+    // m_dirty indicates the count of PlayerControlbarModel
+    // residing in m_models which has the dirty property
+    // set true.
+    int m_dirty = 0;
+
+    QString m_name {"N/A"};
+    bool m_pauseControlListGeneration = false;
+
+    // According to benchmarks, QMap performs better than
+    // QHash when item count is less than 32.
+    // Assuming model (player) count to stay below that,
+    // QMap is used here.
+    QMap<QString, PlayerControlbarModel *> m_models;
+
+    struct Configuration {
+        QString identifier;
+        std::array<QVector<int>, 3> data;
+    };
+    static const QVector<Configuration> m_defaults;
+
+private:
+    void injectModel(const QVector<Configuration>& modelData);
+
+private slots:
+    void generateLinearControlList();
+
+signals:
+    void dirtyChanged(bool dirty);
+    void nameChanged(QString name);
+
+    void controlListChanged(const QVector<int>& linearControlList);
+};
+
+#endif // ControlbarProfile_HPP
diff --git a/modules/gui/qt/dialogs/toolbar/controlbar_profile_model.cpp b/modules/gui/qt/dialogs/toolbar/controlbar_profile_model.cpp
new file mode 100644
index 0000000000..a052048bb0
--- /dev/null
+++ b/modules/gui/qt/dialogs/toolbar/controlbar_profile_model.cpp
@@ -0,0 +1,692 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#include "controlbar_profile_model.hpp"
+
+#include <QSettings>
+
+#include "qt.hpp"
+#include "controlbar_profile.hpp"
+#include "player/control_list_model.hpp"
+
+#define SETTINGS_KEY_SELECTEDPROFILE "SelectedProfile"
+#define SETTINGS_ARRAYNAME_PROFILES "Profiles"
+#define SETTINGS_KEY_NAME "Name"
+#define SETTINGS_KEY_MODEL "Model"
+
+#define SETTINGS_CONTROL_SEPARATOR ","
+#define SETTINGS_CONFIGURATION_SEPARATOR "|"
+#define SETTINGS_PROFILE_SEPARATOR "$"
+
+decltype (ControlbarProfileModel::m_defaults)
+    ControlbarProfileModel::m_defaults =
+        {
+            {
+                {
+                    "Minimalist Style"
+                },
+                {
+                    {
+                        {
+                            "MainPlayer"
+                        },
+                        {
+                            {
+                                {
+                                    ControlListModel::PLAY_BUTTON,
+                                    ControlListModel::WIDGET_SPACER,
+                                    ControlListModel::PREVIOUS_BUTTON,
+                                    ControlListModel::STOP_BUTTON,
+                                    ControlListModel::NEXT_BUTTON,
+                                    ControlListModel::WIDGET_SPACER,
+                                    ControlListModel::RECORD_BUTTON,
+                                    ControlListModel::WIDGET_SPACER,
+                                    ControlListModel::TELETEXT_BUTTONS,
+                                    ControlListModel::WIDGET_SPACER,
+                                    ControlListModel::PLAYLIST_BUTTON,
+                                    ControlListModel::WIDGET_SPACER,
+                                    ControlListModel::VOLUME
+                                },
+                                {
+
+                                },
+                                {
+
+                                }
+                            }
+                        }
+                    },
+                    {
+                        {
+                            "MiniPlayer"
+                        },
+                        {
+                            {
+                                {
+                                    ControlListModel::PREVIOUS_BUTTON,
+                                    ControlListModel::PLAY_BUTTON,
+                                    ControlListModel::STOP_BUTTON,
+                                    ControlListModel::NEXT_BUTTON
+                                },
+                                {
+
+                                },
+                                {
+
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            {
+                {
+                    "One-liner Style"
+                },
+                {
+                    {
+                        {
+                            "MainPlayer"
+                        },
+                        {
+                            {
+                                {
+                                    ControlListModel::PLAY_BUTTON,
+                                    ControlListModel::WIDGET_SPACER,
+                                    ControlListModel::PREVIOUS_BUTTON,
+                                    ControlListModel::STOP_BUTTON,
+                                    ControlListModel::NEXT_BUTTON,
+                                    ControlListModel::WIDGET_SPACER,
+                                    ControlListModel::FULLSCREEN_BUTTON,
+                                    ControlListModel::PLAYLIST_BUTTON,
+                                    ControlListModel::EXTENDED_BUTTON,
+                                    ControlListModel::WIDGET_SPACER,
+                                    ControlListModel::WIDGET_SPACER,
+                                    ControlListModel::RECORD_BUTTON,
+                                    ControlListModel::SNAPSHOT_BUTTON,
+                                    ControlListModel::ATOB_BUTTON,
+                                    ControlListModel::FRAME_BUTTON
+                                },
+                                {
+                                    ControlListModel::VOLUME
+                                },
+                                {
+
+                                }
+                            }
+                        }
+                    },
+                    {
+                        {
+                            "MiniPlayer"
+                        },
+                        {
+                            {
+                                {
+                                    ControlListModel::RANDOM_BUTTON,
+                                    ControlListModel::PREVIOUS_BUTTON,
+                                    ControlListModel::PLAY_BUTTON,
+                                    ControlListModel::STOP_BUTTON,
+                                    ControlListModel::NEXT_BUTTON,
+                                    ControlListModel::LOOP_BUTTON
+                                },
+                                {
+
+                                },
+                                {
+
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            {
+                {
+                    "Simplest Style"
+                },
+                {
+                    {
+                        {
+                            "MainPlayer"
+                        },
+                        {
+                            {
+                                {
+                                    ControlListModel::VOLUME
+                                },
+                                {
+                                    ControlListModel::PLAY_BUTTON,
+                                    ControlListModel::NEXT_BUTTON,
+                                    ControlListModel::STOP_BUTTON
+                                },
+                                {
+                                    ControlListModel::FULLSCREEN_BUTTON
+                                }
+                            }
+                        }
+                    },
+                    {
+                        {
+                            "MiniPlayer"
+                        },
+                        {
+                            {
+                                {
+                                    ControlListModel::PREVIOUS_BUTTON,
+                                    ControlListModel::PLAY_BUTTON,
+                                    ControlListModel::NEXT_BUTTON
+                                },
+                                {
+
+                                },
+                                {
+
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        };
+
+
+ControlbarProfileModel::ControlbarProfileModel(intf_thread_t *p_intf, QObject *parent)
+    : QAbstractListModel(parent),
+    m_intf(p_intf)
+{
+    assert(m_intf);
+
+    connect(this, &QAbstractListModel::rowsInserted, this, &ControlbarProfileModel::countChanged);
+    connect(this, &QAbstractListModel::rowsRemoved, this, &ControlbarProfileModel::countChanged);
+    connect(this, &QAbstractListModel::modelReset, this, &ControlbarProfileModel::countChanged);
+
+    // To make the QML player controlbars update when model is Reset
+    connect(this, &QAbstractListModel::modelReset, this, &ControlbarProfileModel::selectedProfileChanged);
+
+    // When all profiles are removed, insert defaults:
+    // Maybe add a dedicate button for this purpose and don't allow removing all profiles ?
+    connect(this, &ControlbarProfileModel::countChanged, this, [this] () {
+        if (rowCount() == 0)
+            insertDefaults();
+    });
+
+    if (reload() == false)
+    {
+        // If initial reload fails, load the default profiles:
+        insertDefaults();
+    }
+}
+
+void ControlbarProfileModel::insertDefaults()
+{
+    // First, add a blank new profile:
+    // ControlbarProfile will inject the default configurations during its construction.
+    newProfile(tr("Default Profile"));
+
+    // Add default profiles:
+    for (const auto& i : m_defaults)
+    {
+        const auto ptrNewProfile = newProfile(i.name);
+        if (!ptrNewProfile)
+            continue;
+
+        ptrNewProfile->injectModel(i.modelData);
+        ptrNewProfile->resetDirty(); // default profiles should not be dirty initially
+    }
+
+    setSelectedProfile(0);
+}
+
+QString ControlbarProfileModel::generateUniqueName(const QString &name)
+{
+    const auto sameNameCount = std::count_if(m_profiles.begin(),
+                                             m_profiles.end(),
+                                             [name](const ControlbarProfile* i) {
+                                                 return i->name() == name;
+                                             });
+
+    if (sameNameCount > 0)
+        return QString("%1 (%2)").arg(name).arg(sameNameCount + 1);
+    else
+        return name;
+}
+
+int ControlbarProfileModel::rowCount(const QModelIndex &parent) const
+{
+    if (parent.isValid())
+        return 0;
+
+    return m_profiles.size();
+}
+
+QVariant ControlbarProfileModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid())
+        return QVariant();
+
+    const auto ptrProfile = m_profiles.at(index.row());
+
+    if (!ptrProfile)
+        return QVariant();
+
+    switch (role)
+    {
+    case Qt::DisplayRole:
+        return ptrProfile->name();
+    case MODEL_ROLE:
+        return QVariant::fromValue(ptrProfile);
+    }
+
+    return QVariant();
+}
+
+QHash<int, QByteArray> ControlbarProfileModel::roleNames() const
+{
+    return {
+        {
+            Qt::DisplayRole, "name"
+        },
+        {
+            MODEL_ROLE, "model"
+        }
+    };
+}
+
+bool ControlbarProfileModel::insertRows(int row, int count, const QModelIndex &parent)
+{
+    if (row < 0 || row > m_profiles.size())
+        return false;
+
+    beginInsertRows(parent, row, row + count - 1);
+
+    for (int i = 0; i < count; ++i)
+    {
+        const auto profile = new ControlbarProfile(this);
+        profile->setName(tr("Profile %1").arg(m_profiles.size()));
+
+        m_profiles.insert(row, profile);
+    }
+
+    endInsertRows();
+
+    return true;
+}
+
+bool ControlbarProfileModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+    if (row < 0 || count < 1 || row + count > m_profiles.size())
+        return false;
+
+    beginRemoveRows(parent, row, row + count - 1);
+
+    auto from = m_profiles.begin() + row;
+    auto to = from + count - 1;
+    std::for_each(from, to, [](auto* item) {
+        assert(item);
+        item->deleteLater();
+    });
+    m_profiles.erase(from, to);
+
+    endRemoveRows();
+
+    return true;
+}
+
+bool ControlbarProfileModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    if (data(index, role) != value)
+    {
+        auto ptrProfile = m_profiles.at(index.row());
+
+        if (!ptrProfile)
+            return false;
+
+        switch (role)
+        {
+        case Qt::DisplayRole:
+            if (value.canConvert(QVariant::String))
+                ptrProfile->setName(value.toString());
+            else
+                return false;
+            break;
+        case MODEL_ROLE:
+            if (value.canConvert<ControlbarProfile*>())
+                ptrProfile = qvariant_cast<ControlbarProfile*>(value);
+            else
+                return false;
+            break;
+        default:
+            return false;
+        }
+
+        m_profiles.replace(index.row(), ptrProfile);
+
+        emit dataChanged(index, index, { role });
+        return true;
+    }
+    return false;
+}
+
+Qt::ItemFlags ControlbarProfileModel::flags(const QModelIndex &index) const
+{
+    if (!index.isValid())
+        return Qt::NoItemFlags;
+
+    return (Qt::ItemIsEditable | Qt::ItemNeverHasChildren);
+}
+
+int ControlbarProfileModel::selectedProfile() const
+{
+    return m_selectedProfile;
+}
+
+ControlbarProfile* ControlbarProfileModel::currentModel() const
+{
+    return getProfile(selectedProfile());
+}
+
+void ControlbarProfileModel::save(bool clearDirty) const
+{
+    assert(m_intf->p_sys);
+    assert(m_intf->p_sys->mainSettings);
+
+    if (!m_intf || !m_intf->p_sys || !m_intf->p_sys->mainSettings)
+        return;
+
+    const auto settings = m_intf->p_sys->mainSettings;
+    const auto groupName = metaObject()->className();
+
+    settings->beginGroup(groupName);
+    settings->remove(""); // clear the group before save
+
+    settings->setValue(SETTINGS_KEY_SELECTEDPROFILE, selectedProfile());
+
+    settings->beginWriteArray(SETTINGS_ARRAYNAME_PROFILES);
+
+    for (int i = 0; i < m_profiles.size(); ++i)
+    {
+        settings->setArrayIndex(i);
+
+        const auto& ptrModelMap = m_profiles.at(i)->m_models;
+
+        QString val;
+        for (auto it = ptrModelMap.constBegin(); it != ptrModelMap.end(); ++it)
+        {
+            const QString identifier = it.key();
+
+            const auto serializedModels = m_profiles.at(i)->getModelData(identifier);
+
+            static const auto join = [](const QVector<int>& list) {
+                QString ret;
+                for (auto i : list)
+                {
+                    ret += QString::number(i) + SETTINGS_CONTROL_SEPARATOR;
+                }
+                if (!ret.isEmpty())
+                    ret.chop(1);
+                return ret;
+            };
+
+            val += QString(SETTINGS_PROFILE_SEPARATOR
+                           "%1"
+                           SETTINGS_CONFIGURATION_SEPARATOR
+                           "%2"
+                           SETTINGS_CONFIGURATION_SEPARATOR
+                           "%3"
+                           SETTINGS_CONFIGURATION_SEPARATOR
+                           "%4").arg(identifier,
+                                     join(serializedModels[0]),
+                                     join(serializedModels[1]),
+                                     join(serializedModels[2]));
+        }
+
+        if (clearDirty)
+            m_profiles.at(i)->resetDirty();
+
+        settings->setValue(SETTINGS_KEY_NAME, m_profiles.at(i)->name());
+        settings->setValue(SETTINGS_KEY_MODEL, val);
+    }
+
+    settings->endArray();
+    settings->endGroup();
+}
+
+bool ControlbarProfileModel::reload()
+{
+    assert(m_intf->p_sys);
+    assert(m_intf->p_sys->mainSettings);
+
+    if (!m_intf || !m_intf->p_sys || !m_intf->p_sys->mainSettings)
+        return false;
+
+    const auto settings = m_intf->p_sys->mainSettings;
+    const auto groupName = metaObject()->className();
+
+    settings->beginGroup(groupName);
+
+    const int size = settings->beginReadArray(SETTINGS_ARRAYNAME_PROFILES);
+
+    if (size <= 0)
+    {
+        settings->endArray();
+        settings->endGroup();
+
+        return false;
+    }
+
+    beginResetModel();
+
+    decltype (m_profiles) profiles;
+    for (int i = 0; i < size; ++i)
+    {
+        settings->setArrayIndex(i);
+
+        const QString modelValue = settings->value(SETTINGS_KEY_MODEL).toString();
+        if (modelValue.isEmpty())
+            continue;
+
+        const auto val = modelValue.splitRef(SETTINGS_PROFILE_SEPARATOR);
+        if (val.isEmpty())
+            continue;
+
+        const auto ptrNewProfile = new ControlbarProfile(this);
+        ptrNewProfile->setName(settings->value(SETTINGS_KEY_NAME).toString());
+
+        for (auto j : val)
+        {
+            if (j.isEmpty())
+                continue;
+
+            const auto alignments = j.split(SETTINGS_CONFIGURATION_SEPARATOR);
+
+            if (alignments.length() != 4)
+                continue;
+
+            if (alignments[0].toString().isEmpty())
+                continue;
+
+            static const auto split = [](auto ref) {
+                QVector<int> list;
+
+                if (ref.isEmpty())
+                    return list;
+
+                for (auto i : ref.split(SETTINGS_CONTROL_SEPARATOR))
+                {
+                    bool ok = false;
+                    int k = i.toInt(&ok);
+
+                    if (ok)
+                        list.append(k);
+                }
+                return list;
+            };
+
+            const std::array<QVector<int>, 3> data { split(alignments[1]),
+                                                     split(alignments[2]),
+                                                     split(alignments[3]) };
+
+            ptrNewProfile->setModelData(alignments[0].toString(), data);
+            ptrNewProfile->resetDirty(); // Newly loaded model can not be dirty
+        }
+
+        profiles.append(ptrNewProfile);
+    }
+
+    settings->endArray();
+
+    m_selectedProfile = -1;
+    std::for_each(m_profiles.begin(), m_profiles.end(), [](auto i) { delete i; });
+
+    m_profiles = std::move(profiles);
+
+    endResetModel();
+
+    bool ok = false;
+    int index = settings->value(SETTINGS_KEY_SELECTEDPROFILE).toInt(&ok);
+
+    if (ok)
+        setSelectedProfile(index);
+    else
+        setSelectedProfile(0);
+
+    settings->endGroup();
+
+    return true;
+}
+
+bool ControlbarProfileModel::setSelectedProfile(int selectedProfile)
+{
+    if (m_selectedProfile == selectedProfile)
+        return false;
+
+    const auto ptrProfileNew = getProfile(selectedProfile);
+    const auto ptrProfileOld = getProfile(m_selectedProfile);
+
+    assert(ptrProfileNew);
+
+    if (!ptrProfileNew)
+        return false;
+
+    connect(ptrProfileNew, &ControlbarProfile::controlListChanged, this, &ControlbarProfileModel::selectedProfileControlListChanged);
+    connect(this, &QAbstractListModel::modelReset, ptrProfileNew, &ControlbarProfile::generateLinearControlList);
+    connect(this, &ControlbarProfileModel::selectedProfileChanged, ptrProfileNew, &ControlbarProfile::generateLinearControlList);
+
+    if (ptrProfileOld && (ptrProfileNew != ptrProfileOld))
+    {
+        disconnect(ptrProfileOld, &ControlbarProfile::controlListChanged, this, &ControlbarProfileModel::selectedProfileControlListChanged);
+        disconnect(this, &QAbstractListModel::modelReset, ptrProfileOld, &ControlbarProfile::generateLinearControlList);
+        disconnect(this, &ControlbarProfileModel::selectedProfileChanged, ptrProfileOld, &ControlbarProfile::generateLinearControlList);
+    }
+
+    m_selectedProfile = selectedProfile;
+
+    emit selectedProfileChanged();
+
+    return true;
+}
+
+ControlbarProfile *ControlbarProfileModel::getProfile(int index) const
+{
+    if (index < 0 || index >= m_profiles.size())
+        return nullptr;
+
+    return m_profiles.at(index);
+}
+
+ControlbarProfile *ControlbarProfileModel::newProfile(const QString &name)
+{
+    if (name.isEmpty())
+        return nullptr;
+
+    const auto ptrProfile = newProfile();
+
+    ptrProfile->setName(generateUniqueName(name));
+
+    return ptrProfile;
+}
+
+ControlbarProfile *ControlbarProfileModel::newProfile()
+{
+    const auto ptrNewProfile = new ControlbarProfile(this);
+
+    beginInsertRows(QModelIndex(), m_profiles.size(), m_profiles.size());
+
+    m_profiles.append(ptrNewProfile);
+
+    endInsertRows();
+
+    return ptrNewProfile;
+}
+
+ControlbarProfile *ControlbarProfileModel::cloneProfile(const ControlbarProfile *profile)
+{
+    const auto ptrNewProfile = newProfile(profile->name());
+
+    if (!ptrNewProfile)
+        return nullptr;
+
+    for (auto it = profile->m_models.constBegin(); it != profile->m_models.constEnd(); ++it)
+    {
+        ptrNewProfile->setModelData(it.key(), profile->getModelData(it.key()));
+        ptrNewProfile->resetDirty();
+    }
+
+    return ptrNewProfile;
+}
+
+void ControlbarProfileModel::cloneSelectedProfile(const QString &newProfileName)
+{
+    const auto ptrModel = currentModel();
+
+    assert(ptrModel);
+    if (!ptrModel)
+        return;
+
+    const auto ptrNewModel = cloneProfile(ptrModel);
+
+    assert(ptrNewModel);
+    if (!ptrNewModel)
+        return;
+
+    ptrNewModel->setName(generateUniqueName(newProfileName));
+}
+
+void ControlbarProfileModel::deleteSelectedProfile()
+{
+    const auto ptrSelectedProfile = getProfile(m_selectedProfile);
+
+    if (!ptrSelectedProfile)
+        return;
+
+    const auto _selectedProfile = m_selectedProfile;
+
+    beginRemoveRows(QModelIndex(), _selectedProfile, _selectedProfile);
+
+    m_selectedProfile = -1;
+
+    delete ptrSelectedProfile;
+    m_profiles.removeAt(_selectedProfile);
+
+    endRemoveRows();
+
+    if (getProfile(_selectedProfile - 1))
+        setSelectedProfile(_selectedProfile - 1);
+    else
+        setSelectedProfile(_selectedProfile);
+}
diff --git a/modules/gui/qt/dialogs/toolbar/controlbar_profile_model.hpp b/modules/gui/qt/dialogs/toolbar/controlbar_profile_model.hpp
new file mode 100644
index 0000000000..108229bbe7
--- /dev/null
+++ b/modules/gui/qt/dialogs/toolbar/controlbar_profile_model.hpp
@@ -0,0 +1,110 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef CONTROLBARPROFILEMODEL_H
+#define CONTROLBARPROFILEMODEL_H
+
+#include <QAbstractListModel>
+#include <array>
+
+#include "controlbar_profile.hpp"
+
+struct intf_thread_t;
+
+class ControlbarProfileModel : public QAbstractListModel
+{
+    Q_OBJECT
+
+    Q_PROPERTY(int selectedProfile READ selectedProfile WRITE setSelectedProfile NOTIFY selectedProfileChanged)
+    Q_PROPERTY(ControlbarProfile* currentModel READ currentModel NOTIFY selectedProfileChanged)
+
+    Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
+
+public:
+    explicit ControlbarProfileModel(intf_thread_t *p_intf, QObject *parent = nullptr);
+
+    // Basic functionality:
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+    QHash<int, QByteArray> roleNames() const override;
+
+    // Editable:
+    Q_INVOKABLE bool setData(const QModelIndex &index, const QVariant &value,
+                 int role = Qt::DisplayRole) override;
+
+    Qt::ItemFlags flags(const QModelIndex& index) const override;
+
+    // Add data:
+    Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
+
+    // Remove data:
+    bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
+
+public:
+    enum Roles {
+        MODEL_ROLE = Qt::UserRole,
+    };
+
+    int selectedProfile() const;
+    ControlbarProfile* currentModel() const;
+
+    ControlbarProfile* cloneProfile(const ControlbarProfile* profile);
+    Q_INVOKABLE void cloneSelectedProfile(const QString& newProfileName);
+
+    Q_INVOKABLE ControlbarProfile* getProfile(int index) const;
+
+    Q_INVOKABLE ControlbarProfile* newProfile(const QString& name);
+    ControlbarProfile* newProfile();
+
+    Q_INVOKABLE void deleteSelectedProfile();
+
+public slots:
+    void save(bool clearDirty = true) const;
+    bool reload();
+
+    bool setSelectedProfile(int selectedProfile);
+
+signals:
+    void countChanged();
+    void selectedProfileChanged();
+
+    void selectedProfileControlListChanged(const QVector<int>& linearControlList);
+
+private:
+    QVector<ControlbarProfile *> m_profiles;
+
+    int m_selectedProfile = -1;
+
+    struct Profile {
+        QString name;
+        QVector<ControlbarProfile::Configuration> modelData;
+    };
+
+    static const QVector<Profile> m_defaults;
+
+private:
+    void insertDefaults();
+
+    QString generateUniqueName(const QString& name);
+
+protected:
+    intf_thread_t *m_intf = nullptr;
+};
+
+#endif // CONTROLBARPROFILEMODEL_H
diff --git a/modules/gui/qt/maininterface/main_interface.cpp b/modules/gui/qt/maininterface/main_interface.cpp
index e0bcdcb0a4..206cedd25d 100644
--- a/modules/gui/qt/maininterface/main_interface.cpp
+++ b/modules/gui/qt/maininterface/main_interface.cpp
@@ -50,6 +50,8 @@
 
 #include "vlc_media_library.h"
 
+#include "dialogs/toolbar/controlbar_profile_model.hpp"
+
 #include <QCloseEvent>
 #include <QKeyEvent>
 
@@ -155,6 +157,9 @@ MainInterface::MainInterface(intf_thread_t *_p_intf , QWidget* parent, Qt::Windo
     m_colorScheme = new ColorSchemeModel(this);
     m_colorScheme->setCurrent(currentColorScheme);
 
+    /* Controlbar Profile Model Creation */
+    m_controlbarProfileModel = new ControlbarProfileModel(p_intf, this);
+
     /* Should the UI stays on top of other windows */
     b_interfaceOnTop = var_InheritBool( p_intf, "video-on-top" );
 
diff --git a/modules/gui/qt/maininterface/main_interface.hpp b/modules/gui/qt/maininterface/main_interface.hpp
index d8deb10c47..1af9804d11 100644
--- a/modules/gui/qt/maininterface/main_interface.hpp
+++ b/modules/gui/qt/maininterface/main_interface.hpp
@@ -59,6 +59,7 @@ class QTimer;
 class StandardPLPanel;
 struct vout_window_t;
 class VideoSurfaceProvider;
+class ControlbarProfileModel;
 
 class WindowStateHolder : public QObject
 {
@@ -159,6 +160,8 @@ class MainInterface : public QVLCMW
     Q_PROPERTY(bool hasToolbarMenu READ hasToolbarMenu NOTIFY hasToolbarMenuChanged)
     Q_PROPERTY(bool canShowVideoPIP READ canShowVideoPIP CONSTANT)
     Q_PROPERTY(bool pinVideoControls READ pinVideoControls WRITE setPinVideoControls NOTIFY pinVideoControlsChanged)
+    Q_PROPERTY(ControlbarProfileModel* controlbarProfileModel READ controlbarProfileModel CONSTANT)
+
 
 public:
     /* tors */
@@ -207,6 +210,7 @@ public:
     inline bool canShowVideoPIP() const { return m_canShowVideoPIP; }
     inline void setCanShowVideoPIP(bool canShowVideoPIP) { m_canShowVideoPIP = canShowVideoPIP; }
     inline bool pinVideoControls() const { return m_pinVideoControls; }
+    inline ControlbarProfileModel* controlbarProfileModel() const { return m_controlbarProfileModel; }
 
     bool hasEmbededVideo() const;
     VideoSurfaceProvider* getVideoSurfaceProvider() const;
@@ -290,6 +294,8 @@ protected:
 
     VLCVarChoiceModel* m_extraInterfaces;
 
+    ControlbarProfileModel* m_controlbarProfileModel;
+
 public slots:
     void toggleUpdateSystrayMenu();
     void showUpdateSystrayMenu();
diff --git a/modules/gui/qt/maininterface/mainui.cpp b/modules/gui/qt/maininterface/mainui.cpp
index 678b2f87e0..0c518b2599 100644
--- a/modules/gui/qt/maininterface/mainui.cpp
+++ b/modules/gui/qt/maininterface/mainui.cpp
@@ -18,7 +18,11 @@
 #include "medialibrary/mlplaylist.hpp"
 
 #include "player/player_controller.hpp"
-#include "player/playercontrolbarmodel.hpp"
+#include "player/player_controlbar_model.hpp"
+#include "player/control_list_model.hpp"
+
+#include "dialogs/toolbar/controlbar_profile_model.hpp"
+#include "dialogs/toolbar/controlbar_profile.hpp"
 
 #include "playlist/playlist_model.hpp"
 #include "playlist/playlist_controller.hpp"
@@ -235,7 +239,11 @@ void MainUI::registerQMLTypes()
 
     qmlRegisterType<QmlEventFilter>( "org.videolan.vlc", 0, 1, "EventFilter" );
 
-    qmlRegisterType<PlayerControlBarModel>( "org.videolan.vlc", 0, 1, "PlayerControlBarModel");
+    qRegisterMetaType<ControlbarProfile*>();
+    qRegisterMetaType<ControlbarProfileModel*>();
+    qmlRegisterUncreatableType<ControlbarProfile>("org.videolan.vlc", 0, 1, "ControlbarProfile", "");
+    qmlRegisterUncreatableType<PlayerControlbarModel>("org.videolan.vlc", 0, 1, "PlayerControlbarModel", "");
+    qmlRegisterUncreatableType<ControlListModel>( "org.videolan.vlc", 0, 1, "ControlListModel", "" );
 
     qRegisterMetaType<QmlMainContext*>();
     qmlRegisterType<QmlGlobalMenu>( "org.videolan.vlc", 0, 1, "QmlGlobalMenu" );
diff --git a/modules/gui/qt/player/control_list_model.cpp b/modules/gui/qt/player/control_list_model.cpp
new file mode 100644
index 0000000000..4c9d8e5eb5
--- /dev/null
+++ b/modules/gui/qt/player/control_list_model.cpp
@@ -0,0 +1,155 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#include "control_list_model.hpp"
+
+ControlListModel::ControlListModel(QObject *parent) : QAbstractListModel(parent)
+{
+    connect(this, &QAbstractListModel::rowsInserted, this, &ControlListModel::countChanged);
+    connect(this, &QAbstractListModel::rowsRemoved, this, &ControlListModel::countChanged);
+    connect(this, &QAbstractListModel::modelReset, this, &ControlListModel::countChanged);
+}
+
+int ControlListModel::rowCount(const QModelIndex &parent) const
+{
+    if (parent.isValid())
+        return 0;
+
+    return m_controls.size();
+}
+
+QVariant ControlListModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid())
+        return QVariant();
+
+    const ControlType control = m_controls.at(index.row());
+
+    switch (role) {
+    case ID_ROLE:
+        return QVariant(control);
+    }
+    return QVariant();
+}
+
+bool ControlListModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    ControlType control = m_controls.at(index.row());
+
+    switch (role) {
+    case ID_ROLE:
+        if (value.canConvert(QVariant::Int))
+            control = static_cast<ControlType>(value.toInt());
+        else
+            return false;
+        break;
+    }
+
+    if (setButtonAt(index.row(), control)) {
+        emit dataChanged(index, index, { role });
+        return true;
+    }
+    return false;
+}
+
+Qt::ItemFlags ControlListModel::flags(const QModelIndex &index) const
+{
+    if (!index.isValid())
+        return Qt::NoItemFlags;
+
+    return (Qt::ItemIsEditable | Qt::ItemNeverHasChildren);
+}
+
+QHash<int, QByteArray> ControlListModel::roleNames() const
+{
+    return {
+        {
+            ID_ROLE, "id"
+        }
+    };
+}
+
+QVector<int> ControlListModel::getControls() const
+{
+    QVector<int> list;
+
+    for (auto i : m_controls)
+    {
+        list.append(static_cast<int>(i));
+    }
+
+    return list;
+}
+
+void ControlListModel::setControls(const QVector<int> &list)
+{
+    beginResetModel();
+
+    m_controls.resize(list.size());
+
+    for (int i = 0; i < list.size(); ++i)
+    {
+        m_controls[i] = static_cast<ControlType>(list.at(i));
+    }
+
+    endResetModel();
+}
+
+bool ControlListModel::setButtonAt(int index, const ControlType &button)
+{
+    if(index < 0 || index >= m_controls.size())
+        return false;
+
+    const ControlType oldControl = m_controls.at(index);
+
+    if (button == oldControl)
+        return false;
+
+    m_controls[index] = button;
+    return true;
+}
+
+void ControlListModel::insert(int index, QVariantMap bdata)
+{
+    beginInsertRows(QModelIndex(), index, index);
+    m_controls.insert(index, static_cast<ControlType>(bdata.value("id").toInt()));
+    endInsertRows();
+}
+void ControlListModel::move(int src, int dest)
+{
+    if(src == dest)
+        return;
+
+    beginMoveRows(QModelIndex(), src, src, QModelIndex(), dest + (src < dest ? 1 : 0));
+    m_controls.move(src, dest);
+    endMoveRows();
+}
+
+void ControlListModel::remove(int index)
+{
+    beginRemoveRows(QModelIndex(), index, index);
+    m_controls.remove(index);
+    endRemoveRows();
+}
+
+void ControlListModel::clear()
+{
+    beginResetModel();
+    m_controls.clear();
+    endResetModel();
+}
diff --git a/modules/gui/qt/player/playercontrolbarmodel.hpp b/modules/gui/qt/player/control_list_model.hpp
similarity index 63%
rename from modules/gui/qt/player/playercontrolbarmodel.hpp
rename to modules/gui/qt/player/control_list_model.hpp
index d439bbbdb0..34d2f1aebe 100644
--- a/modules/gui/qt/player/playercontrolbarmodel.hpp
+++ b/modules/gui/qt/player/control_list_model.hpp
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * Copyright (C) 2019 VLC authors and VideoLAN
+ * Copyright (C) 2021 VLC authors and VideoLAN
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -16,32 +16,25 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
-#ifndef CONTROLLERMODEL_H
-#define CONTROLLERMODEL_H
+#ifndef CONTROLLISTMODEL_HPP
+#define CONTROLLISTMODEL_HPP
 
 #include <QAbstractListModel>
 #include <QVector>
 
-#include "util/qml_main_context.hpp"
-
-class PlayerControlBarModel : public QAbstractListModel
+class ControlListModel : public QAbstractListModel
 {
     Q_OBJECT
-    Q_PROPERTY(QmlMainContext* mainCtx READ getMainCtx WRITE setMainCtx NOTIFY ctxChanged)
-    Q_PROPERTY(QString configName READ getConfigName WRITE setConfigName NOTIFY configNameChanged)
     Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
 
-
 public:
-    explicit PlayerControlBarModel(QObject *_parent = nullptr);
-    struct IconToolButton
-    {
-        int id;
-    };
-    enum{
-        ID_ROLE
+    explicit ControlListModel(QObject *parent = nullptr);
+
+    enum Roles {
+        ID_ROLE = Qt::UserRole
     };
-    enum ButtonType_e
+
+    enum ControlType
     {
         PLAY_BUTTON,
         STOP_BUTTON,
@@ -82,7 +75,7 @@ public:
         WIDGET_SPACER_EXTEND,
         WIDGET_MAX
     };
-    Q_ENUM(ButtonType_e)
+    Q_ENUM(ControlType)
 
     // Basic functionality:
     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -97,43 +90,21 @@ public:
 
     virtual QHash<int, QByteArray> roleNames() const override;
 
-    inline QmlMainContext* getMainCtx() const { return m_mainCtx; }
-    void setMainCtx(QmlMainContext*);
-
-    inline QString getConfigName() { return configName; }
-    void setConfigName(QString name);
-
-    static QString getSerializedDefaultStyle();
+    QVector<int> getControls() const;
+    void setControls(const QVector<int>& list);
 
 signals:
-    void ctxChanged(QmlMainContext*);
-    void configNameChanged(QString);
     void countChanged();
 
-protected:
-    intf_thread_t       *p_intf  = nullptr;
-
 private:
-    QVector<IconToolButton> mButtons;
-    QString configName;
-
-    void parseAndAdd(const QString& config);
-    void parseDefault(const QVector<IconToolButton>& config);
-
-    bool setButtonAt(int index, const IconToolButton &button);
-    void addProfiles();
-    void loadConfig();
-
-    QmlMainContext* m_mainCtx = nullptr;
+    QVector<ControlType> m_controls;
+    bool setButtonAt(int index, const ControlType &button);
 
 public slots:
     Q_INVOKABLE void insert(int index, QVariantMap bdata);
     Q_INVOKABLE void move(int src,int dest);
     Q_INVOKABLE void remove(int index);
-    Q_INVOKABLE void reloadConfig(QString config);
-    Q_INVOKABLE void saveConfig();
-    Q_INVOKABLE QString getConfig();
-    Q_INVOKABLE void reloadModel();
+    Q_INVOKABLE void clear();
 };
 
-#endif // CONTROLLERMODEL_H
+#endif // CONTROLLISTMODEL_HPP
diff --git a/modules/gui/qt/player/player_controlbar_model.cpp b/modules/gui/qt/player/player_controlbar_model.cpp
new file mode 100644
index 0000000000..9496e8f7ba
--- /dev/null
+++ b/modules/gui/qt/player/player_controlbar_model.cpp
@@ -0,0 +1,91 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#include "player_controlbar_model.hpp"
+
+#include "control_list_model.hpp"
+
+PlayerControlbarModel::PlayerControlbarModel(QObject *parent) : QObject(parent)
+{
+    m_left = new ControlListModel(this);
+    m_center = new ControlListModel(this);
+    m_right = new ControlListModel(this);
+
+    connect(m_left, &ControlListModel::countChanged, this, &PlayerControlbarModel::contentChanged);
+    connect(m_center, &ControlListModel::countChanged, this, &PlayerControlbarModel::contentChanged);
+    connect(m_right, &ControlListModel::countChanged, this, &PlayerControlbarModel::contentChanged);
+
+    connect(m_left, &QAbstractListModel::dataChanged, this, &PlayerControlbarModel::contentChanged);
+    connect(m_center, &QAbstractListModel::dataChanged, this, &PlayerControlbarModel::contentChanged);
+    connect(m_right, &QAbstractListModel::dataChanged, this, &PlayerControlbarModel::contentChanged);
+}
+
+PlayerControlbarModel::~PlayerControlbarModel()
+{
+    setDirty(false);
+}
+
+bool PlayerControlbarModel::dirty() const
+{
+    return m_dirty;
+}
+
+std::array<QVector<int>, 3> PlayerControlbarModel::serializeModels() const
+{
+    return { left()->getControls(),
+            center()->getControls(),
+            right()->getControls() };
+}
+
+void PlayerControlbarModel::loadModels(const std::array<QVector<int>, 3> &array)
+{
+    left()->setControls(array.at(0));
+    center()->setControls(array.at(1));
+    right()->setControls(array.at(2));
+}
+
+ControlListModel *PlayerControlbarModel::left() const
+{
+    return m_left;
+}
+
+ControlListModel *PlayerControlbarModel::center() const
+{
+    return m_center;
+}
+
+ControlListModel *PlayerControlbarModel::right() const
+{
+    return m_right;
+}
+
+void PlayerControlbarModel::setDirty(bool dirty)
+{
+    if (m_dirty == dirty)
+        return;
+
+    m_dirty = dirty;
+    emit dirtyChanged(m_dirty);
+}
+
+void PlayerControlbarModel::contentChanged()
+{
+    setDirty(true);
+
+    emit controlListChanged();
+}
diff --git a/modules/gui/qt/player/player_controlbar_model.hpp b/modules/gui/qt/player/player_controlbar_model.hpp
new file mode 100644
index 0000000000..a6b71319cf
--- /dev/null
+++ b/modules/gui/qt/player/player_controlbar_model.hpp
@@ -0,0 +1,68 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef PLAYERCONTROLBARMODEL_HPP
+#define PLAYERCONTROLBARMODEL_HPP
+
+#include <QObject>
+#include <array>
+
+class ControlListModel;
+
+class PlayerControlbarModel : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged)
+
+    Q_PROPERTY(ControlListModel* left READ left CONSTANT)
+    Q_PROPERTY(ControlListModel* center READ center CONSTANT)
+    Q_PROPERTY(ControlListModel* right READ right CONSTANT)
+
+public:
+    explicit PlayerControlbarModel(QObject *parent = nullptr);
+    ~PlayerControlbarModel();
+
+    bool dirty() const;
+
+    std::array<QVector<int>, 3> serializeModels() const;
+    void loadModels(const std::array<QVector<int>, 3>& array);
+
+    ControlListModel* left() const;
+    ControlListModel* center() const;
+    ControlListModel* right() const;
+
+public slots:
+    void setDirty(bool dirty);
+
+signals:
+    void dirtyChanged(bool dirty);
+    void controlListChanged();
+
+private:
+    bool m_dirty = false;
+
+    ControlListModel* m_left = nullptr;
+    ControlListModel* m_center = nullptr;
+    ControlListModel* m_right = nullptr;
+
+private slots:
+    void contentChanged();
+};
+
+#endif
diff --git a/modules/gui/qt/player/playercontrolbarmodel.cpp b/modules/gui/qt/player/playercontrolbarmodel.cpp
deleted file mode 100644
index 6aa486cfa5..0000000000
--- a/modules/gui/qt/player/playercontrolbarmodel.cpp
+++ /dev/null
@@ -1,326 +0,0 @@
-/*****************************************************************************
- * Copyright (C) 2019 VLC authors and VideoLAN
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * ( at your option ) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-#include <QSettings>
-
-#include "qt.hpp"
-#include "playercontrolbarmodel.hpp"
-
-enum default_align {
-    ALIGN_LEFT = 0,
-    ALIGN_CENTER,
-    ALIGN_RIGHT,
-    ALIGN_SIZE
-};
-
-static const QVector<PlayerControlBarModel::IconToolButton> MAIN_TB_DEFAULT[default_align::ALIGN_SIZE] = {
-    {
-        // left
-        {PlayerControlBarModel::LANG_BUTTON},
-        {PlayerControlBarModel::MENU_BUTTON}
-    },
-    {
-        // center
-        {PlayerControlBarModel::RANDOM_BUTTON},
-        {PlayerControlBarModel::PREVIOUS_BUTTON},
-        {PlayerControlBarModel::PLAY_BUTTON},
-        {PlayerControlBarModel::NEXT_BUTTON},
-        {PlayerControlBarModel::LOOP_BUTTON}
-    },
-    {
-        // right
-        {PlayerControlBarModel::VOLUME},
-        {PlayerControlBarModel::FULLSCREEN_BUTTON}
-    }
-};
-
-static const QVector<PlayerControlBarModel::IconToolButton> MINI_TB_DEFAULT[default_align::ALIGN_SIZE] = {
-    {
-        // left
-        {PlayerControlBarModel::ARTWORK_INFO}
-    },
-    {
-        // center
-        {PlayerControlBarModel::RANDOM_BUTTON},
-        {PlayerControlBarModel::PREVIOUS_BUTTON},
-        {PlayerControlBarModel::PLAY_BUTTON},
-        {PlayerControlBarModel::NEXT_BUTTON},
-        {PlayerControlBarModel::LOOP_BUTTON}
-    },
-    {
-        // right
-        {PlayerControlBarModel::VOLUME},
-        {PlayerControlBarModel::PLAYER_SWITCH_BUTTON}
-    }
-};
-
-
-PlayerControlBarModel::PlayerControlBarModel(QObject *_parent) : QAbstractListModel(_parent)
-{
-    configName = "MainPlayerToolbar";
-
-    connect(this, &QAbstractListModel::rowsInserted, this, &PlayerControlBarModel::countChanged);
-    connect(this, &QAbstractListModel::rowsRemoved, this, &PlayerControlBarModel::countChanged);
-    connect(this, &QAbstractListModel::modelReset, this, &PlayerControlBarModel::countChanged);
-}
-
-void PlayerControlBarModel::saveConfig()
-{
-    getSettings()->setValue(configName,getConfig());
-}
-
-QString PlayerControlBarModel::getConfig()
-{
-    QString config="";
-    for (IconToolButton it: mButtons) {
-        config += QString::number(it.id);
-        config += ";";
-    }
-    return config;
-}
-
-void PlayerControlBarModel::reloadConfig(QString config)
-{
-    beginResetModel();
-    mButtons.clear();
-    if (!config.isEmpty())
-        parseAndAdd(config);
-    endResetModel();
-}
-
-void PlayerControlBarModel::reloadModel()
-{
-    beginResetModel();
-    mButtons.clear();
-
-    QVariant config = getSettings() ->value(configName);
-
-    if (!config.isNull() && config.canConvert<QString>())
-        parseAndAdd(config.toString());
-    else
-    {
-        const auto configAndAlignment = configName.split("-");
-        if (configAndAlignment.size() == 2)
-        {
-            const auto alignment = configAndAlignment[1];
-            if (configAndAlignment[0] == QLatin1String("MainPlayerToolbar"))
-            {
-                if (alignment == "left")
-                    parseDefault(MAIN_TB_DEFAULT[default_align::ALIGN_LEFT]);
-                else if (alignment == "center")
-                    parseDefault(MAIN_TB_DEFAULT[default_align::ALIGN_CENTER]);
-                else if (alignment == "right")
-                    parseDefault(MAIN_TB_DEFAULT[default_align::ALIGN_RIGHT]);
-            }
-            else
-            {
-                if (alignment == "left")
-                    parseDefault(MINI_TB_DEFAULT[default_align::ALIGN_LEFT]);
-                else if (alignment == "center")
-                    parseDefault(MINI_TB_DEFAULT[default_align::ALIGN_CENTER]);
-                else if (alignment == "right")
-                    parseDefault(MINI_TB_DEFAULT[default_align::ALIGN_RIGHT]);
-
-            }
-        }
-    }
-    endResetModel();
-}
-
-void PlayerControlBarModel::parseDefault(const QVector<PlayerControlBarModel::IconToolButton>& config)
-{
-    beginInsertRows(QModelIndex(),rowCount(),rowCount() + config.size());
-    for (const auto& i : config)
-        mButtons.append(i);
-    endInsertRows();
-}
-
-void PlayerControlBarModel::parseAndAdd(const QString &config)
-{
-    beginInsertRows(QModelIndex(),rowCount(),rowCount()+config.split(";",
-                                                                     #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
-                                                                         Qt::SkipEmptyParts
-                                                                     #else
-                                                                         QString::SkipEmptyParts
-                                                                     #endif
-                                                                     ).length() - 1);
-
-    for (const QString& iconPropertyTxt : config.split( ";",
-                                                    #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
-                                                        Qt::SkipEmptyParts
-                                                    #else
-                                                        QString::SkipEmptyParts
-                                                    #endif
-                                                    ) )
-    {
-        QStringList list2 = iconPropertyTxt.trimmed().split( "-" );
-
-        if( list2.count() < 1 )
-        {
-            msg_Warn( p_intf, "Parsing error 1. Please, report this." );
-            continue;
-        }
-        bool ok;
-        ButtonType_e i_type = static_cast<ButtonType_e>(list2.at( 0 ).toInt( &ok ));
-        if( !ok )
-        {
-            msg_Warn( p_intf, "Parsing error 2. Please, report this." );
-            continue;
-        }
-
-        IconToolButton itButton = {i_type};
-        mButtons.append(itButton);
-    }
-
-    endInsertRows();
-}
-
-int PlayerControlBarModel::rowCount(const QModelIndex &parent) const
-{
-    if (parent.isValid() )
-        return 0;
-
-    return mButtons.size();
-}
-
-QVariant PlayerControlBarModel::data(const QModelIndex &index, int role) const
-{
-    if (!index.isValid() )
-        return QVariant();
-
-    const IconToolButton button = mButtons.at(index.row());
-
-    switch (role) {
-    case ID_ROLE:
-        return QVariant(button.id);
-    }
-    return QVariant();
-}
-
-bool PlayerControlBarModel::setData(const QModelIndex &index, const QVariant &value, int role)
-{
-    IconToolButton button = mButtons.at(index.row());
-    switch (role) {
-    case ID_ROLE:
-        button.id = value.toInt();
-        break;
-    }
-
-    if (setButtonAt(index.row(),button)) {
-        emit dataChanged(index, index, QVector<int>() << role);
-        return true;
-    }
-    return false;
-}
-
-Qt::ItemFlags PlayerControlBarModel::flags(const QModelIndex &index) const
-{
-    if (!index.isValid())
-        return Qt::NoItemFlags;
-
-    return Qt::ItemIsEditable;
-}
-
-QHash<int, QByteArray> PlayerControlBarModel::roleNames() const
-{
-    QHash<int, QByteArray> names;
-
-    names[ID_ROLE] = "id";
-
-    return names;
-}
-bool PlayerControlBarModel::setButtonAt(int index, const IconToolButton &button)
-{
-    if(index < 0 || index >= mButtons.size())
-        return false;
-    const IconToolButton &oldButton = mButtons.at(index);
-
-    if (button.id == oldButton.id)
-        return false;
-
-    mButtons[index] = button;
-    return true;
-}
-
-void PlayerControlBarModel::setMainCtx(QmlMainContext* ctx)
-{
-    if(ctx == nullptr && m_mainCtx == ctx)
-        return;
-    m_mainCtx = ctx;
-    p_intf = m_mainCtx->getIntf();
-    assert(p_intf != nullptr);
-    reloadModel();
-    emit ctxChanged(ctx);
-}
-
-void PlayerControlBarModel::setConfigName(QString name)
-{
-    if(configName == name)
-        return;
-    configName = name;
-    if  (m_mainCtx)
-        reloadModel();
-    emit configNameChanged(name);
-}
-
-QString PlayerControlBarModel::getSerializedDefaultStyle()
-{
-    QString out;
-
-    auto serialize = [](auto style)
-    {
-          QString _out;
-          for (size_t i = 0; i < default_align::ALIGN_SIZE; i++)
-          {
-              for (const auto& it : style[i])
-              {
-                  _out += QString::number(it.id) + ";";
-              }
-              _out.chop(1);
-              _out += "#";
-          }
-          _out.chop(1);
-          return _out;
-    };
-
-    out += serialize(MAIN_TB_DEFAULT);
-    out += " | ";
-    out += serialize(MINI_TB_DEFAULT);
-
-    return out;
-}
-
-void PlayerControlBarModel::insert(int index, QVariantMap bdata)
-{
-    beginInsertRows(QModelIndex(),index,index);
-    mButtons.insert(index, { bdata.value("id").toInt() });
-    endInsertRows();
-}
-void PlayerControlBarModel::move(int src, int dest)
-{
-    if(src == dest) return;
-    beginMoveRows(QModelIndex(),src,src,QModelIndex(),dest + (src < dest ? 1:0));
-    mButtons.move(src,dest);
-    endMoveRows();
-}
-
-void PlayerControlBarModel::remove(int index)
-{
-    beginRemoveRows(QModelIndex(),index,index);
-    mButtons.remove(index);
-    endRemoveRows();
-}
diff --git a/modules/gui/qt/player/qml/ControlBar.qml b/modules/gui/qt/player/qml/ControlBar.qml
index a867bca770..17655e3578 100644
--- a/modules/gui/qt/player/qml/ControlBar.qml
+++ b/modules/gui/qt/player/qml/ControlBar.qml
@@ -40,7 +40,7 @@ Widgets.NavigableFocusScope {
     readonly property alias sliderY: row2.y
     property int textPosition: ControlBar.TimeTextPosition.AboveSlider
     property VLCColors colors: VLCStyle.nightColors
-    property var configs: ["MainPlayerToolbar-left", "MainPlayerToolbar-center", "MainPlayerToolbar-right"]
+    property alias identifier: playerButtonsLayout.identifier
     property alias sliderHeight: trackPositionSlider.barHeight
     property alias sliderBackgroundColor: trackPositionSlider.backgroundColor
     property alias sliderProgressColor: trackPositionSlider.progressBarColor
@@ -145,8 +145,6 @@ Widgets.NavigableFocusScope {
                     bottomMargin: VLCStyle.applicationVerticalMargin
                 }
 
-                models: [playerControlBarModel_left, playerControlBarModel_center, playerControlBarModel_right]
-
                 navigationUpItem: trackPositionSlider.enabled ? trackPositionSlider : root.navigationUpItem
 
                 colors: root.colors
@@ -194,23 +192,4 @@ Widgets.NavigableFocusScope {
 
         Keys.onDownPressed: playerButtonsLayout.focus = true
     }
-
-
-    PlayerControlBarModel{
-        id:playerControlBarModel_left
-        mainCtx: mainctx
-        configName: root.configs[0]
-    }
-
-    PlayerControlBarModel{
-        id:playerControlBarModel_center
-        mainCtx: mainctx
-        configName: root.configs[1]
-    }
-
-    PlayerControlBarModel{
-        id:playerControlBarModel_right
-        mainCtx: mainctx
-        configName: root.configs[2]
-    }
 }
diff --git a/modules/gui/qt/player/qml/ControlButtons.qml b/modules/gui/qt/player/qml/ControlButtons.qml
index e04c00d45c..c3f3dac142 100644
--- a/modules/gui/qt/player/qml/ControlButtons.qml
+++ b/modules/gui/qt/player/qml/ControlButtons.qml
@@ -35,74 +35,74 @@ Item{
     signal requestLockUnlockAutoHide(bool lock, var source)
 
     property var buttonL: [
-        { id:  PlayerControlBarModel.PLAY_BUTTON, label: VLCIcons.play, text: i18n.qtr("Play")},
-        { id:  PlayerControlBarModel.STOP_BUTTON, label: VLCIcons.stop, text: i18n.qtr("Stop")},
-        { id:  PlayerControlBarModel.OPEN_BUTTON, label: VLCIcons.eject, text: i18n.qtr("Open")},
-        { id:  PlayerControlBarModel.PREVIOUS_BUTTON, label: VLCIcons.previous, text: i18n.qtr("Previous")},
-        { id:  PlayerControlBarModel.NEXT_BUTTON, label: VLCIcons.next, text: i18n.qtr("Next")},
-        { id:  PlayerControlBarModel.SLOWER_BUTTON, label: VLCIcons.slower, text: i18n.qtr("Slower")},
-        { id:  PlayerControlBarModel.FASTER_BUTTON, label: VLCIcons.faster, text: i18n.qtr("Faster")},
-        { id:  PlayerControlBarModel.FULLSCREEN_BUTTON, label: VLCIcons.fullscreen, text: i18n.qtr("Fullscreen")},
-        { id:  PlayerControlBarModel.EXTENDED_BUTTON, label: VLCIcons.extended, text: i18n.qtr("Extended panel")},
-        { id:  PlayerControlBarModel.PLAYLIST_BUTTON, label: VLCIcons.playlist, text: i18n.qtr("Playlist")},
-        { id:  PlayerControlBarModel.SNAPSHOT_BUTTON, label: VLCIcons.snapshot, text: i18n.qtr("Snapshot")},
-        { id:  PlayerControlBarModel.RECORD_BUTTON, label: VLCIcons.record, text: i18n.qtr("Record")},
-        { id:  PlayerControlBarModel.ATOB_BUTTON, label: VLCIcons.atob, text: i18n.qtr("A-B Loop")},
-        { id:  PlayerControlBarModel.FRAME_BUTTON, label: VLCIcons.frame_by_frame, text: i18n.qtr("Frame By Frame")},
-        { id:  PlayerControlBarModel.SKIP_BACK_BUTTON, label: VLCIcons.skip_back, text: i18n.qtr("Step backward")},
-        { id:  PlayerControlBarModel.SKIP_FW_BUTTON, label: VLCIcons.skip_for, text: i18n.qtr("Step forward")},
-        { id:  PlayerControlBarModel.QUIT_BUTTON, label: VLCIcons.clear, text: i18n.qtr("Quit")},
-        { id:  PlayerControlBarModel.RANDOM_BUTTON, label: VLCIcons.shuffle_on, text: i18n.qtr("Random")},
-        { id:  PlayerControlBarModel.LOOP_BUTTON, label: VLCIcons.repeat_all, text: i18n.qtr("Loop")},
-        { id:  PlayerControlBarModel.INFO_BUTTON, label: VLCIcons.info, text: i18n.qtr("Information")},
-        { id:  PlayerControlBarModel.LANG_BUTTON, label: VLCIcons.audiosub, text: i18n.qtr("Open subtitles")},
-        { id:  PlayerControlBarModel.MENU_BUTTON, label: VLCIcons.menu, text: i18n.qtr("Menu Button")},
-        { id:  PlayerControlBarModel.BACK_BUTTON, label: VLCIcons.exit, text: i18n.qtr("Back Button")},
-        { id:  PlayerControlBarModel.CHAPTER_PREVIOUS_BUTTON, label: VLCIcons.dvd_prev, text: i18n.qtr("Previous chapter")},
-        { id:  PlayerControlBarModel.CHAPTER_NEXT_BUTTON, label: VLCIcons.dvd_next, text: i18n.qtr("Next chapter")},
-        { id:  PlayerControlBarModel.VOLUME, label: VLCIcons.volume_high, text: i18n.qtr("Volume Widget")},
-        { id:  PlayerControlBarModel.TELETEXT_BUTTONS, label: VLCIcons.tvtelx, text: i18n.qtr("Teletext")},
-        { id:  PlayerControlBarModel.ASPECT_RATIO_COMBOBOX, label: VLCIcons.aspect_ratio, text: i18n.qtr("Aspect Ratio")},
-        { id:  PlayerControlBarModel.WIDGET_SPACER, label: VLCIcons.space, text: i18n.qtr("Spacer")},
-        { id:  PlayerControlBarModel.WIDGET_SPACER_EXTEND, label: VLCIcons.space, text: i18n.qtr("Expanding Spacer")},
-        { id:  PlayerControlBarModel.PLAYER_SWITCH_BUTTON, label: VLCIcons.fullscreen, text: i18n.qtr("Switch Player")},
-        { id:  PlayerControlBarModel.ARTWORK_INFO, label: VLCIcons.info, text: i18n.qtr("Artwork Info")}
+        { id:  ControlListModel.PLAY_BUTTON, label: VLCIcons.play, text: i18n.qtr("Play")},
+        { id:  ControlListModel.STOP_BUTTON, label: VLCIcons.stop, text: i18n.qtr("Stop")},
+        { id:  ControlListModel.OPEN_BUTTON, label: VLCIcons.eject, text: i18n.qtr("Open")},
+        { id:  ControlListModel.PREVIOUS_BUTTON, label: VLCIcons.previous, text: i18n.qtr("Previous")},
+        { id:  ControlListModel.NEXT_BUTTON, label: VLCIcons.next, text: i18n.qtr("Next")},
+        { id:  ControlListModel.SLOWER_BUTTON, label: VLCIcons.slower, text: i18n.qtr("Slower")},
+        { id:  ControlListModel.FASTER_BUTTON, label: VLCIcons.faster, text: i18n.qtr("Faster")},
+        { id:  ControlListModel.FULLSCREEN_BUTTON, label: VLCIcons.fullscreen, text: i18n.qtr("Fullscreen")},
+        { id:  ControlListModel.EXTENDED_BUTTON, label: VLCIcons.extended, text: i18n.qtr("Extended panel")},
+        { id:  ControlListModel.PLAYLIST_BUTTON, label: VLCIcons.playlist, text: i18n.qtr("Playlist")},
+        { id:  ControlListModel.SNAPSHOT_BUTTON, label: VLCIcons.snapshot, text: i18n.qtr("Snapshot")},
+        { id:  ControlListModel.RECORD_BUTTON, label: VLCIcons.record, text: i18n.qtr("Record")},
+        { id:  ControlListModel.ATOB_BUTTON, label: VLCIcons.atob, text: i18n.qtr("A-B Loop")},
+        { id:  ControlListModel.FRAME_BUTTON, label: VLCIcons.frame_by_frame, text: i18n.qtr("Frame By Frame")},
+        { id:  ControlListModel.SKIP_BACK_BUTTON, label: VLCIcons.skip_back, text: i18n.qtr("Step backward")},
+        { id:  ControlListModel.SKIP_FW_BUTTON, label: VLCIcons.skip_for, text: i18n.qtr("Step forward")},
+        { id:  ControlListModel.QUIT_BUTTON, label: VLCIcons.clear, text: i18n.qtr("Quit")},
+        { id:  ControlListModel.RANDOM_BUTTON, label: VLCIcons.shuffle_on, text: i18n.qtr("Random")},
+        { id:  ControlListModel.LOOP_BUTTON, label: VLCIcons.repeat_all, text: i18n.qtr("Loop")},
+        { id:  ControlListModel.INFO_BUTTON, label: VLCIcons.info, text: i18n.qtr("Information")},
+        { id:  ControlListModel.LANG_BUTTON, label: VLCIcons.audiosub, text: i18n.qtr("Open subtitles")},
+        { id:  ControlListModel.MENU_BUTTON, label: VLCIcons.menu, text: i18n.qtr("Menu Button")},
+        { id:  ControlListModel.BACK_BUTTON, label: VLCIcons.exit, text: i18n.qtr("Back Button")},
+        { id:  ControlListModel.CHAPTER_PREVIOUS_BUTTON, label: VLCIcons.dvd_prev, text: i18n.qtr("Previous chapter")},
+        { id:  ControlListModel.CHAPTER_NEXT_BUTTON, label: VLCIcons.dvd_next, text: i18n.qtr("Next chapter")},
+        { id:  ControlListModel.VOLUME, label: VLCIcons.volume_high, text: i18n.qtr("Volume Widget")},
+        { id:  ControlListModel.TELETEXT_BUTTONS, label: VLCIcons.tvtelx, text: i18n.qtr("Teletext")},
+        { id:  ControlListModel.ASPECT_RATIO_COMBOBOX, label: VLCIcons.aspect_ratio, text: i18n.qtr("Aspect Ratio")},
+        { id:  ControlListModel.WIDGET_SPACER, label: VLCIcons.space, text: i18n.qtr("Spacer")},
+        { id:  ControlListModel.WIDGET_SPACER_EXTEND, label: VLCIcons.space, text: i18n.qtr("Expanding Spacer")},
+        { id:  ControlListModel.PLAYER_SWITCH_BUTTON, label: VLCIcons.fullscreen, text: i18n.qtr("Switch Player")},
+        { id:  ControlListModel.ARTWORK_INFO, label: VLCIcons.info, text: i18n.qtr("Artwork Info")}
     ]
 
     function returnbuttondelegate(inpID){
         switch (inpID){
-        case PlayerControlBarModel.RANDOM_BUTTON: return randomBtnDelegate
-        case PlayerControlBarModel.PREVIOUS_BUTTON: return prevBtnDelegate
-        case PlayerControlBarModel.PLAY_BUTTON: return playBtnDelegate
-        case PlayerControlBarModel.NEXT_BUTTON: return nextBtnDelegate
-        case PlayerControlBarModel.LOOP_BUTTON: return repeatBtnDelegate
-        case PlayerControlBarModel.LANG_BUTTON: return langBtnDelegate
-        case PlayerControlBarModel.PLAYLIST_BUTTON:return playlistBtnDelegate
-        case PlayerControlBarModel.MENU_BUTTON:return  menuBtnDelegate
-        case PlayerControlBarModel.CHAPTER_PREVIOUS_BUTTON:return  chapterPreviousBtnDelegate
-        case PlayerControlBarModel.CHAPTER_NEXT_BUTTON:return  chapterNextBtnDelegate
-        case PlayerControlBarModel.BACK_BUTTON:return  backBtnDelegate
-        case PlayerControlBarModel.WIDGET_SPACER:return  spacerDelegate
-        case PlayerControlBarModel.WIDGET_SPACER_EXTEND:return  extendiblespacerDelegate
-        case PlayerControlBarModel.RECORD_BUTTON: return recordBtnDelegate
-        case PlayerControlBarModel.FULLSCREEN_BUTTON: return fullScreenBtnDelegate
-        case PlayerControlBarModel.ATOB_BUTTON: return toggleABloopstateDelegate
-        case PlayerControlBarModel.SNAPSHOT_BUTTON: return snapshotBtnDelegate
-        case PlayerControlBarModel.STOP_BUTTON: return stopBtndelgate
-        case PlayerControlBarModel.INFO_BUTTON: return mediainfoBtnDelegate
-        case PlayerControlBarModel.FRAME_BUTTON: return framebyframeDelegate
-        case PlayerControlBarModel.FASTER_BUTTON: return fasterBtnDelegate
-        case PlayerControlBarModel.SLOWER_BUTTON: return slowerBtnDelegate
-        case PlayerControlBarModel.OPEN_BUTTON: return openmediaBtnDelegate
-        case PlayerControlBarModel.EXTENDED_BUTTON: return extdSettingsBtnDelegate
-        case PlayerControlBarModel.SKIP_FW_BUTTON: return stepFwdBtnDelegate
-        case PlayerControlBarModel.SKIP_BACK_BUTTON: return stepBackBtnDelegate
-        case PlayerControlBarModel.QUIT_BUTTON: return quitBtnDelegate
-        case PlayerControlBarModel.VOLUME: return volumeBtnDelegate
-        case PlayerControlBarModel.ASPECT_RATIO_COMBOBOX: return aspectRatioDelegate
-        case PlayerControlBarModel.TELETEXT_BUTTONS: return teletextdelegate
-        case PlayerControlBarModel.PLAYER_SWITCH_BUTTON: return playerSwitchBtnDelegate
-        case PlayerControlBarModel.ARTWORK_INFO: return artworkInfoDelegate
+        case ControlListModel.RANDOM_BUTTON: return randomBtnDelegate
+        case ControlListModel.PREVIOUS_BUTTON: return prevBtnDelegate
+        case ControlListModel.PLAY_BUTTON: return playBtnDelegate
+        case ControlListModel.NEXT_BUTTON: return nextBtnDelegate
+        case ControlListModel.LOOP_BUTTON: return repeatBtnDelegate
+        case ControlListModel.LANG_BUTTON: return langBtnDelegate
+        case ControlListModel.PLAYLIST_BUTTON:return playlistBtnDelegate
+        case ControlListModel.MENU_BUTTON:return  menuBtnDelegate
+        case ControlListModel.CHAPTER_PREVIOUS_BUTTON:return  chapterPreviousBtnDelegate
+        case ControlListModel.CHAPTER_NEXT_BUTTON:return  chapterNextBtnDelegate
+        case ControlListModel.BACK_BUTTON:return  backBtnDelegate
+        case ControlListModel.WIDGET_SPACER:return  spacerDelegate
+        case ControlListModel.WIDGET_SPACER_EXTEND:return  extendiblespacerDelegate
+        case ControlListModel.RECORD_BUTTON: return recordBtnDelegate
+        case ControlListModel.FULLSCREEN_BUTTON: return fullScreenBtnDelegate
+        case ControlListModel.ATOB_BUTTON: return toggleABloopstateDelegate
+        case ControlListModel.SNAPSHOT_BUTTON: return snapshotBtnDelegate
+        case ControlListModel.STOP_BUTTON: return stopBtndelgate
+        case ControlListModel.INFO_BUTTON: return mediainfoBtnDelegate
+        case ControlListModel.FRAME_BUTTON: return framebyframeDelegate
+        case ControlListModel.FASTER_BUTTON: return fasterBtnDelegate
+        case ControlListModel.SLOWER_BUTTON: return slowerBtnDelegate
+        case ControlListModel.OPEN_BUTTON: return openmediaBtnDelegate
+        case ControlListModel.EXTENDED_BUTTON: return extdSettingsBtnDelegate
+        case ControlListModel.SKIP_FW_BUTTON: return stepFwdBtnDelegate
+        case ControlListModel.SKIP_BACK_BUTTON: return stepBackBtnDelegate
+        case ControlListModel.QUIT_BUTTON: return quitBtnDelegate
+        case ControlListModel.VOLUME: return volumeBtnDelegate
+        case ControlListModel.ASPECT_RATIO_COMBOBOX: return aspectRatioDelegate
+        case ControlListModel.TELETEXT_BUTTONS: return teletextdelegate
+        case ControlListModel.PLAYER_SWITCH_BUTTON: return playerSwitchBtnDelegate
+        case ControlListModel.ARTWORK_INFO: return artworkInfoDelegate
         }
         console.log("button delegate id " + inpID +  " doesn't exists")
         return spacerDelegate
diff --git a/modules/gui/qt/player/qml/MiniPlayer.qml b/modules/gui/qt/player/qml/MiniPlayer.qml
index 748751da31..9cc983dc05 100644
--- a/modules/gui/qt/player/qml/MiniPlayer.qml
+++ b/modules/gui/qt/player/qml/MiniPlayer.qml
@@ -83,7 +83,7 @@ Widgets.NavigableFocusScope {
         sliderHeight: VLCStyle.dp(3, VLCStyle.scale)
         sliderBackgroundColor: colors.sliderBarMiniplayerBgColor
         sliderProgressColor: colors.accent
-        configs: ["MiniPlayerToolbar-left", "MiniPlayerToolbar-center", "MiniPlayerToolbar-right"]
+        identifier: "MiniPlayer"
         navigationParent: root
 
         Keys.onPressed: {
diff --git a/modules/gui/qt/player/qml/Player.qml b/modules/gui/qt/player/qml/Player.qml
index e7190fcd34..ebe95f5792 100644
--- a/modules/gui/qt/player/qml/Player.qml
+++ b/modules/gui/qt/player/qml/Player.qml
@@ -499,6 +499,8 @@ Widgets.NavigableFocusScope {
                 navigationUpItem: playlistpopup.showPlaylist ? playlistpopup : (audioControls.visible ? audioControls : topcontrolView)
 
                 onRequestLockUnlockAutoHide: rootPlayer.lockUnlockAutoHide(lock, source)
+
+                identifier: "MainPlayer"
             }
         }
     }
diff --git a/modules/gui/qt/player/qml/PlayerButtonsLayout.qml b/modules/gui/qt/player/qml/PlayerButtonsLayout.qml
index b6016988cd..15f11ae293 100644
--- a/modules/gui/qt/player/qml/PlayerButtonsLayout.qml
+++ b/modules/gui/qt/player/qml/PlayerButtonsLayout.qml
@@ -42,24 +42,20 @@ Widgets.NavigableFocusScope {
     property real spacing: VLCStyle.margin_normal // spacing between controls
     property real layoutSpacing: VLCStyle.margin_xlarge // spacing between layouts (left, center, and right)
 
-    signal requestLockUnlockAutoHide(bool lock, var source)
-
-    enum Alignment {
-        Left = 0,
-        Center = 1,
-        Right = 2
+    property string identifier
+    readonly property var model: {
+        if (!!mainInterface.controlbarProfileModel.currentModel)
+            mainInterface.controlbarProfileModel.currentModel.getModel(identifier)
+        else
+            undefined
     }
 
-    property var models: []
+    signal requestLockUnlockAutoHide(bool lock, var source)
 
-    Connections {
-        target: mainInterface
 
-        onToolBarConfUpdated: {
-            models[PlayerButtonsLayout.Alignment.Left].reloadModel()
-            models[PlayerButtonsLayout.Alignment.Center].reloadModel()
-            models[PlayerButtonsLayout.Alignment.Right].reloadModel()
-        }
+    Component.onCompleted: {
+        console.assert(!!identifier)
+        console.assert(identifier.length > 0)
     }
 
     ControlButtons {
@@ -70,13 +66,9 @@ Widgets.NavigableFocusScope {
         onRequestLockUnlockAutoHide: playerButtonsLayout.requestLockUnlockAutoHide(lock, source)
     }
 
-    ButtonsLayout {
+    Loader {
         id: buttonrow_left
 
-        model: models[PlayerButtonsLayout.Alignment.Left]
-
-        extraWidth: (buttonrow_center.x - buttonrow_left.x - minimumWidth - layoutSpacing)
-
         anchors {
             left: parent.left
             verticalCenter: parent.verticalCenter
@@ -86,20 +78,26 @@ Widgets.NavigableFocusScope {
             bottomMargin: marginBottom
             rightMargin: layoutSpacing
         }
-        
-        visible: extraWidth < 0 ? false : true // extraWidth < 0 means there is not even available space for minimumSize
 
-        navigationParent: playerButtonsLayout
-        navigationRightItem: buttonrow_center
+        active: !!playerButtonsLayout.model && !!playerButtonsLayout.model.left
 
-        focus: true
+        sourceComponent: ButtonsLayout {
+            model: playerButtonsLayout.model.left
+
+            extraWidth: (buttonrow_center.x - buttonrow_left.x - minimumWidth - layoutSpacing)
+
+            visible: extraWidth < 0 ? false : true // extraWidth < 0 means there is not even available space for minimumSize
+
+            navigationParent: playerButtonsLayout
+            navigationRightItem: buttonrow_center
+
+            focus: true
+        }
     }
 
-    ButtonsLayout {
+    Loader {
         id: buttonrow_center
 
-        model: models[PlayerButtonsLayout.Alignment.Center]
-
         anchors {
             centerIn: parent
 
@@ -107,17 +105,19 @@ Widgets.NavigableFocusScope {
             bottomMargin: playerButtonsLayout.marginBottom
         }
 
-        navigationParent: playerButtonsLayout
-        navigationLeftItem: buttonrow_left
-        navigationRightItem: buttonrow_right
-    }
+        active: !!playerButtonsLayout.model && !!playerButtonsLayout.model.center
 
-    ButtonsLayout {
-        id: buttonrow_right
+        sourceComponent: ButtonsLayout {
+            model: playerButtonsLayout.model.center
 
-        model: models[PlayerButtonsLayout.Alignment.Right]
+            navigationParent: playerButtonsLayout
+            navigationLeftItem: buttonrow_left
+            navigationRightItem: buttonrow_right
+        }
+    }
 
-        extraWidth: (playerButtonsLayout.width - (buttonrow_center.x + buttonrow_center.width) - minimumWidth - (2 * layoutSpacing))
+    Loader {
+        id: buttonrow_right
 
         anchors {
             right: parent.right
@@ -129,9 +129,19 @@ Widgets.NavigableFocusScope {
             leftMargin: layoutSpacing
         }
 
-        visible: extraWidth < 0 ? false : true // extraWidth < 0 means there is not even available space for minimumSize
+        active: !!playerButtonsLayout.model && !!playerButtonsLayout.model.right
+
+        sourceComponent: ButtonsLayout {
 
-        navigationParent: playerButtonsLayout
-        navigationLeftItem: buttonrow_center
+
+            model: playerButtonsLayout.model.right
+
+            extraWidth: (playerButtonsLayout.width - (buttonrow_center.x + buttonrow_center.width) - minimumWidth - (2 * layoutSpacing))
+
+            visible: extraWidth < 0 ? false : true // extraWidth < 0 means there is not even available space for minimumSize
+
+            navigationParent: playerButtonsLayout
+            navigationLeftItem: buttonrow_center
+        }
     }
 }
diff --git a/modules/gui/qt/player/qml/TopBar.qml b/modules/gui/qt/player/qml/TopBar.qml
index 4aa53d15a8..c621c63f1f 100644
--- a/modules/gui/qt/player/qml/TopBar.qml
+++ b/modules/gui/qt/player/qml/TopBar.qml
@@ -269,7 +269,7 @@ Widgets.NavigableFocusScope{
         Widgets.IconToolButton {
             id: playlistButton
 
-            objectName: PlayerControlBarModel.PLAYLIST_BUTTON
+            objectName: ControlListModel.PLAYLIST_BUTTON
             size: VLCStyle.banner_icon_size
             iconText: VLCIcons.playlist
             text: i18n.qtr("Playlist")
-- 
2.27.0




More information about the vlc-devel mailing list