[vlc-commits] [Git][videolan/vlc][master] 2 commits: qt: implement SortMenu

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Tue Jul 13 08:30:29 UTC 2021



Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
f12f7fa1 by Prince Gupta at 2021-07-13T08:19:00+00:00
qt: implement SortMenu

- - - - -
3e466a6d by Prince Gupta at 2021-07-13T08:19:00+00:00
qml/SortControl: use native menu

- - - - -


6 changed files:

- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/maininterface/qml/BannerSources.qml
- modules/gui/qt/menus/qml_menu_wrapper.cpp
- modules/gui/qt/menus/qml_menu_wrapper.hpp
- modules/gui/qt/playlist/qml/PlaylistToolbar.qml
- modules/gui/qt/widgets/qml/SortControl.qml


Changes:

=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -252,6 +252,7 @@ void MainUI::registerQMLTypes()
 
     qRegisterMetaType<QmlMainContext*>();
     qmlRegisterType<StringListMenu>( "org.videolan.vlc", 0, 1, "StringListMenu" );
+    qmlRegisterType<SortMenu>( "org.videolan.vlc", 0, 1, "SortMenu" );
     qmlRegisterType<QmlGlobalMenu>( "org.videolan.vlc", 0, 1, "QmlGlobalMenu" );
     qmlRegisterType<QmlMenuBar>( "org.videolan.vlc", 0, 1, "QmlMenuBar" );
     qmlRegisterType<NetworkMediaContextMenu>( "org.videolan.vlc", 0, 1, "NetworkMediaContextMenu" );


=====================================
modules/gui/qt/maininterface/qml/BannerSources.qml
=====================================
@@ -251,8 +251,6 @@ FocusScope {
                             height: VLCStyle.bannerButton_height
                             iconSize: VLCStyle.banner_icon_size
 
-                            popupAlignment: Qt.AlignLeft | Qt.AlignBottom
-
                             visible: root.sortModel !== undefined && root.sortModel.length > 1
                             enabled: visible
 


=====================================
modules/gui/qt/menus/qml_menu_wrapper.cpp
=====================================
@@ -38,6 +38,35 @@
 
 #include <QSignalMapper>
 
+namespace
+{
+    QIcon sortIcon(QWidget *widget, int order)
+    {
+        assert(order == Qt::AscendingOrder || order == Qt::DescendingOrder);
+
+        QStyleOptionHeader headerOption;
+        headerOption.init(widget);
+        headerOption.sortIndicator = (order == Qt::AscendingOrder)
+                ? QStyleOptionHeader::SortDown
+                : QStyleOptionHeader::SortUp;
+
+        QStyle *style = qApp->style();
+        int arrowsize = style->pixelMetric(QStyle::PM_HeaderMarkSize, &headerOption, widget);
+        if (arrowsize <= 0)
+            arrowsize = 32;
+
+        headerOption.rect = QRect(0, 0, arrowsize, arrowsize);
+        QPixmap arrow(arrowsize, arrowsize);
+        arrow.fill(Qt::transparent);
+
+        {
+            QPainter arrowPainter(&arrow);
+            style->drawPrimitive(QStyle::PE_IndicatorHeaderArrow, &headerOption, &arrowPainter, widget);
+        }
+
+        return QIcon(arrow);
+    }
+}
 
 static inline void addSubMenu( QMenu *func, QString title, QMenu *bar ) {
     func->setTitle( title );
@@ -62,6 +91,55 @@ void StringListMenu::popup(const QPoint &point, const QVariantList &stringList)
     m->popup(point);
 }
 
+SortMenu::~SortMenu()
+{
+    if (m_menu)
+        delete m_menu;
+}
+
+void SortMenu::popup(const QPoint &point, const bool popupAbovePoint, const QVariantList &model)
+{
+    if (m_menu)
+        delete m_menu;
+
+    m_menu = new QMenu;
+
+    // model => [{text: "", checked: <bool>, order: <sort order> if checked else <invalid>}...]
+    for (int i = 0; i != model.size(); ++i)
+    {
+        const auto obj = model[i].toMap();
+
+        auto action = m_menu->addAction(obj.value("text").toString());
+        action->setCheckable(true);
+
+        const bool checked = obj.value("checked").toBool();
+        action->setChecked(checked);
+
+        if (checked)
+            action->setIcon(sortIcon(m_menu, obj.value("order").toInt()));
+
+        connect(action, &QAction::triggered, this, [this, i]()
+        {
+            emit selected(i);
+        });
+    }
+
+    // m_menu->height() returns invalid height until initial popup call
+    // so in case of 'popupAbovePoint', first show the menu and then reposition it
+    m_menu->popup(point);
+    if (popupAbovePoint)
+    {
+        // use 'popup' instead of 'move' so that menu can reposition itself if it's parts are hidden
+        m_menu->popup(QPoint(point.x(), point.y() - m_menu->height()));
+    }
+}
+
+void SortMenu::close()
+{
+    if (m_menu)
+        m_menu->close();
+}
+
 QmlGlobalMenu::QmlGlobalMenu(QObject *parent)
     : VLCMenuBar(parent)
 {


=====================================
modules/gui/qt/menus/qml_menu_wrapper.hpp
=====================================
@@ -69,6 +69,27 @@ signals:
 };
 
 
+class SortMenu : public QObject
+{
+    Q_OBJECT
+
+public:
+    using QObject::QObject;
+
+    ~SortMenu();
+
+    Q_INVOKABLE void popup(const QPoint &point, bool popupAbovePoint, const QVariantList &model);
+
+    Q_INVOKABLE void close();
+
+signals:
+    void selected(int index);
+
+private:
+    QMenu *m_menu = nullptr;
+};
+
+
 //inherit VLCMenuBar so we can access menu creation functions
 class QmlGlobalMenu : public VLCMenuBar
 {


=====================================
modules/gui/qt/playlist/qml/PlaylistToolbar.qml
=====================================
@@ -78,7 +78,8 @@ RowLayout {
             anchors.centerIn: parent
 
             enabled: mainPlaylistController.count > 1
-            popupAlignment: Qt.AlignRight | Qt.AlignTop
+
+            popupAbove: true
 
             focusPolicy: Qt.NoFocus
 


=====================================
modules/gui/qt/widgets/qml/SortControl.qml
=====================================
@@ -35,14 +35,15 @@ FocusScope {
     implicitWidth: button.implicitWidth
     implicitHeight: button.implicitHeight
 
-    property alias model: listView.model
+    property var model: []
     property string textRole
     property string criteriaRole
     // provided for convenience:
     property alias titleRole: root.textRole
     property alias keyRole: root.criteriaRole
 
-    property int popupAlignment: Qt.AlignRight | Qt.AlignBottom
+    property bool popupAbove: false
+
     property real listWidth: VLCStyle.widthSortBox
     property alias focusPolicy: button.focusPolicy
     property alias iconSize: button.size
@@ -62,12 +63,12 @@ FocusScope {
 
     onVisibleChanged: {
         if (!visible)
-            popup._close()
+            popup.close()
     }
 
     onEnabledChanged: {
         if (!enabled)
-            popup._close()
+            popup.close()
     }
 
     Widgets.IconToolButton {
@@ -84,246 +85,44 @@ FocusScope {
 
         focus: true
 
-        onClicked: {
-            if (popup.visible && !closeAnimation.running)
-                popup._close()
-            else
-                popup._open()
-        }
+        onClicked: popup.show()
 
         Navigation.parentItem: root
         Keys.priority: Keys.AfterItem
         Keys.onPressed: Navigation.defaultKeyAction(event)
     }
 
-    Popup {
-        id: popup
-
-        closePolicy: Popup.NoAutoClose
-        y: (popupAlignment & Qt.AlignBottom) ? (root.height) : -(height)
-        x: (popupAlignment & Qt.AlignRight) ? (button.width - width) : 0
-
-        width: listWidth
-
-        padding: bgRect.border.width
-
-        clip: true
-
-        height: 0
-
-        NumberAnimation {
-            id: openAnimation
-            target: popup
-            property: "height"
-            duration: 125
-            easing.type: Easing.InOutSine
-            to: popup.implicitHeight
-
-            onStarted: closeAnimation.stop()
-        }
-
-        NumberAnimation {
-            id: closeAnimation
-            target: popup
-            property: "height"
-            duration: 125
-            easing.type: Easing.InOutSine
-            to: 0
-
-            onStarted: openAnimation.stop()
-            onStopped: if (!openAnimation.running) popup.close()
-        }
-
-        function _open() {
-            if (!popup.visible)
-                popup.open()
-            openAnimation.start()
-        }
-
-        function _close() {
-            closeAnimation.start()
-        }
-
-        onOpened: {
-            button.highlighted = true
-            listView.forceActiveFocus()
-        }
-
-        onClosed: {
-            popup.height = 0
-
-            button.highlighted = false
-
-            if (button.focusPolicy !== Qt.NoFocus)
-                button.forceActiveFocus()
-        }
-
-        contentItem: ListView {
-            id: listView
-
-            implicitHeight: contentHeight
-
-            onActiveFocusChanged: {
-                // since Popup.CloseOnReleaseOutside closePolicy is limited to
-                // modal popups, this is an alternative way of closing the popup
-                // when the focus is lost
-                if (!activeFocus && !button.activeFocus)
-                    popup._close()
-            }
-
-            ScrollIndicator.vertical: ScrollIndicator { }
-
-            property bool containsMouse: false
-
-            delegate: ItemDelegate {
-                id: itemDelegate
-
-                width: parent.width
-
-                readonly property var delegateSortKey: modelData[root.criteriaRole]
-
-                readonly property bool isActive: (delegateSortKey === sortKey)
-
-                background: Widgets.AnimatedBackground {
-                    active: itemDelegate.activeFocus
-
-                    // NOTE: We don't want animations here, because it looks sluggish.
-                    animationDuration: 0
-
-                    backgroundColor: (closeAnimation.running === false && itemDelegate.hovered)
-                                     ? VLCStyle.colors.listHover
-                                     : "transparent"
-                }
-
-                onHoveredChanged: {
-                    listView.containsMouse = hovered
-                    itemDelegate.forceActiveFocus()
+    SortMenu {
+       id: popup
+
+        function show() {
+            var model = root.model.map(function(modelData) {
+                var checked = modelData[root.criteriaRole] === sortKey
+                var order = checked ? root.sortOrder : undefined
+                return {
+                    "text": modelData[root.textRole],
+                    "checked": checked,
+                    "order": order
                 }
+            })
 
-                contentItem: Item {
-                    implicitHeight: itemRow.height
-                    width: itemDelegate.width
+            var point
 
-                    RowLayout {
-                        id: itemRow
-
-                        anchors.left: parent.left
-                        anchors.right: parent.right
-
-                        anchors {
-                            leftMargin: VLCStyle.margin_xxsmall
-                            rightMargin: VLCStyle.margin_xxsmall
-                        }
-
-                        MenuCaption {
-                            Layout.preferredHeight: itemText.implicitHeight
-                            Layout.preferredWidth: tickMetric.width
-
-                            horizontalAlignment: Text.AlignHCenter
-
-                            text: isActive ? tickMetric.text : ""
-
-                            color: colors.buttonText
-
-                            TextMetrics {
-                                id: tickMetric
-                                text: "✓"
-                            }
-                        }
-
-                        MenuCaption {
-                            Layout.fillWidth: true
-                            Layout.leftMargin: VLCStyle.margin_xxsmall
-
-                            id: itemText
-                            text: modelData[root.textRole]
-
-                            color: colors.buttonText
-                        }
-
-                        MenuCaption {
-                            Layout.preferredHeight: itemText.implicitHeight
-
-                            text: (sortOrder === Qt.AscendingOrder ? "↓" : "↑")
-                            visible: isActive
-
-                            color: colors.buttonText
-                        }
-                    }
-                }
-
-                onClicked: {
-                    if (root.sortKey !== delegateSortKey) {
-                        root.sortSelected(delegateSortKey)
-                        root.sortOrderSelected(Qt.AscendingOrder)
-                    }
-                    else {
-                        root.sortOrderSelected(root.sortOrder === Qt.AscendingOrder ? Qt.DescendingOrder : Qt.AscendingOrder)
-                    }
-
-                    popup.close()
-                }
-            }
-        }
-
-        background: Rectangle {
-            id: bgRect
-
-            border.width: VLCStyle.dp(1)
-
-            // FIXME: We might want another color for this.
-            border.color: VLCStyle.colors.text
-
-            Loader {
-                id: effectLoader
-
-                anchors.fill: parent
-                anchors.margins: VLCStyle.dp(1)
-
-                asynchronous: true
-
-                Component {
-                    id: frostedGlassEffect
-
-                    Widgets.FrostedGlassEffect {
-                        source: g_root
-
-                        // since Popup is not an Item, we can not directly map its position
-                        // to the source item. Instead, we can use root because popup's
-                        // position is relative to root's position.
-                        // This method unfortunately causes issues when source item is resized.
-                        // But in that case, we reload the effectLoader to redraw the effect.
-                        property point popupMappedPos: g_root.mapFromItem(root, popup.x, popup.y)
-                        sourceRect: Qt.rect(popupMappedPos.x, popupMappedPos.y, width, height)
-
-                        tint: colors.bg
-                        tintStrength: 0.3
-                    }
-                }
-
-                sourceComponent: frostedGlassEffect
-
-                function reload() {
-                    if (status != Loader.Ready)
-                        return
+            if (root.popupAbove)
+                point = root.mapToGlobal(0, - VLCStyle.margin_xxsmall)
+            else
+                point = root.mapToGlobal(0, root.height + VLCStyle.margin_xxsmall)
 
-                    sourceComponent = undefined
-                    sourceComponent = frostedGlassEffect
-                }
-            }
+            popup.popup(point, root.popupAbove, model)
         }
 
-        Connections {
-            target: g_root
-
-            enabled: popup.visible
-
-            onWidthChanged: {
-                effectLoader.reload()
-            }
-
-            onHeightChanged: {
-                effectLoader.reload()
+        onSelected: {
+            var selectedSortKey = root.model[index][root.criteriaRole]
+            if (root.sortKey !== selectedSortKey) {
+                root.sortSelected(selectedSortKey)
+                root.sortOrderSelected(Qt.AscendingOrder)
+            } else {
+                root.sortOrderSelected(root.sortOrder === Qt.AscendingOrder ? Qt.DescendingOrder : Qt.AscendingOrder)
             }
         }
     }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e469c589a7c1ec61ff38d746ff8ef073ac33b9c3...3e466a6d25ecad826dea08ae34d10f17743331ff

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e469c589a7c1ec61ff38d746ff8ef073ac33b9c3...3e466a6d25ecad826dea08ae34d10f17743331ff
You're receiving this email because of your account on code.videolan.org.




More information about the vlc-commits mailing list