[vlc-devel] [PATCH 2/3] qt: implement mixed qml/native menubar

Pierre Lamot pierre at videolabs.io
Wed Oct 7 15:13:58 CEST 2020


---
 modules/gui/qt/Makefile.am                |   1 +
 modules/gui/qt/maininterface/mainui.cpp   |   1 +
 modules/gui/qt/menus/qml/Menubar.qml      | 117 +++++++++++++++++++
 modules/gui/qt/menus/qml_menu_wrapper.cpp | 136 ++++++++++++++++++++++
 modules/gui/qt/menus/qml_menu_wrapper.hpp |  57 ++++++++-
 modules/gui/qt/vlc.qrc                    |   1 +
 6 files changed, 312 insertions(+), 1 deletion(-)
 create mode 100644 modules/gui/qt/menus/qml/Menubar.qml

diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am
index e6cab60218..1cf5da7f59 100644
--- a/modules/gui/qt/Makefile.am
+++ b/modules/gui/qt/Makefile.am
@@ -641,6 +641,7 @@ libqt_plugin_la_QML = \
 	gui/qt/menus/qml/HelpMenu.qml \
 	gui/qt/menus/qml/MainDropdownMenu.qml \
 	gui/qt/menus/qml/MainMenubar.qml \
+	gui/qt/menus/qml/Menubar.qml \
 	gui/qt/menus/qml/MediaMenu.qml \
 	gui/qt/menus/qml/PlaybackMenu.qml \
 	gui/qt/menus/qml/PopupMenu.qml \
diff --git a/modules/gui/qt/maininterface/mainui.cpp b/modules/gui/qt/maininterface/mainui.cpp
index 500b547585..9b8bc9e93f 100644
--- a/modules/gui/qt/maininterface/mainui.cpp
+++ b/modules/gui/qt/maininterface/mainui.cpp
@@ -215,6 +215,7 @@ void MainUI::registerQMLTypes()
     qmlRegisterType<PlayerControlBarModel>( "org.videolan.vlc", 0, 1, "PlayerControlBarModel");
 
     qmlRegisterType<QmlGlobalMenu>( "org.videolan.vlc", 0, 1, "QmlGlobalMenu" );
+    qmlRegisterType<QmlMenuBar>( "org.videolan.vlc", 0, 1, "QmlMenuBar" );
     qmlRegisterType<NetworkMediaContextMenu>( "org.videolan.vlc", 0, 1, "NetworkMediaContextMenu" );
     qmlRegisterType<NetworkDeviceContextMenu>( "org.videolan.vlc", 0, 1, "NetworkDeviceContextMenu" );
     qmlRegisterType<PlaylistContextMenu>( "org.videolan.vlc", 0, 1, "PlaylistContextMenu" );
diff --git a/modules/gui/qt/menus/qml/Menubar.qml b/modules/gui/qt/menus/qml/Menubar.qml
new file mode 100644
index 0000000000..97ffa4c486
--- /dev/null
+++ b/modules/gui/qt/menus/qml/Menubar.qml
@@ -0,0 +1,117 @@
+/*****************************************************************************
+ * Copyright (C) 2020 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.
+ *****************************************************************************/
+import QtQuick 2.11
+import QtQuick.Controls 2.4
+import QtQuick.Controls.impl 2.4
+import QtQuick.Templates 2.4 as T
+import org.videolan.vlc 0.1
+import QtQuick.Layouts 1.3
+
+import "qrc:///style/"
+
+
+Item {
+    id: root
+
+    implicitHeight: menubarLayout.implicitHeight
+    implicitWidth: menubarLayout.implicitWidth
+
+    property color textColor: VLCStyle.colors.text
+    property color bgColor:   VLCStyle.colors.bgHover
+
+    Action{ id: mediaMenu;    text: i18n.qtr("&Media")    ; onTriggered: menubar.popupMediaMenu(source);   }
+    Action{ id: playbackMenu; text: i18n.qtr("&Playback") ; onTriggered: menubar.popupPlaybackMenu(source);}
+    Action{ id: videoMenu;    text: i18n.qtr("&Video")    ; onTriggered: menubar.popupVideoMenu(source);   }
+    Action{ id: audioMenu;    text: i18n.qtr("&Audio")    ; onTriggered: menubar.popupAudioMenu(source);   }
+    Action{ id: subtitleMenu; text: i18n.qtr("&Subtitle") ; onTriggered: menubar.popupSubtitleMenu(source);}
+    Action{ id: toolMenu;     text: i18n.qtr("&Tools")    ; onTriggered: menubar.popupToolsMenu(source);   }
+    Action{ id: viewMenu;     text: i18n.qtr("V&iew")     ; onTriggered: menubar.popupViewMenu(source);    }
+    Action{ id: helpMenu;     text: i18n.qtr("&Help")     ; onTriggered: menubar.popupHelpMenu(source);    }
+
+    property var toolbarModel: [
+        mediaMenu,
+        playbackMenu,
+        videoMenu,
+        audioMenu,
+        subtitleMenu,
+        toolMenu,
+        viewMenu,
+        helpMenu,
+    ]
+
+    property int _menuIndex: -1
+
+    function openMenu(obj, cb, index) {
+        cb.trigger(obj)
+        root._menuIndex = index
+    }
+
+    function updateHover(obj, cb, index, hovered ) {
+        if (hovered && menubar.openMenuOnHover) {
+            cb.trigger(obj)
+            root._menuIndex = index
+        }
+    }
+
+    QmlMenuBar {
+        id: menubar
+        ctx: mainctx
+        menubar: menubarLayout
+
+        onMenuClosed: _menuIndex = -1
+        onNavigateMenu: {
+            var i =  (root._menuIndex + root.toolbarModel.length + direction) % root.toolbarModel.length
+            root.openMenu(menubarLayout.visibleChildren[i], root.toolbarModel[i], i)
+        }
+
+    }
+
+    RowLayout {
+        id: menubarLayout
+        spacing: 0
+        Repeater {
+            model: root.toolbarModel
+
+            T.Button {
+                id: control
+
+                text: modelData.text
+                onClicked: root.openMenu(this, modelData, index)
+                onHoveredChanged: root.updateHover(this, modelData, index, hovered)
+                font.pixelSize: VLCStyle.fontSize_normal
+
+                Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+
+                implicitWidth: contentItem.implicitWidth + VLCStyle.margin_xsmall * 2
+                implicitHeight: contentItem.implicitHeight + VLCStyle.margin_xxxsmall * 2
+
+                contentItem: IconLabel {
+                    text: control.text
+                    font: control.font
+                    opacity: enabled ? 1.0 : 0.3
+                    color: root.textColor
+                }
+
+                background: Rectangle {
+                    color: (control.hovered || index === root._menuIndex) ? root.bgColor
+                                                                          :  "transparent"
+                }
+            }
+        }
+    }
+}
diff --git a/modules/gui/qt/menus/qml_menu_wrapper.cpp b/modules/gui/qt/menus/qml_menu_wrapper.cpp
index 01a85ba730..259b10c877 100644
--- a/modules/gui/qt/menus/qml_menu_wrapper.cpp
+++ b/modules/gui/qt/menus/qml_menu_wrapper.cpp
@@ -30,6 +30,7 @@
 #include "playlist/playlist_controller.hpp"
 #include "playlist/playlist_model.hpp"
 #include "dialogs/dialogs_provider.hpp"
+#include "maininterface/main_interface.hpp"
 
 
 #include <QSignalMapper>
@@ -95,6 +96,141 @@ void QmlGlobalMenu::popup(QPoint pos)
     m_menu->popup(pos);
 }
 
+QmlMenuBarMenu::QmlMenuBarMenu(QmlMenuBar* menubar, QWidget* parent)
+    : QMenu(parent)
+    , m_menubar(menubar)
+{}
+
+QmlMenuBarMenu::~QmlMenuBarMenu()
+{
+}
+
+void QmlMenuBarMenu::mouseMoveEvent(QMouseEvent* mouseEvent)
+{
+    QPoint globalPos =m_menubar-> m_menu->mapToGlobal(mouseEvent->pos());
+    if (m_menubar->getmenubar()->contains(m_menubar->getmenubar()->mapFromGlobal(globalPos))
+        && !m_menubar->m_button->contains(m_menubar->m_button->mapFromGlobal(globalPos)))
+    {
+        m_menubar->setopenMenuOnHover(true);
+        close();
+        return;
+    }
+    QMenu::mouseMoveEvent(mouseEvent);
+}
+
+void QmlMenuBarMenu::keyPressEvent(QKeyEvent * event)
+{
+    QMenu::keyPressEvent(event);
+    if (!event->isAccepted()
+        && (event->key() == Qt::Key_Left  || event->key() == Qt::Key_Right))
+    {
+        event->accept();
+        emit m_menubar->navigateMenu(event->key() == Qt::Key_Left ? -1 : 1);
+    }
+}
+
+void QmlMenuBarMenu::keyReleaseEvent(QKeyEvent * event)
+{
+    QMenu::keyReleaseEvent(event);
+}
+
+QmlMenuBar::QmlMenuBar(QObject *parent)
+    : VLCMenuBar(parent)
+{
+}
+
+QmlMenuBar::~QmlMenuBar()
+{
+    if (m_menu)
+        delete m_menu;
+}
+
+void QmlMenuBar::popupMenuCommon( QQuickItem* button, std::function<void(QMenu*)> createMenuFunc)
+{
+    if (!m_ctx || !m_menubar || !button)
+        return;
+
+    intf_thread_t* p_intf = m_ctx->getIntf();
+    if (!p_intf)
+        return;
+
+    if (m_menu)
+        delete m_menu;
+
+    m_menu = new QmlMenuBarMenu(this, nullptr);
+    createMenuFunc(m_menu);
+    m_button = button;
+    m_openMenuOnHover = false;
+    connect(m_menu, &QMenu::aboutToHide, this, &QmlMenuBar::onMenuClosed);
+    QPointF position = button->mapToGlobal(QPoint(0, button->height()));
+    m_menu->popup(position.toPoint());
+}
+
+void QmlMenuBar::popupMediaMenu( QQuickItem* button )
+{
+    popupMenuCommon(button, [this](QMenu* menu) {
+        intf_thread_t* p_intf = m_ctx->getIntf();
+        FileMenu( p_intf, menu , p_intf->p_sys->p_mi );
+    });
+}
+
+void QmlMenuBar::popupPlaybackMenu( QQuickItem* button )
+{
+    popupMenuCommon(button, [this](QMenu* menu) {
+        NavigMenu( m_ctx->getIntf(), menu );
+    });
+}
+
+void QmlMenuBar::popupAudioMenu(QQuickItem* button )
+{
+    popupMenuCommon(button, [this](QMenu* menu) {
+        AudioMenu( m_ctx->getIntf(), menu );
+    });
+}
+
+void QmlMenuBar::popupVideoMenu( QQuickItem* button )
+{
+    popupMenuCommon(button, [this](QMenu* menu) {
+        VideoMenu( m_ctx->getIntf(), menu );
+    });
+}
+
+void QmlMenuBar::popupSubtitleMenu( QQuickItem* button )
+{
+    popupMenuCommon(button, [this](QMenu* menu) {
+        SubtitleMenu( m_ctx->getIntf(), menu );
+    });
+}
+
+
+void QmlMenuBar::popupToolsMenu( QQuickItem* button )
+{
+    popupMenuCommon(button, [this](QMenu* menu) {
+        ToolsMenu( m_ctx->getIntf(), menu );
+    });
+}
+
+void QmlMenuBar::popupViewMenu( QQuickItem* button )
+{
+    popupMenuCommon(button, [this](QMenu* menu) {
+        intf_thread_t* p_intf = m_ctx->getIntf();
+        ViewMenu( p_intf, menu, p_intf->p_sys->p_mi );
+    });
+}
+
+void QmlMenuBar::popupHelpMenu( QQuickItem* button )
+{
+    popupMenuCommon(button, [](QMenu* menu) {
+        HelpMenu(menu);
+    });
+}
+
+void QmlMenuBar::onMenuClosed()
+{
+    if (!m_openMenuOnHover)
+        emit menuClosed();
+}
+
 BaseMedialibMenu::BaseMedialibMenu(QObject* parent)
     : QObject(parent)
 {}
diff --git a/modules/gui/qt/menus/qml_menu_wrapper.hpp b/modules/gui/qt/menus/qml_menu_wrapper.hpp
index a3031a4a7b..d9a87ddb7a 100644
--- a/modules/gui/qt/menus/qml_menu_wrapper.hpp
+++ b/modules/gui/qt/menus/qml_menu_wrapper.hpp
@@ -22,7 +22,7 @@
 
 #include <QObject>
 #include <QPoint>
-
+#include <QQuickItem>
 #include "menus.hpp"
 
 class MediaLib;
@@ -65,6 +65,61 @@ private:
     QMenu* m_menu = nullptr;
 };
 
+//inherit VLCMenuBar so we can access menu creation functions
+class QmlMenuBarMenu;
+class QmlMenuBar : public VLCMenuBar
+{
+    Q_OBJECT
+    SIMPLE_MENU_PROPERTY(QmlMainContext*, ctx, nullptr)
+    SIMPLE_MENU_PROPERTY(QQuickItem*, menubar, nullptr)
+    SIMPLE_MENU_PROPERTY(bool, openMenuOnHover, false)
+
+public:
+    explicit QmlMenuBar(QObject *parent = nullptr);
+    ~QmlMenuBar();
+
+signals:
+    //navigate to the left(-1)/right(1) menu
+    void navigateMenu(int direction);
+
+    void menuClosed();
+
+public slots:
+    void popupMediaMenu( QQuickItem* button);
+    void popupPlaybackMenu( QQuickItem* button);
+    void popupAudioMenu( QQuickItem* button );
+    void popupVideoMenu( QQuickItem* button );
+    void popupSubtitleMenu( QQuickItem* button );
+    void popupToolsMenu( QQuickItem* button );
+    void popupViewMenu( QQuickItem* button );
+    void popupHelpMenu( QQuickItem* button );
+
+private slots:
+    void onMenuClosed();
+
+private:
+    typedef QMenu* (*CreateMenuFunc)();
+    void popupMenuCommon( QQuickItem* button, std::function<void(QMenu*)> createMenuFunc);
+    QMenu* m_menu = nullptr;
+    QQuickItem* m_button = nullptr;
+    friend class QmlMenuBarMenu;
+};
+
+//specialized QMenu for QmlMenuBar
+class QmlMenuBarMenu : public QMenu
+{
+    Q_OBJECT
+public:
+    QmlMenuBarMenu(QmlMenuBar* menubar, QWidget* parent = nullptr);
+    ~QmlMenuBarMenu();
+protected:
+    void mouseMoveEvent(QMouseEvent* mouseEvent) override;
+    void keyPressEvent(QKeyEvent *) override;
+    void keyReleaseEvent(QKeyEvent *) override;
+private:
+    QmlMenuBar* m_menubar = nullptr;
+};
+
 class BaseMedialibMenu : public QObject
 {
     Q_OBJECT
diff --git a/modules/gui/qt/vlc.qrc b/modules/gui/qt/vlc.qrc
index d9d9c8da66..1fc54805fe 100644
--- a/modules/gui/qt/vlc.qrc
+++ b/modules/gui/qt/vlc.qrc
@@ -287,6 +287,7 @@
         <file alias="MainDropdownMenu.qml">menus/qml/MainDropdownMenu.qml</file>
         <file alias="MainMenubar.qml">menus/qml/MainMenubar.qml</file>
         <file alias="MediaMenu.qml">menus/qml/MediaMenu.qml</file>
+        <file alias="Menubar.qml">menus/qml/Menubar.qml</file>
         <file alias="PlaybackMenu.qml">menus/qml/PlaybackMenu.qml</file>
         <file alias="SubtitleMenu.qml">menus/qml/SubtitleMenu.qml</file>
         <file alias="ToolsMenu.qml">menus/qml/ToolsMenu.qml</file>
-- 
2.25.1



More information about the vlc-devel mailing list