MauiKit Controls

SelectionBar.qml
1/*
2 * Copyright 2018 Camilo Higuita <milo.h@aol.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License as
6 * published by the Free Software Foundation; either version 2, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20import QtQuick
21import QtQuick.Controls
22import QtQuick.Layouts
23import QtQuick.Window
24
25import org.mauikit.controls 1.3 as Maui
26
27import Qt5Compat.GraphicalEffects
28
29/**
30 * @inherit QtQuick.Item
31 * @brief A bar to group selected items and a list of actions to perform to such selection.
32 *
33 * <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>
34 *
35 * The list of actions is represented by a set of buttons in a horizontal row layout.
36 * @see actions
37 *
38 * This control provides methods to append and query elements added to it. To add elements, it is necesary to map them,
39 * so each item has an unique ID referred here as an URI.
40 * @see append
41 *
42 *
43 * @image html Misc/selectionbar.png
44 *
45 * @section notes Notes
46 *
47 * By default it is hidden when the selection is empty.
48 * @see count
49 *
50 * The SelectionBar is usually placed as the `footer` element of a Page.
51 * 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`.
52 *
53 * 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.
54 *
55 * Here we have three components that can be intertwined: Page, ListBrowser and the SelectionBar.
56 * 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.
57 * @see ListBrowser::flickable
58 * @see Page::flickable
59 *
60 * Below you will find a more complete example of the functionality of these three controls out together.
61 *
62 * @code
63 * Maui.Page
64 * {
65 * anchors.fill: parent
66 *
67 * Maui.Controls.showCSD: true
68 *
69 * floatingFooter: true
70 * flickable: _listBrowser.flickable //helps to keep the content from under the selection bar at the end.
71 *
72 * headBar.leftContent: Switch
73 * {
74 * text: "Single selection"
75 * checked: _selectionBar.singleSelection
76 * onToggled: _selectionBar.singleSelection = !_selectionBar.singleSelection
77 * }
78 *
79 * Maui.ListBrowser
80 * {
81 * id: _listBrowser
82 *
83 * anchors.fill: parent
84 *
85 * model: 60
86 *
87 * delegate: Maui.ListBrowserDelegate
88 * {
89 * id: _delegate
90 *
91 * property string id : index // we need an unique ID for the selectio nbar
92 *
93 * width: ListView.view.width
94 *
95 * label1.text: "An example delegate."
96 * label2.text: "The ID of this element is " + id
97 *
98 * iconSource: "folder"
99 *
100 * checkable: true
101 *
102 * Connections
103 * {
104 * target: _selectionBar
105 * function onUriRemoved(uri) //watch when a uri is removed from the selection bar
106 * {
107 * if(uri == _delegate.id)
108 * {
109 * _delegate.checked = false
110 * }
111 * }
112 *
113 * function onUriAdded(uri) //watch when an uri is successfully added and mark the delegate as checked
114 * {
115 * if(uri == _delegate.id)
116 * {
117 * _delegate.checked = true
118 * }
119 * }
120 *
121 * function onCleared() //watch when the selection has been cleared and uncheck all the delegates
122 * {
123 * _delegate.checked = false
124 * }
125 * }
126 *
127 * onToggled: (state) =>
128 * {
129 * if(state)
130 * {
131 * _selectionBar.append(_delegate.id, ({'title': "Testing"}))
132 * }else
133 * {
134 * _selectionBar.removeAtUri(_delegate.id)
135 * }
136 * } // 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.
137 * }
138 * }
139 *
140 * footer: Maui.SelectionBar
141 * {
142 * id: _selectionBar
143 *
144 * anchors.horizontalCenter: parent.horizontalCenter
145 * width: Math.min(parent.width-(Maui.Style.space.medium*2), implicitWidth)
146 * maxListHeight: root.height - (Maui.Style.contentMargins*2)
147 *
148 * Action
149 * {
150 * icon.name: "love"
151 * onTriggered: console.log(_selectionBar.getSelectedUrisString())
152 * }
153 *
154 * Action
155 * {
156 * icon.name: "folder"
157 * onTriggered: console.log(_selectionBar.contains("0"))
158 * }
159 *
160 * Action
161 * {
162 * icon.name: "list-add"
163 * }
164 *
165 * onExitClicked: clear()
166 * }
167 * }
168 * @endcode
169 *
170 * <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>
171 *
172 */
173Item
174{
175 id: control
176
177 Maui.Theme.inherit: false
178 Maui.Theme.colorSet: Maui.Theme.Complementary
179
180 implicitHeight: _layout.implicitHeight + Maui.Style.space.big
181 implicitWidth: _layout.implicitWidth
182
183 /**
184 * @brief Whether the bar is currently hidden. This is not the same as `visible`.
185 * It is hidden when there is not items in the selection.
186 */
187 readonly property bool hidden : count === 0
188
189 onHiddenChanged:
190 {
191 if(hidden && !singleSelection)
192 {
193 control.close()
194 }else
195 {
196 control.open()
198 }
199
200 visible: false
201 focus: true
202 Keys.enabled: true
203
204 /**
205 * @brief Default list of QQC2 Action; the actions are represented as buttons.
206 * All of the actions listed in here will be visible, to hide some use the `hiddenActions` property.
207 * @see hiddenActions
208 */
209 default property list<Action> actions
210
211 /**
212 * @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.
213 */
214 property list<Action> hiddenActions
215
216 /**
217 * @brief The preferred display mode for the visible action buttons.
218 * By default this is set to `ToolButton.TextBesideIcon`.
219 */
220 property int display : ToolButton.TextBesideIcon
221
222 /**
223 * @brief The list of items can be displayed in a popup. This property defines the maximum height the popup list.
224 * By default this is set to `400`.
225 */
226 property int maxListHeight : 400
227
228 /**
229 * @brief By default the selection bar was designed to be floating and thus has rounded border corners.
230 * This property allows to change the border radius.
231 * By default this is set to `Style.radiusV`.
232 */
233 property int radius: Maui.Style.radiusV
234
235 /**
236 * @brief Whether the control only accepts a single item.
237 * 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.
238 * By default this is set to `false`.
239 **/
240 property bool singleSelection: false
241
242 /**
243 * @brief The array list of the URIs associated to the selected items.
244 * @see items
245 * @property var SelectionBar::uris
246 */
247 readonly property alias uris: _private._uris
248
249 /**
250 * @brief The array list of the items selected.
251 * @property var SelectionBar::items
252 */
253 readonly property alias items: _private._items
254
255 /**
256 * @brief Total amount of items selected.
257 * @property int SelectionBar::count
258 */
259 readonly property alias count : _urisModel.count
260
261 /**
262 * @brief Delegate to be used in popup list.
263 * By default this is set to use a MauiKit ListBrowserDelegate.
264 * The model use to feed the popup list is a QQC2 ListModel, populated by the `item` passed as the argument to the `append` method.
265 * @see append
266 */
267 property Component listDelegate: Maui.ListBrowserDelegate
268 {
269 id: delegate
271 width: ListView.view.width
272
273 Maui.Theme.backgroundColor: "transparent"
274 Maui.Theme.textColor: control.Maui.Theme.textColor
276 iconVisible: false
277 label1.text: model.uri
278
279 checkable: true
280 checked: true
281 onToggled: control.removeAtIndex(index)
282
283 onClicked: control.itemClicked(index)
284 onPressAndHold: control.itemPressAndHold(index)
286
287 /**
288 * @brief Emitted when the selection is cleared either by the constrain of the single selection or by manually calling the clear method.
289 */
290 signal cleared()
291
292 /**
293 * @brief Emitted when close button is pressed or the Escape keyboard shortcut invoked.
294 */
295 signal exitClicked()
296
297 /**
298 * @brief Emitted when an item in the popup list view is clicked.
299 * @paran index the index number of the item clicked. Use the `itemAt` function to get to the item.
300 * @see itemAt
301 */
302 signal itemClicked(int index)
303
304 /**
305 * @brief Emitted when an item in the popup list view is pressed for a long time.
306 * @paran index the index number of the item clicked. Use the `itemAt` function to get to the item.
307 * @see itemAt
308 */
309 signal itemPressAndHold(int index)
310
311 /**
312 * @brief Emitted when an item is newly added to the selection.
313 * @param item the item map passed to the `append` function.
314 */
315 signal itemAdded(var item)
316
317 /**
318 * @brief Emitted when an item has been removed from the selection.
319 * @param item the item map passed to the `append` function.
320 */
321 signal itemRemoved(var item)
322
323 /**
324 * @brief Emitted when an item is newly added to the selection. This signal only sends the URI of the item.
325 * @param uri the URI identifier
326 */
327 signal uriAdded(string uri)
328
329 /**
330 * @brief Emitted when an item has been removed from the selection. This signal only sends the URI of the item.
331 * @param uri the URI identifier
332 */
333 signal uriRemoved(string uri)
334
335 /**
336 * @brief Emitted when an empty area of the selection bar has been clicked.
337 * @param mouse the object with information of the event
338 */
339 signal clicked(var mouse)
340
341 /**
342 * @brief Emitted when an empty area of the selection bar has been right clicked.
343 * @param mouse the object with information of the event
344 */
345 signal rightClicked(var mouse)
346
347 /**
348 * @brief Emitted when a group of URLs has been dropped onto the selection bar area.
349 * @param uris the list of urls
350 */
351 signal urisDropped(var uris)
353 QtObject
354 {
355 id: _private
356 property var _uris : []
357 property var _items : []
358 }
359
360 ListModel
362 id: _urisModel
363 }
364
365 Loader
366 {
367 id: _loader
368 active: control.visible
369 }
370
371 Component
372 {
373 id: _listContainerComponent
374
375 Popup
376 {
377 parent: control
378 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
379 modal: true
380 height: Math.min(Math.min(400, control.maxListHeight), selectionList.contentHeight) + Maui.Style.space.big
381 width: Math.min(600, control.Window.window.width*widthHint)
382
383 Maui.ListBrowser
384 {
385 id: selectionList
386
387 anchors.fill: parent
388 model: _urisModel
389
390 delegate: control.listDelegate
391 }
392 }
393 }
394
395 ParallelAnimation
396 {
397 id: _openAnimation
398 NumberAnimation
399 {
400 target: _layout
401 property: "y"
402 from: _layout.height
403 to: Maui.Style.space.big/2
404 duration: Maui.Style.units.longDuration*1
405 easing.type: Easing.OutBack
406 }
407
408 NumberAnimation
409 {
410 target: _layout
411 property: "scale"
412 from: 0.5
413 to: 1
414 duration: Maui.Style.units.longDuration*1
415 easing.type: Easing.OutQuad
416 }
417 }
418
419 ParallelAnimation
420 {
421 id: _closeAnimation
422 NumberAnimation
423 {
424 target: _layout
425 property: "y"
426 from: Maui.Style.space.big/2
427 to: _layout.height
428 duration: Maui.Style.units.longDuration*1
429 easing.type: Easing.InBack
430
431 }
432
433 NumberAnimation
434 {
435 target: _layout
436 property: "scale"
437 from: 1
438 to: 0.5
439 duration: Maui.Style.units.longDuration*1
440 easing.type: Easing.InQuad
441
442 }
443
444 onFinished: control.visible = false
445 }
446
447 Maui.ToolBar
448 {
449 id: _layout
450 width: control.width
451 padding: Maui.Style.space.medium
452
453 Maui.Theme.inherit: false
454 Maui.Theme.colorSet: Maui.Theme.Complementary
455
456 forceCenterMiddleContent: false
457 position: ToolBar.Footer
458
459 leftContent: Maui.Chip
460 {
461 id: _counter
462 visible: !control.singleSelection
463 text: control.count
464 font.pointSize: Maui.Style.fontSizes.big
465 Maui.Theme.colorSet: control.Maui.Theme.colorSet
466 Maui.Theme.backgroundColor: _loader.item && _loader.item.visible ?
467 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))
468
469 Maui.Rectangle
470 {
471 opacity: 0.3
472 anchors.fill: parent
473 anchors.margins: 4
474 visible: _counter.hovered
475 color: "transparent"
476 borderColor: "white"
477 solidBorder: false
478 }
479
480 MouseArea
481 {
482 id: _mouseArea
483 anchors.fill: parent
484 propagateComposedEvents: true
485 property int startX
486 property int startY
487 Drag.active: drag.active
488 Drag.hotSpot.x: 0
489 Drag.hotSpot.y: 0
490 Drag.dragType: Drag.Automatic
491 Drag.supportedActions: Qt.CopyAction
492 Drag.keys: ["text/plain","text/uri-list"]
493
494 onPressed: (mouse) =>
495 {
496 if( mouse.source !== Qt.MouseEventSynthesizedByQt)
497 {
498 drag.target = _counter
499 _counter.grabToImage(function(result)
500 {
501 _mouseArea.Drag.imageSource = result.url
502 })
503
504 _mouseArea.Drag.mimeData = { "text/uri-list": control.uris.join("\n")}
505
506 startX = _counter.x
507 startY = _counter.y
508
509 }else mouse.accepted = false
510 }
511
512 onReleased :
513 {
514 _counter.x = startX
515 _counter.y = startY
516 }
517
518
519 onClicked:
520 {
521 if(!_loader.item)
522 {
523 _loader.sourceComponent = _listContainerComponent
524 }
525 _loader.item.open()
526 console.log("Opening list")
527 }
528
529 }
530 }
531
532 Repeater
533 {
534 model: control.actions
535
536 ToolButton
537 {
538 Maui.Theme.inherit: false
539 Maui.Theme.colorSet: Maui.Theme.Complementary
540
541 action: modelData
542 display: control.display
543 ToolTip.delay: 1000
544 ToolTip.timeout: 5000
545 ToolTip.visible: hovered || pressed && action.text
546 ToolTip.text: action.text
547 }
548 }
549
550 Maui.ToolButtonMenu
551 {
552 Maui.Theme.inherit: false
553 Maui.Theme.colorSet: Maui.Theme.Complementary
554
555 icon.name: "overflow-menu"
556 visible: control.hiddenActions.length > 0
557 Repeater
558 {
559 model: control.hiddenActions
560 delegate: MenuItem{ action: modelData}
561 }
562 }
563
564 rightContent: Maui.CloseButton
565 {
566 Maui.Theme.inherit: false
567 Maui.Theme.colorSet: Maui.Theme.Complementary
568
569 onClicked:
570 {
571 control.exitClicked()
572 }
573 }
574
575 background: Rectangle
576 {
577 id: bg
578 color: Maui.Theme.backgroundColor
579 radius: control.radius
580
581 Behavior on color
582 {
583 Maui.ColorTransition {}
584 }
585
586 MouseArea
587 {
588 anchors.fill: parent
589 acceptedButtons: Qt.RightButton | Qt.LeftButton
590 propagateComposedEvents: false
591 preventStealing: true
592
593 onClicked: (mouse) =>
594 {
595 if(!Maui.Handy.isMobile && mouse.button === Qt.RightButton)
596 control.rightClicked(mouse)
597 else
598 control.clicked(mouse)
599 }
600
601 onPressAndHold : (mouse) =>
602 {
603 if(Maui.Handy.isMobile)
604 control.rightClicked(mouse)
605 }
606 }
607
608 Maui.Rectangle
609 {
610 opacity: 0.2
611 anchors.fill: parent
612 anchors.margins: 4
613 visible: _dropArea.containsDrag
614 color: "transparent"
615 borderColor: Maui.Theme.textColor
616 solidBorder: false
617 }
618
619 DropArea
620 {
621 id: _dropArea
622 anchors.fill: parent
623 onDropped:
624 {
625 control.urisDropped(drop.urls)
626 }
627 }
628
629 layer.enabled: true
630 layer.effect: DropShadow
631 {
632 cached: true
633 horizontalOffset: 0
634 verticalOffset: 0
635 radius: 8.0
636 samples: 16
637 color: "#80000000"
638 smooth: true
639 }
640 }
641 }
642
643 Keys.onEscapePressed:
644 {
645 control.exitClicked();
646 }
647
648 Keys.onBackPressed:
649 {
650 control.exitClicked();
651 event.accepted = true
652 }
653
654 /**
655 * @brief Removes all the items from the selection.
656 * Triggers the `cleared` signal.
657 * @see cleared
658 */
659 function clear()
660 {
661 _private._uris = []
662 _private._items = []
663 _urisModel.clear()
664 control.cleared()
665 }
666
667 /**
668 * @brief Returns an item at the given index
669 * @param index the index number of the item.
670 * @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.
671 * @return the requested item/map if it is found, otherwise null.
672 */
673 function itemAt(index)
674 {
675 if(index < 0 || index > control.count)
676 {
677 return
678 }
679 return _urisModel.get(index)
680 }
681
682 /**
683 * @brief Remove a single item at the given index
684 * @param index index of the item in the selection list
685 */
686 function removeAtIndex(index)
687 {
688 if(index < 0)
689 {
690 return
691 }
692
693 const item = _urisModel.get(index)
694 const uri = item.uri
695
696 if(contains(uri))
697 {
698 _private._uris.splice(index, 1)
699 _private._items.splice(index, 1)
700 _urisModel.remove(index)
701 control.itemRemoved(item)
702 control.uriRemoved(uri)
703 }
704 }
705
706 /**
707 * @brief Removes an item from the selection at the given URI
708 * @param uri the URI used to append the item
709 */
710 function removeAtUri(uri)
711 {
712 removeAtIndex(indexOf(uri))
713 }
714
715 /**
716 * @brief Returns the index of an item in the selection given its URI
717 * @param uri the URI used to append the item
718 * @return the index number of the found item, otherwise `-1`
719 *
720 */
721 function indexOf(uri)
722 {
723 return _private._uris.indexOf(uri)
724 }
725
726 /**
727 * @brief Appends a new item to the selection associated to the given URI
728 * @param uri the URI to be associated with the item
729 * @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"})`
730 */
731 function append(uri, item)
732 {
733 if(control.singleSelection)
734 {
735 clear()
736 }
737
738 if(!contains(uri) || control.singleSelection)
739 {
740 _private._items.push(item)
741 _private._uris.push(uri)
742
743 item.uri = uri
744 _urisModel.append(item)
745 control.itemAdded(item)
746 control.uriAdded(uri)
747 }
748 }
749
750 /**
751 * @brief Returns a single string with all the URIs separated by a comma.
752 */
753 function getSelectedUrisString()
754 {
755 return String(""+_private._uris.join(","))
756 }
757
758 /**
759 * @brief Returns whether the selection contains an item associated to the given URI.
760 * @param uri the URI used to append the item
761 */
762 function contains(uri)
763 {
764 return _private._uris.includes(uri)
765 }
766
767 /**
768 * @brief Forces to open the selection bar, if hidden.
769 */
770 function open()
771 {
772 if(control.visible)
773 {
774 return;
775 }
776
777 control.visible = true
778 _openAnimation.start()
779 }
780
781 /**
782 * @brief Forces to close the selection bar, if visible.
783 */
784 function close()
785 {
786 if(!control.visible)
787 {
788 return
789 }
790 _closeAnimation.start()
791 }
792}
An alternative to QQC2 ToolBar, with a custom horizontal layout - divided into three main sections - ...
Definition ToolBar.qml:115
int count
The total amount of items in the toolbar sections, items can be non-visible and sum-up.
Definition ToolBar.qml:217
int timeout
KIOWIDGETS_EXPORT DropJob * drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags=DefaultFlags)
QAction * close(const QObject *recvr, const char *slot, QObject *parent)
QAction * open(const QObject *recvr, const char *slot, QObject *parent)
QAction * clear(const QObject *recvr, const char *slot, QObject *parent)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:56:16 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.