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 }