[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