Warning, /maui/mauikit/src/controls.6/SelectionBar.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  *   Copyright 2018 Camilo Higuita <milo.h@aol.com>
0003  *
0004  *   This program is free software; you can redistribute it and/or modify
0005  *   it under the terms of the GNU Library General Public License as
0006  *   published by the Free Software Foundation; either version 2, or
0007  *   (at your option) any later version.
0008  *
0009  *   This program is distributed in the hope that it will be useful,
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *   GNU General Public License for more details
0013  *
0014  *   You should have received a copy of the GNU Library General Public
0015  *   License along with this program; if not, write to the
0016  *   Free Software Foundation, Inc.,
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0018  */
0019 
0020 import QtQuick
0021 import QtQuick.Controls
0022 import QtQuick.Layouts
0023 import QtQuick.Window
0024 
0025 import org.mauikit.controls 1.3 as Maui
0026 
0027 import Qt5Compat.GraphicalEffects
0028 
0029 /**
0030  * @inherit QtQuick.Item
0031  * @brief A bar to group selected items and a list of actions to perform to such selection.
0032  * 
0033  * <a href="https://doc.qt.io/qt-6/qml-qtquick-controls-item.html">This controls inherits from QQC2 Item, to checkout its inherited properties refer to the Qt Docs.</a>
0034  * 
0035  * The list of actions is represented by a set of buttons in a horizontal row layout.
0036  * @see actions
0037  * 
0038  * This control provides methods to append and query elements added to it. To add elements, it is necesary to map them,
0039  * so each item has an unique ID referred here as an URI.
0040  * @see append
0041  *  
0042  * 
0043  * @image html Misc/selectionbar.png
0044  * 
0045  * @section notes Notes 
0046  * 
0047  * By default it is hidden when the selection is empty.
0048  * @see count 
0049  * 
0050  * The SelectionBar is usually placed as the `footer` element of a Page.
0051  * This control is styled as a floating bar, so it can be placed on top of its parent contents; due to this behavior it is a good idea to pair it with a MauiKit Page as its footer, and set the Page property `floatingFooter: true`.
0052  * 
0053  * Most of the times this will be uses along with a browsing view, such as a ListBrowser or GridBrowser, which will list the items to be added or removed from the selection.
0054  *
0055  * Here we have three components that can be intertwined: Page, ListBrowser and the SelectionBar.
0056  * For the floating selection bar to not get on the way of the contents, the Page property `flickable` needs to be set to the flickable element of the browsing view, such as the ListBrowser::flickable.
0057  * @see ListBrowser::flickable
0058  * @see Page::flickable
0059  * 
0060  * Below you will find a more complete example of the functionality of these three controls out together.
0061  * 
0062  * @code
0063  * Maui.Page
0064  * {
0065  *    anchors.fill: parent
0066  * 
0067  *    Maui.Controls.showCSD: true
0068  * 
0069  *    floatingFooter: true
0070  *    flickable: _listBrowser.flickable //helps to keep the content from under the selection bar at the end.
0071  * 
0072  *    headBar.leftContent: Switch
0073  *    {
0074  *        text: "Single selection"
0075  *        checked: _selectionBar.singleSelection
0076  *        onToggled: _selectionBar.singleSelection = !_selectionBar.singleSelection
0077  *    }
0078  * 
0079  *    Maui.ListBrowser
0080  *    {
0081  *        id: _listBrowser
0082  * 
0083  *        anchors.fill: parent
0084  * 
0085  *        model: 60
0086  * 
0087  *        delegate: Maui.ListBrowserDelegate
0088  *        {
0089  *            id: _delegate
0090  * 
0091  *            property string id : index // we need an unique ID for the selectio nbar
0092  * 
0093  *            width: ListView.view.width
0094  * 
0095  *            label1.text: "An example delegate."
0096  *            label2.text: "The ID of this element is " + id
0097  * 
0098  *            iconSource: "folder"
0099  * 
0100  *            checkable: true
0101  * 
0102  *            Connections
0103  *            {
0104  *                target: _selectionBar
0105  *                function onUriRemoved(uri) //watch when a uri is removed from the selection bar
0106  *                {
0107  *                    if(uri == _delegate.id)
0108  *                    {
0109  *                        _delegate.checked = false
0110  *                    }
0111  *                }
0112  * 
0113  *                function onUriAdded(uri) //watch when an uri is successfully added and mark the delegate as checked
0114  *                {
0115  *                    if(uri == _delegate.id)
0116  *                    {
0117  *                        _delegate.checked = true
0118  *                    }
0119  *                }
0120  * 
0121  *                function onCleared() //watch when the selection has been cleared and uncheck all the delegates
0122  *                {
0123  *                    _delegate.checked = false
0124  *                }
0125  *            }
0126  * 
0127  *            onToggled: (state) =>
0128  *                        {
0129  *                            if(state)
0130  *                            {
0131  *                                _selectionBar.append(_delegate.id, ({'title': "Testing"}))
0132  *                            }else
0133  *                            {
0134  *                                _selectionBar.removeAtUri(_delegate.id)
0135  *                            }
0136  *                        } // when the item is toggled, we mark it as checked and add it to the selection bar, otherwise we unchecked it and remove it from selection.
0137  *        }
0138  *    }
0139  * 
0140  *    footer: Maui.SelectionBar
0141  *    {
0142  *        id: _selectionBar
0143  * 
0144  *        anchors.horizontalCenter: parent.horizontalCenter
0145  *        width: Math.min(parent.width-(Maui.Style.space.medium*2), implicitWidth)
0146  *        maxListHeight: root.height - (Maui.Style.contentMargins*2)
0147  * 
0148  *        Action
0149  *        {
0150  *            icon.name: "love"
0151  *            onTriggered: console.log(_selectionBar.getSelectedUrisString())
0152  *        }
0153  * 
0154  *        Action
0155  *        {
0156  *            icon.name: "folder"
0157  *            onTriggered: console.log(_selectionBar.contains("0"))
0158  *        }
0159  * 
0160  *        Action
0161  *        {
0162  *            icon.name: "list-add"
0163  *        }
0164  * 
0165  *        onExitClicked: clear()
0166  *    }
0167  * }
0168  *    @endcode
0169  * 
0170  * <a href="https://invent.kde.org/maui/mauikit/-/blob/qt6-2/examples/SelectionBar.qml">You can find a more complete example at this link.</a>
0171  * 
0172  */
0173 Item
0174 {
0175     id: control
0176     
0177     implicitHeight: _layout.implicitHeight + Maui.Style.space.big
0178     implicitWidth: _layout.implicitWidth
0179     
0180     /**
0181      * @brief Whether the bar is currently hidden. This is not the same as `visible`.
0182      * It is hidden when there is not items in the selection.
0183      */
0184     readonly property bool hidden : count === 0
0185     
0186     onHiddenChanged:
0187     {
0188         if(hidden && !singleSelection)
0189         {
0190             control.close()
0191         }else
0192         {
0193             control.open()
0194         }
0195     }
0196     
0197     visible: false
0198     focus: true
0199     Keys.enabled: true
0200     
0201     /**
0202      * @brief Default list of QQC2 Action; the actions are represented as buttons.
0203      * All of the actions listed in here will be visible, to hide some use the `hiddenActions` property.
0204      * @see hiddenActions
0205      */
0206     default property list<Action> actions
0207     
0208     /**
0209      * @brief List of action that won't be shown inside of the bar, but instead will always be hidden and listed in a dedicated overflow menu button.
0210      */
0211     property list<Action> hiddenActions
0212     
0213     /**
0214      * @brief The preferred display mode for the visible action buttons. 
0215      * By default this is set to `ToolButton.TextBesideIcon`.
0216      */
0217     property int display : ToolButton.TextBesideIcon
0218     
0219     /**
0220      * @brief The list of items can be displayed in a popup. This property defines the maximum height the popup list.
0221      * By default this is set to `400`.
0222      */
0223     property int maxListHeight : 400
0224     
0225     /**
0226      * @brief By default the selection bar was designed to be floating and thus has rounded border corners.
0227      * This property allows to change the border radius.
0228      * By default this is set to `Style.radiusV`.
0229      */
0230     property int radius: Maui.Style.radiusV
0231     
0232     /**
0233      * @brief Whether the control only accepts a single item.
0234      * If single selection is set to true then only a single item can be appended, if another item is added then it replaces the previous one.
0235      * By default this is set to `false`.
0236      **/
0237     property bool singleSelection: false
0238     
0239     /**
0240      * @brief The array list of the URIs associated to the selected items.
0241      * @see items
0242      * @property var SelectionBar::uris
0243      */
0244     readonly property alias uris: _private._uris
0245     
0246     /**
0247      * @brief The array list of the items selected.
0248      * @property var SelectionBar::items
0249      */
0250     readonly property alias items: _private._items
0251     
0252     /**
0253      * @brief Total amount of items selected.
0254      * @property int SelectionBar::count
0255      */
0256     readonly property alias count : _urisModel.count
0257     
0258     /**
0259      * @brief Delegate to be used in popup list.
0260      * By default this is set to use a MauiKit ListBrowserDelegate.
0261      * The model use to feed the popup list is a QQC2 ListModel, populated by the `item` passed as the argument to the `append` method.
0262      * @see append
0263      */
0264     property Component listDelegate: Maui.ListBrowserDelegate
0265     {
0266         id: delegate
0267         
0268         width: ListView.view.width
0269         
0270         Maui.Theme.backgroundColor: "transparent"
0271         Maui.Theme.textColor: control.Maui.Theme.textColor
0272         
0273         iconVisible: false
0274         label1.text: model.uri
0275         
0276         checkable: true
0277         checked: true
0278         onToggled: control.removeAtIndex(index)
0279         
0280         onClicked: control.itemClicked(index)
0281         onPressAndHold: control.itemPressAndHold(index)
0282     }
0283     
0284     /**
0285      * @brief Emitted when the selection is cleared either by the constrain of the single selection or by manually calling the clear method.
0286      */
0287     signal cleared()
0288     
0289     /**
0290      * @brief Emitted when close button is pressed or the Escape keyboard shortcut invoked.
0291      */
0292     signal exitClicked()
0293     
0294     /**
0295      * @brief Emitted when an item in the popup list view is clicked.
0296      * @paran index the index number of the item clicked.  Use the `itemAt` function to get to the item.
0297      * @see itemAt
0298      */
0299     signal itemClicked(int index)
0300     
0301     /**
0302      * @brief Emitted when an item in the popup list view is pressed for a long time.
0303      * @paran index the index number of the item clicked.  Use the `itemAt` function to get to the item.
0304      * @see itemAt
0305      */
0306     signal itemPressAndHold(int index)
0307     
0308     /**
0309      * @brief Emitted when an item is newly added to the selection.
0310      * @param item the item map passed to the `append` function.
0311      */
0312     signal itemAdded(var item)
0313     
0314     /**
0315      * @brief Emitted when an item has been removed from the selection.
0316      * @param item the item map passed to the `append` function.
0317      */
0318     signal itemRemoved(var item)
0319     
0320     /**
0321      * @brief Emitted when an item is newly added to the selection. This signal only sends the URI of the item.
0322      * @param uri the URI identifier
0323      */
0324     signal uriAdded(string uri)
0325     
0326     /**
0327      *@brief Emitted when an item has been removed from the selection. This signal only sends the URI of the item.
0328      * @param uri the URI identifier
0329      */
0330     signal uriRemoved(string uri)
0331     
0332     /**
0333      * @brief Emitted when an empty area of the selection bar has been clicked.
0334      * @param mouse the object with information of the event
0335      */
0336     signal clicked(var mouse)
0337     
0338      /**
0339      * @brief Emitted when an empty area of the selection bar has been right clicked.
0340      * @param mouse the object with information of the event
0341      */
0342     signal rightClicked(var mouse)
0343     
0344     /**
0345      * @brief Emitted when a group of URLs has been dropped onto the selection bar area.
0346      * @param uris the list of urls
0347      */
0348     signal urisDropped(var uris)
0349   
0350     QtObject
0351     {
0352         id: _private
0353         property var _uris : []
0354         property var _items : []
0355     }
0356     
0357     ListModel
0358     {
0359         id: _urisModel
0360     }
0361     
0362     Loader
0363     {
0364         id: _loader
0365         active: control.visible
0366     }
0367     
0368     Component
0369     {
0370         id: _listContainerComponent
0371         
0372         Popup
0373         {
0374             parent: control
0375             closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
0376             modal: true
0377             height: Math.min(Math.min(400, control.maxListHeight), selectionList.contentHeight) + Maui.Style.space.big
0378             width: Math.min(600, control.Window.window.width*widthHint)
0379             
0380             Maui.ListBrowser
0381             {
0382                 id: selectionList
0383                 
0384                 anchors.fill: parent
0385                 model: _urisModel
0386                 
0387                 delegate: control.listDelegate
0388             }
0389         }
0390     }
0391     
0392     ParallelAnimation
0393     {
0394         id: _openAnimation
0395         NumberAnimation
0396         {
0397             target: _layout
0398             property: "y"
0399             from: _layout.height
0400             to: Maui.Style.space.big/2
0401             duration: Maui.Style.units.longDuration*1
0402             easing.type: Easing.OutBack
0403             
0404         }
0405         
0406         NumberAnimation
0407         {
0408             target: _layout
0409             property: "scale"
0410             from: 0.5
0411             to: 1
0412             duration: Maui.Style.units.longDuration*1
0413             easing.type: Easing.OutQuad
0414         }
0415     }
0416     
0417     ParallelAnimation
0418     {
0419         id: _closeAnimation
0420         NumberAnimation
0421         {
0422             target: _layout
0423             property: "y"
0424             from: Maui.Style.space.big/2
0425             to: _layout.height
0426             duration: Maui.Style.units.longDuration*1
0427             easing.type: Easing.InBack
0428             
0429         }
0430         
0431         NumberAnimation
0432         {
0433             target: _layout
0434             property: "scale"
0435             from: 1
0436             to: 0.5
0437             duration: Maui.Style.units.longDuration*1
0438             easing.type: Easing.InQuad
0439             
0440         }
0441         
0442         onFinished: control.visible = false
0443     }
0444     
0445     Maui.ToolBar
0446     {
0447         id: _layout
0448         width: control.width
0449         padding: Maui.Style.space.medium
0450         forceCenterMiddleContent: false
0451             position: ToolBar.Footer
0452             
0453             leftContent: Maui.Chip
0454             {
0455                 id: _counter
0456                 visible: !control.singleSelection
0457                 text: control.count
0458                 font.pointSize: Maui.Style.fontSizes.big
0459                 Maui.Theme.colorSet: control.Maui.Theme.colorSet
0460                 Maui.Theme.backgroundColor: _loader.item && _loader.item.visible ?
0461                 Maui.Theme.highlightColor : Qt.tint(control.Maui.Theme.textColor, Qt.rgba(control.Maui.Theme.backgroundColor.r, control.Maui.Theme.backgroundColor.g, control.Maui.Theme.backgroundColor.b, 0.9))
0462                 
0463                 
0464                 
0465                 Maui.Rectangle
0466                 {
0467                     opacity: 0.3
0468                     anchors.fill: parent
0469                     anchors.margins: 4
0470                     visible: _counter.hovered
0471                     color: "transparent"
0472                     borderColor: "white"
0473                     solidBorder: false
0474                 }
0475                 
0476                 MouseArea
0477                 {
0478                     id: _mouseArea
0479                     anchors.fill: parent
0480                     propagateComposedEvents: true
0481                     property int startX
0482                     property int startY
0483                     Drag.active: drag.active
0484                     Drag.hotSpot.x: 0
0485                     Drag.hotSpot.y: 0
0486                     Drag.dragType: Drag.Automatic
0487                     Drag.supportedActions: Qt.CopyAction
0488                     Drag.keys: ["text/plain","text/uri-list"]
0489                     
0490                     onPressed: (mouse) =>
0491                     {
0492                         if( mouse.source !== Qt.MouseEventSynthesizedByQt)
0493                         {
0494                             drag.target = _counter
0495                             _counter.grabToImage(function(result)
0496                             {
0497                                 _mouseArea.Drag.imageSource = result.url
0498                             })
0499                             
0500                             _mouseArea.Drag.mimeData = { "text/uri-list": control.uris.join("\n")}
0501                             
0502                             startX = _counter.x
0503                             startY = _counter.y
0504                             
0505                         }else mouse.accepted = false
0506                     }
0507                     
0508                     onReleased :
0509                     {
0510                         _counter.x = startX
0511                         _counter.y = startY
0512                     }
0513                     
0514                     
0515                     onClicked:
0516                     {
0517                         if(!_loader.item)
0518                         {
0519                             _loader.sourceComponent = _listContainerComponent
0520                         }
0521                         _loader.item.open()
0522                         console.log("Opening list")
0523                     }
0524                     
0525                 }
0526             }
0527             
0528             Repeater
0529             {
0530                 model: control.actions
0531                 
0532                 ToolButton
0533                 {
0534                     action: modelData
0535                     display: control.display
0536                     ToolTip.delay: 1000
0537                     ToolTip.timeout: 5000
0538                     ToolTip.visible: hovered || pressed && action.text
0539                     ToolTip.text: action.text
0540                 }
0541             }
0542             
0543             Maui.ToolButtonMenu
0544             {
0545                 icon.name: "overflow-menu"
0546                 visible: control.hiddenActions.length > 0
0547                 Repeater
0548                 {
0549                     model:  control.hiddenActions
0550                     delegate: MenuItem{ action: modelData}
0551                 }
0552             }
0553             
0554             rightContent: Maui.CloseButton
0555             {
0556                 onClicked:
0557                 {
0558                     control.exitClicked()
0559                 }
0560             }
0561             
0562             background: Rectangle
0563             {
0564                 id: bg
0565                 color: Maui.Theme.backgroundColor
0566                 radius: control.radius
0567                 
0568                 Behavior on color
0569                 {
0570                     Maui.ColorTransition {}
0571                 }
0572                 
0573                 MouseArea
0574                 {
0575                     anchors.fill: parent
0576                     acceptedButtons: Qt.RightButton | Qt.LeftButton
0577                     propagateComposedEvents: false
0578                     preventStealing: true
0579                     
0580                     onClicked: (mouse) =>
0581                     {
0582                         if(!Maui.Handy.isMobile && mouse.button === Qt.RightButton)
0583                             control.rightClicked(mouse)
0584                             else
0585                                 control.clicked(mouse)
0586                     }
0587                     
0588                     onPressAndHold : (mouse) =>
0589                     {
0590                         if(Maui.Handy.isMobile)
0591                             control.rightClicked(mouse)
0592                     }
0593                 }
0594                 
0595                 Maui.Rectangle
0596                 {
0597                     opacity: 0.2
0598                     anchors.fill: parent
0599                     anchors.margins: 4
0600                     visible: _dropArea.containsDrag
0601                     color: "transparent"
0602                     borderColor: Maui.Theme.textColor
0603                     solidBorder: false
0604                 }
0605                 
0606                 DropArea
0607                 {
0608                     id: _dropArea
0609                     anchors.fill: parent
0610                     onDropped:
0611                     {
0612                         control.urisDropped(drop.urls)
0613                     }
0614                 }
0615                 
0616                 layer.enabled: true
0617                 layer.effect: DropShadow
0618                 {
0619                     cached: true
0620                     horizontalOffset: 0
0621                     verticalOffset: 0
0622                     radius: 8.0
0623                     samples: 16
0624                     color:  "#80000000"
0625                     smooth: true
0626                 }
0627             }
0628     }
0629     
0630     Keys.onEscapePressed:
0631     {
0632         control.exitClicked();
0633     }
0634     
0635     Keys.onBackPressed:
0636     {
0637         control.exitClicked();
0638         event.accepted = true
0639     }
0640     
0641     /**
0642      * @brief Removes all the items from the selection.
0643      * Triggers the `cleared` signal.
0644      * @see cleared
0645      */
0646     function clear()
0647     {
0648         _private._uris = []
0649         _private._items = []
0650         _urisModel.clear()
0651         control.cleared()
0652     }
0653     
0654     /**
0655      * @brief Returns an item at the given index
0656      * @param index the index number of the item. 
0657      * @note Note that this is the index of the internal list on how items were added, and not the original index of the source list used to make the selection.
0658      * @return the requested item/map if it is found, otherwise null.
0659      */
0660     function itemAt(index)
0661     {
0662         if(index < 0 ||  index > control.count)
0663         {
0664             return
0665         }
0666         return _urisModel.get(index)
0667     }
0668     
0669     /**
0670      * @brief Remove a single item at the given index
0671      * @param index index of the item in the selection list
0672      */
0673     function removeAtIndex(index)
0674     {
0675         if(index < 0)
0676         {
0677             return
0678         }
0679         
0680         const item = _urisModel.get(index)
0681         const uri = item.uri
0682         
0683         if(contains(uri))
0684         {
0685             _private._uris.splice(index, 1)
0686             _private._items.splice(index, 1)
0687             _urisModel.remove(index)
0688             control.itemRemoved(item)
0689             control.uriRemoved(uri)
0690         }
0691     }
0692     
0693     /**
0694      * @brief Removes an item from the selection at the given URI
0695      * @param uri the URI used to append the item
0696      */
0697     function removeAtUri(uri)
0698     {
0699         removeAtIndex(indexOf(uri))
0700     }
0701     
0702     /**
0703      * @brief Returns the index of an item in the selection given its URI
0704      * @param uri the URI used to append the item
0705      * @return the index number of the found item, otherwise `-1`
0706      * 
0707      */
0708     function indexOf(uri)
0709     {
0710         return _private._uris.indexOf(uri)
0711     }
0712     
0713     /**
0714      * @brief Appends a new item to the selection associated to the given URI
0715      * @param uri the URI to be associated with the item
0716      * @param item a map to be used to represent the item in the model. For example a valid item map would be: `({'title': "A title", 'icon': "love"})`
0717      */
0718     function append(uri, item)
0719     {
0720         if(control.singleSelection)
0721         {
0722             clear()
0723         }
0724         
0725         if(!contains(uri) || control.singleSelection)
0726         {
0727             _private._items.push(item)
0728             _private._uris.push(uri)
0729             
0730             item.uri = uri
0731             _urisModel.append(item)
0732             control.itemAdded(item)
0733             control.uriAdded(uri)
0734         }
0735     }
0736     
0737     /**
0738      * @brief Returns a single string with all the URIs separated by a comma.
0739      */
0740     function getSelectedUrisString()
0741     {
0742         return String(""+_private._uris.join(","))
0743     }
0744     
0745     /**
0746      * @brief Returns whether the selection contains an item associated to the given URI.
0747      * @param uri the URI used to append the item
0748      */
0749     function contains(uri)
0750     {
0751         return _private._uris.includes(uri)
0752     }
0753     
0754     /**
0755      * @brief Forces to open the selection bar, if hidden.
0756      */
0757     function open()
0758     {
0759         if(control.visible)
0760         {
0761             return;
0762         }
0763         
0764         control.visible = true
0765         _openAnimation.start()
0766     }
0767     
0768     /**
0769      * @brief Forces to close the selection bar, if visible.
0770      */
0771     function close()
0772     {
0773         if(!control.visible)
0774         {
0775             return
0776         }
0777         _closeAnimation.start()
0778     }
0779 }