MauiKit Controls

TabView.qml
1import QtQuick
2import QtQml
3
4import QtQuick.Controls
5import QtQuick.Layouts
6import Qt5Compat.GraphicalEffects
7
8import org.mauikit.controls 1.3 as Maui
9
10/**
11 * @brief Container to organize items as a tab view.
12 *
13 * <a href="https://doc.qt.io/qt-6/qml-qtquick-controls-pane.html">This controls inherits from QQC2 Pane, to checkout its inherited properties refer to the Qt Docs.</a>
14 *
15 * The TabView organizes its children into different tabs - a tab for each child.
16 * There are two ways for adding tabs to this view. The first one and easier is to declare the items as children. This will create a tab for each declared child item.
17 * @see content
18 *
19 * @code
20 * TabView
21 * {
22 * Rectangle
23 * {
24 * TabViewInfo.tabTitle: "Tab1"
25 * TabViewInfo.tabIcon: "folder"
26 * color: "blue"
27 * }
28 *
29 * Rectangle
30 * {
31 * TabViewInfo.tabTitle: "Tab2"
32 * TabViewInfo.tabIcon: "folder"
33 * color: "yellow"
34 * }
35 * }
36 * @endcode
37 *
38 * The second way to create tabs dynamically by adding new items using the control functions.
39 * @see addTab
40 *
41 * @code
42 * TabView
43 * {
44 * id: _tabView
45 * tabBar.leftContent: Button
46 * {
47 * text: "Add Tab"
48 * onClicked:
49 * {
50 * _tabView.addTab(_component)
51 * }
52 * }
53 *
54 * Component
55 * {
56 * id: _component
57 * Rectangle
58 * {
59 * TabViewInfo.tabTitle: "Tab1"
60 * TabViewInfo.tabIcon: "folder"
61 * color: "blue"
62 * }
63 * }
64 * }
65 * @endcode
66 *
67 * @section structure Structure
68 *
69 * @note If you use this as the application window main control, remember you can use the attached Controls.showCSD property to display the window control buttons.
70 * @code
71 * TabView
72 * {
73 * Controls.showCSD : true
74 * }
75 * @endcode
76 *
77 * The TabView has two main sections, [1] the main contents area and [2] the tab bar, where the tab buttons - representing all the tab views - are listed.
78 *
79 * The tab bar listing has two "modes": one designed to fit desktop computers as a horizontal set of tab buttons; and the other one - more mobile friendly - as a grid overview of miniatures of the tab-views. Both modes can be toggle by using the mobile property.
80 * @see mobile
81 *
82 * The main tab bar - usually in the top section - can be moved to the bottom area, for better reachability.
83 * @see altTabBar
84 *
85 * The tab bar component is exposed via an alias, and items can be added to it - to the left or right side, using the leftContent or rightContent properties. This tab bar is handled by a MauiKit TabBar.
86 * @see tabBar
87 * @see TabBar
88 *
89 * @image html TabView/tabviews.png "TabView different states/sections. Further down the regular tab view with a tab bar on top, in the middle the tab view overview mode, and in the front the mobile mode with a single tab - other tab buttons can be flicked."
90 *
91 * @subsection finder Finder
92 * The TabView has an integrated dialog to quickly perform searchers for an tab.
93 *
94 * @section notes Notes
95 *
96 * @subsection tabinfo Tabs Info
97 * To add information, such as title, icon, etc, to the views, there is the `TabViewInfo` attached property.
98 * Some of the available properties are:
99 * - TabViewInfo.tabTitle
100 * - TabViewInfo.tabToolTipText
101 * - TabViewInfo.tabColor
102 * - TabViewInfo.tabIcon
103 * @see TabViewInfo
104 *
105 * @subsection menus Menus
106 * It is possible to add actions to the the tab contextual menus.
107 * @see menuActions
108 *
109 * @subsection customize Custom Tab Button
110 * It is possible to set a different tab button for the tab bar. This will allow you to have more control about how the tab button is presented. To make it easier to integrate there is a control template that can be use as a base TabViewButton - different from TabButton.
111 * @see tabViewButton
112 * @see TabViewButton
113 *
114 * @note If you plan to change the tab button, consider using TabViewButton as a base, since it already has many of the needed things by the TabView to be integrated smoothly.
115 *
116 * @subsection functionality Functionality
117 *
118 * By default when clicking/tapping on the current tab button, the overview of tabs miniatures will be opened. The overview can also be oped using the corresponding function.
119 * @see openOverview
120 * @see closeOverview
121 *
122 * <a href="https://invent.kde.org/maui/mauikit/-/blob/qt6-2/examples/TabView.qml">You can find a more complete example at this link.</a>
123 *
124 */
125Pane
126{
127 id: control
128
129 /**
130 * @brief Each one of the items declared as the children of this component will become a tab view.
131 * @property list<QtObject> TabView::content
132 */
133 default property alias content: _listView.contentData
134
135 /**
136 * @brief An alias to the model of the container.
137 * @property model TabView::contentModel
138 */
139 readonly property alias contentModel: _listView.contentModel
141 /**
142 * @brief Current index number of the current tab in the view port.
143 * @property int TabView::currentIndex
144 */
145 property alias currentIndex: _listView.currentIndex
147 /**
148 * @brief The current item/tab view in focus.
149 * @property Item TabView::currentItem
150 */
151 readonly property alias currentItem: _listView.currentItem
152
153 /**
154 * @brief The total amount of tab views in this container.
155 * @property int TabView::count
156 */
157 property alias count: _listView.count
158
159 /**
160 * @brief An alias to a place holder text handled by a MauiKit Holder control. This is useful to display messages when there is not tab views in this container. To see how to set the message, icon and actions to it checkout the Holder documentation.
161 * @see holder
162 * @property Holder TabView::holder
163 */
164 property alias holder : _holder
165
166 /**
167 * @brief Whether the layout of the tab bar should be in mobile or desktop mode. In mobile mode there is only one tab button in the tab bar view port, other tab button can be navigated by using the touch swipe gesture. In mobile mode the main method to switch between tab views is using the overview.
168 * @see structure
169 */
170 property bool mobile : control.width <= Maui.Style.units.gridUnit * 30
171
172 /**
173 * @brief Whether the tab bar hosting the tab buttons, should go in the bottom section or not. Moving it to the bottom section is a good idea for reachability in hand-held devices, such a phones. You can check if the current platform is a mobile one using the Handy attached property `Handy.isMobile`
174 * @see Handy
175 *
176 */
177 property bool altTabBar : false
178
179 /**
180 * @brief Whether the view will support swipe gestures for switching between tab views.
181 */
182 property bool interactive: Maui.Handy.isTouch
184 /**
185 * @brief Checks if the overview mode is open.
186 */
187 readonly property bool overviewMode : _stackView.depth === 2
188
189 /**
190 * @brief An alias to the tab bar element. This is exposed so any other items can be placed on the right or left sections of it, or to fine tweak its public properties.
191 * @see TabBar
192 * @property TabBar TabView::tabBar
193 */
194 property alias tabBar: _tabBar
195
196 /**
197 * @brief An alias to the contextual menu for the tab buttons. This has a child property named index, which refers to the index number of the tab for which the contextual menu was opened.
198 * @see ContextualMenu
199 * @property ContextualMenu TabView::menu
200 */
201 property alias menu :_menu
202
203 /**
204 * @brief A set of actions can be added to the tab button contextual menu.
205 * @code
206 * TabView
207 * {
208 * id: _tabView
209 * menuActions: Action
210 * {
211 * text: "Detach Tab"
212 * onTriggered:
213 * {
214 * console.log("Detach tab at index, ", _tabView.menu.index)
215 * }
216 * }
217 * }
218 * @endcode
219 */
220 property list<Action> menuActions
221
222 /**
223 * @brief The component to be used as the tab button in the tab bar. This can be changed to any other item, but it is recommend it to use TabViewButton as the base of the new custom control for the better integration.
224 * @see TabViewButton
225 */
226 property Component tabViewButton : _tabButtonComponent
227
228 onWidthChanged: _tabBar.positionViewAtIndex(control.currentIndex)
229 onCurrentIndexChanged: _tabBar.positionViewAtIndex(control.currentIndex)
230
231 spacing: 0
232 padding: 0
233
234 Component
235 {
236 id: _tabButtonComponent
237
238 Maui.TabViewButton
239 {
240 id: _tabButton
241 tabView: control
242 closeButtonVisible: !control.mobile
243
244 onClicked:
245 {
246 if(_tabButton.mindex === control.currentIndex && control.count > 1)
247 {
248 control.openOverview()
249 return
250 }
251
252 _listView.setCurrentIndex(_tabButton.mindex)
254
255 onRightClicked:
256 {
257 openTabMenu(_tabButton.mindex)
258 }
259
260 onCloseClicked:
262 control.closeTabClicked(_tabButton.mindex)
263 }
264 }
266
267 /**
268 * @brief Emitted when the new-tab button has been clicked. This is the same as catching the signal from the TabBar element itself, using the exposed alias property.
269 * @see tabBar
270 */
271 signal newTabClicked()
272
273 /**
274 * @brief Emitted when the close button has been clicked on a tab button, or tab miniature in the overview. This signal sends the index of the tab. To correctly close the tab and release the resources, use the function `closeTab()`.
275 * @param The index of the tab requested to be closed.
276 * @see closeTab
277 */
278 signal closeTabClicked(int index)
279
280 Keys.enabled: true
281 Keys.onPressed: (event) =>
282 {
283 if((event.key === Qt.Key_H) && (event.modifiers & Qt.ControlModifier))
284 {
285 control.findTab()
286 }
287 }
288
289 Maui.ContextualMenu
290 {
291 id: _menu
292 parent: control
293 property int index //tabindex
294
295 Repeater
296 {
297 model: control.menuActions
298 delegate: MenuItem
299 {
300 action: modelData
301 }
302 }
303
304 MenuItem
305 {
306 text: i18nd("mauikit", "Open")
307 icon.name: "tab-new"
308 onTriggered:
309 {
310 _listView.setCurrentIndex(_menu.index)
311 control.closeOverview()
312 }
313 }
314
315 MenuItem
316 {
317 text: i18nd("mauikit", "Close")
318 icon.name: "tab-close"
319 onTriggered:
320 {
321 control.closeTabClicked(_menu.index)
322 }
323 }
324 }
325
326 data: Loader
327 {
328 id: _loader
329 }
330
331 Component
332 {
333 id: _quickSearchComponent
334
335 Maui.PopupPage
336 {
337 id: _quickSearch
338 persistent: false
339 headBar.visible: true
340
341 onOpened: _quickSearchField.forceActiveFocus()
342
343 function find(query)
344 {
345 for(var i = 0; i < control.count; i ++)
346 {
347 var obj = _listView.contentModel.get(i)
348 if(obj.Maui.TabViewInfo.tabTitle)
349 {
350 console.log("Trying to find tab", i, query, obj.Maui.TabViewInfo.tabTitle, String(obj.Maui.TabViewInfo.tabTitle).indexOf(query))
351
352 if(String(obj.Maui.TabViewInfo.tabTitle).toLowerCase().indexOf(query.toLowerCase()) !== -1)
353 {
354 return i
355 }
356 }
357 }
358
359 return -1
360 }
361
362 Timer
363 {
364 id: _typingTimer
365 interval: 250
366 onTriggered:
367 {
368 var index = _quickSearch.find(_quickSearchField.text)
369 if(index > -1)
370 {
371 _filterTabsList.currentIndex = index
372 }
373 }
374 }
375
376 headBar.middleContent: TextField
377 {
378 id: _quickSearchField
379 Layout.fillWidth: true
380 Layout.maximumWidth: 500
381
382 onTextChanged: _typingTimer.restart()
383
384 onAccepted:
385 {
386 control.setCurrentIndex(_filterTabsList.currentIndex)
387 _quickSearch.close()
388 }
389
390 Keys.enabled: true
391
392 Keys.onPressed: (event) =>
393 {
394 if((event.key === Qt.Key_Up))
395 {
396 _filterTabsList.flickable.decrementCurrentIndex()
397 }
398
399 if((event.key === Qt.Key_Down))
400 {
401 _filterTabsList.flickable.incrementCurrentIndex()
402 }
403 }
404 }
405
406 stack: Maui.ListBrowser
407 {
408 id: _filterTabsList
409 Layout.fillWidth: true
410 Layout.fillHeight: true
411 currentIndex: _listView.currentIndex
412
413 model: _listView.count
414
415 delegate: Maui.ListDelegate
416 {
417 width: ListView.view.width
418
419 label: _listView.contentModel.get(index).Maui.TabViewInfo.tabTitle
420
421 onClicked:
422 {
423 currentIndex =index
424 _listView.setCurrentIndex(index)
425 _quickSearch.close()
426 }
427 }
428 }
429 }
430 }
431
432 contentItem: Item
433 {
434 StackView
435 {
436 anchors.fill: parent
437 id: _stackView
438
439 initialItem: Item
440 {
441 Maui.TabBar
442 {
443 id: _tabBar
444
445 z : _listView.z+1
446
447 anchors.left: parent.left
448 anchors.right: parent.right
449
450 Maui.Controls.showCSD : control.Maui.Controls.showCSD === true && position === TabBar.Header
451
452 visible: _listView.count > 1
453
454 interactive: control.interactive
455 showNewTabButton: !control.mobile
456
457 onNewTabClicked: control.newTabClicked()
458 onNewTabFocused:
459 {
460 if(control.mobile)
461 {
462 _listView.setCurrentIndex(index)
463 }
464 }
465
466 position: control.altTabBar ? TabBar.Footer : TabBar.Header
467
468 Repeater
469 {
470 model: control.count
471 delegate: control.tabViewButton
472 }
473
474 Keys.onPressed: (event) =>
475 {
476 if(event.key == Qt.Key_Return)
477 {
478 _listView.setCurrentIndex(currentIndex)
479 event.accepted = true
480 }
481
482 if(event.key == Qt.Key_Down)
483 {
484 _listView.currentItem.forceActiveFocus()
485 event.accepted = true
486 }
487 }
488
489 states: [ State
490 {
491 when: !control.altTabBar && control.tabBar.visible
492
493 AnchorChanges
494 {
495 target: _tabBar
496 anchors.top: parent.top
497 anchors.bottom: undefined
498 }
499
500 PropertyChanges
501 {
502 target: _tabBar
503 position: TabBar.Header
504 }
505 },
506
507 State
508 {
509 when: control.altTabBar && control.tabBar.visible
510
511 AnchorChanges
512 {
513 target: _tabBar
514 anchors.top: undefined
515 anchors.bottom: parent.bottom
516 }
517
518 PropertyChanges
519 {
520 target: _tabBar
521 position: ToolBar.Footer
522 }
523 } ]
524 }
525
526 SwipeView
527 {
528 id: _listView
529 anchors.fill: parent
530
531 anchors.bottomMargin: control.altTabBar && _tabBar.visible ? _tabBar.height : 0
532 anchors.topMargin: !control.altTabBar && _tabBar.visible ? _tabBar.height : 0
533
534 interactive: false
535
536 spacing: control.spacing
537
538 clip: control.clip
539
540 orientation: ListView.Horizontal
541 background: null
542
543 contentItem: ListView
544 {
545 id: _listView2
546 model: _listView.contentModel
547 interactive: false
548 currentIndex: _listView.currentIndex
549
550 spacing: _listView.spacing
551
552 clip: false
553
554 orientation: ListView.Horizontal
555 snapMode: ListView.SnapOneItem
556
557 boundsBehavior: Flickable.StopAtBounds
558 boundsMovement :Flickable.StopAtBounds
559
560 preferredHighlightBegin: 0
561 preferredHighlightEnd: width
562
563 highlightRangeMode: ListView.StrictlyEnforceRange
564 highlightMoveDuration: 0
565 highlightFollowsCurrentItem: true
566 highlightResizeDuration: 0
567 highlightMoveVelocity: -1
568 highlightResizeVelocity: -1
569
570 maximumFlickVelocity: 4 * width
571
572 cacheBuffer: _listView.count * width
573 keyNavigationEnabled : false
574 keyNavigationWraps : false
575 }
576 }
577
578 Maui.Holder
579 {
580 id: _holder
581 anchors.fill: parent
582 visible: !control.count
583 emojiSize: Maui.Style.iconSizes.huge
584 }
585 }
586
587 Component
588 {
589 id: _overviewComponent
590
591 Pane
592 {
593 id: _pane
594 Maui.Theme.colorSet: Maui.Theme.View
595 Maui.Theme.inherit: false
596
597 background: Rectangle
598 {
599 color: Maui.Theme.backgroundColor
600 Loader
601 {
602 active: !Maui.Handy.isMobile
603 asynchronous: true
604 anchors.fill: parent
605 sourceComponent: Item
606 {
607 DragHandler
608 {
609 // acceptedDevices: PointerDevice.GenericPointer
610 grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByAnything
611 onActiveChanged: if (active) { control.Window.window.startSystemMove(); }
612 }
613 }
614 }
615 }
616
617 contentItem: Maui.GridBrowser
618 {
619 id: _overviewGrid
620 model: control.count
621
622 currentIndex: control.currentIndex
623
624 itemSize: Math.min(200, availableWidth /2)
625
626 Loader
627 {
628 asynchronous: true
629 anchors.bottom: parent.bottom
630 anchors.right: parent.right
631 anchors.margins: Maui.Style.space.big
632 sourceComponent: Maui.FloatingButton
633 {
634 icon.name: "list-add"
635 onClicked: control.newTabClicked()
636 }
637 }
638
639 onAreaClicked:
640 {
641 control.closeOverview()
642 }
643
644 delegate: Item
645 {
646 height: GridView.view.cellHeight
647 width: GridView.view.cellWidth
648
649 Maui.GridBrowserDelegate
650 {
651 id: _delegate
652
653 anchors.fill: parent
654 anchors.margins: Maui.Style.space.medium
655
656 isCurrentItem : parent.GridView.isCurrentItem
657 label1.text: _listView.contentModel.get(index).Maui.TabViewInfo.tabTitle
658 // template.labelSizeHint: 32
659 iconSource: "tab-new"
660 flat: false
661
662 tooltipText: _listView.contentModel.get(index).Maui.TabViewInfo.tabToolTipText
663
664 Rectangle
665 {
666 parent: _delegate.background
667 color: _listView.contentModel.get(index).Maui.TabViewInfo.tabColor
668 height: 2
669 width: parent.width*0.9
670 anchors.bottom: parent.bottom
671 anchors.horizontalCenter: parent.horizontalCenter
672 }
673
674 onRightClicked:
675 {
676 _listView.setCurrentIndex(index)
677 openTabMenu(_listView.currentIndex)
678 }
679
680 onClicked:
681 {
682 control.closeOverview()
683 _listView.setCurrentIndex(index)
684 }
685
686 onPressAndHold:
687 {
688 _listView.setCurrentIndex(index)
689 openTabMenu(control.currentIndex)
690 }
691
692 template.iconComponent: Item
693 {
694 clip: true
695
696 ShaderEffectSource
697 {
698 id: _effect
699
700 anchors.centerIn: parent
701
702 readonly property double m_scale: Math.min(parent.height/sourceItem.height, parent.width/sourceItem.width)
703
704 height: sourceItem.height * m_scale
705 width: sourceItem.width * m_scale
706
707 hideSource: false
708 live: false
709 smooth: true
710
711 textureSize: Qt.size(width,height)
712 sourceItem: _listView.contentModel.get(index)
713 layer.enabled: Maui.Style.enableEffects
714 layer.smooth: true
715 layer.effect: OpacityMask
716 {
717 maskSource: Rectangle
718 {
719 width: _effect.width
720 height: _effect.height
721 radius: Maui.Style.radiusV
722 }
723 }
724 }
725
726 Maui.CloseButton
727 {
728 id: _closeButton
729 visible: _delegate.isCurrentItem || _delegate.hovered || Maui.Handy.isMobile
730 anchors.top: parent.top
731 anchors.right: parent.right
732 // anchors.margins: Maui.Style.space.small
733
734 onClicked: control.closeTabClicked(index)
735
736 background: Rectangle
737 {
738 radius: height/2
739 color: _closeButton.hovered || _closeButton.containsPress ? Maui.Theme.negativeBackgroundColor : Maui.Theme.backgroundColor
740
741 Behavior on color
742 {
743 Maui.ColorTransition{}
744 }
745 }
746 }
747 }
748 }
749 }
750 }
751 }
752 }
753 }
754 }
755
756 /**
757 * @brief Close a tab view at a given index. This will release the resources, and move the focus to the previous tab view
758 * @param index index of the tab to be closed
759 */
760 function closeTab(index)
761 {
762 _listView.removeItem(_listView.itemAt(index))
763 // _tabBar.removeItem(_tabBar.itemAt(index))
764
765 _listView.currentItemChanged()
766 _listView.currentItem.forceActiveFocus()
767 }
768
769 /**
770 * @brief Adds a new tab view element, the passed element must be a component which will be created by the function and then added to the container
771 * @param component a Component element hosting the actual element to be created.
772 * @param properties set of values that can be passed as a map of properties to be assigned to the component when created
773 * @param quiet optionally you can choose to create the tab quietly or not: by quietly it means that the tab-component will be instantiated and created but will not be focused right away. If quiet is set to `false` - to which it defaults - then after the creation of the component the new tab will get focused.
774 */
775 function addTab(component, properties, quiet = false) : Item
776 {
777 if(control.overviewMode)
778 {
779 control.closeOverview()
780 }
781
782 const object = component.createObject(control, properties);
783
784 _listView.addItem(object)
785
786 if(!quiet)
787 {
788 _listView.setCurrentIndex(Math.max(_listView.count -1, 0))
789 object.forceActiveFocus()
790 }
791
792 return object
793 }
794
795 /**
796 * @brief Method to open the tab finder.
797 */
798 function findTab()
799 {
800 if(control.count > 1)
801 {
802 _loader.sourceComponent = _quickSearchComponent
803 _loader.item.open()
804 }
805 }
806
807 /**
808 * @brief Method to open the tabs overview.
809 */
810 function openOverview()
811 {
812 if(_stackView.depth === 2)
813 {
814 return
815 }
816 _stackView.push(_overviewComponent)
817 }
818
819 /**
820 * @brief Close the overview of miniature tabs.
821 */
822 function closeOverview()
823 {
824 if(_stackView.depth === 1)
825 {
826 return
827 }
828
829 _stackView.pop()
830 }
831
832 /**
833 * @brief Method to correctly move a tab from one place to another by using the index numbers
834 * @param from the current index number of the tab to be moved
835 * @param to the new index value to where to move the tab
836 */
837 function moveTab(from, to)
838 {
839 _listView.moveItem(from, to)
840 _tabBar.moveItem(from, to)
841
842 _listView.setCurrentIndex(to)
843 _tabBar.setCurrentIndex(_listView.currentIndex)
844
845 _listView2.positionViewAtIndex(_listView.currentIndex, ListView.Contain)
846
847 _listView.currentItemChanged()
848 _listView.currentItem.forceActiveFocus()
849 }
850
851 /**
852 * @brief This allows to change the current tab without breaking the bindings from the `currentIndex` property
853 * @param index the index to change to
854 */
855 function setCurrentIndex(index)
856 {
857 _tabBar.setCurrentIndex(index)
858 _listView.setCurrentIndex(index)
859 _listView.currentItem.forceActiveFocus()
860 }
861
862 /**
863 * @brief Return the Item of the tab view at a given index number
864 * @return the Item element representing the tab view
865 */
866 function tabAt(index)
867 {
868 return _listView.itemAt(index)
869 }
870
871 /**
872 * @brief Opens the tab contextual menu at a given index number
873 */
874 function openTabMenu(index)
875 {
876 _menu.index = index
877 _menu.show()
878 }
879}
alias view
Tab bar alternative to QQC TabBar, and based on it.
Definition TabBar.qml:52
alias interactive
Whether the control will react to touch events to flick the tabs.
Definition TabBar.qml:74
An alternative to QQC2 ToolBar, with a custom horizontal layout - divided into three main sections - ...
Definition ToolBar.qml:115
QString i18nd(const char *domain, const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
std::optional< QSqlQuery > query(const QString &queryStatement)
QAction * find(const QObject *recvr, const char *slot, QObject *parent)
QString label(StandardShortcut id)
void forceActiveFocus()
QQuickWindow * window() const const
bool startSystemMove()
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.