MauiKit File Browsing

BrowserView.qml
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Layouts
4
5import org.mauikit.controls as Maui
6import org.mauikit.filebrowsing as FB
7
8/**
9 * @inherit org::mauikit::controls::AltBrowser
10 * @brief The browsing view implementation for the FileBrowser control.
11 *
12 * @warning This control is private and only exposed for tweaking its properties. It can not be instantiated manually.
13 *
14 */
15Maui.AltBrowser
16{
17 id: control
18
19 headBar.visible: false
20
21 title: currentFMList.pathName
22
23 enableLassoSelection: true
25 gridView.itemSize : control.gridItemSize
26 gridView.itemHeight: gridView.cellWidth
27
28 /**
29 * @brief It is possible to insert an arbitrary element for each entry. The component here declared will be drawn on top of the entry delegate in the grid view, and on the far right side in the list view.
30 * The entry element model details are accessible via the `model` property. For example `model.name`, `model.url`, etc.
31 */
32 property Component delegateInjector : null
33
34 /**
35 * @brief An alias to the current popup dialog being shown.
36 * @property Item BrowserView::dialog
37 */
38 readonly property alias dialog :_dialogLoader.item
39 Loader
40 {
41 id: _dialogLoader
42 }
44 QtObject
45 {
46 id: _private
47 property int gridIconSize: Maui.Style.mapToIconSizes(control.gridItemSize)
48 property int listIconSize: Maui.Style.mapToIconSizes(control.listItemSize)
49 }
50
51 Binding on currentIndex
52 {
53 when: control.currentView
54 value: control.currentView.currentIndex
55 }
56
57 viewType: settings.viewType === FB.FMList.ICON_VIEW ? Maui.AltBrowser.ViewType.Grid : Maui.AltBrowser.ViewType.List
58
59 onPathChanged:
60 {
61 control.currentIndex = 0
62 control.currentView.forceActiveFocus()
63 }
64
65 model: Maui.BaseModel
66 {
67 id: _browserModel
68 list: FB.FMList
69 {
70 id: _commonFMList
71 path: control.path
72 onSortByChanged: if(settings.group) groupBy()
73 onlyDirs: settings.onlyDirs
74 filterType: settings.filterType
75 filters: settings.filters
76 sortBy: settings.sortBy
77 hidden: settings.showHiddenFiles
78 foldersFirst: settings.foldersFirst
79 }
80
81 recursiveFilteringEnabled: true
82 sortCaseSensitivity: Qt.CaseInsensitive
83 filterCaseSensitivity: Qt.CaseInsensitive
84 }
85
86 /**
87 * @brief The current location path.
88 * @see FMList::path
89 * @property string BrowserView::path
90 */
91 property alias path : _commonFMList.path
93 /**
94 * @brief The size of the items in the grid. This is the total sum of the thumbnail icon and the name label.
95 * The icon size is calculated to match always a standard icon size.
96 * By default this is set to `140`
97 */
98 property int gridItemSize : 140
99
100 /**
101 * @brief The height size of the list elements.
102 * By default this is set to `Style.rowHeight`
103 */
104 property int listItemSize : Maui.Style.rowHeight
105
106 /**
107 * @brief An alias to access the grouped setting preferences for tweaking the file listing properties.
108 * @see BrowserSettings
109 * @property BrowserSettings BrowserView::settings
110 */
111 readonly property alias settings : _settings
112
113 /**
114 * @brief Whether the listing of the location contents is still loading. This can be false if the contents are ready or have failed, to check those other conditions refer to the FMList::status property.
115 * @see FMList::status::code
116 */
117 readonly property bool loading : currentFMList.status.code === FB.PathStatus.LOADING
118
119 /**
120 * @see FMList::readOnly
121 * @property bool BrowserView::readOnly
122 */
123 property alias readOnly: _commonFMList.readOnly
125 /**
126 * @brief An alias to the FMList model list and controller for listing the location contents and exposing the browsing management action methods.
127 * @note The sorting of the location contents is done via this object properties, and not using the MauiKit MauiModel wrapper.
128 * @see currentFMModel
130 * @property FMList BrowserView::currentFMList
131 */
132 readonly property alias currentFMList : _commonFMList
133
134 /**
135 * @brief An alias to the MauiKit MauiModel, wrapping the currentFMList.
136 * @property MauiKit::MauiModel BrowserView::currentFMModel
137 */
138 readonly property alias currentFMModel : _browserModel
140 /**
141 * @brief The string value to filter the location contents.
142 * @see MauiKit::MauiModel::filter BrowserView::filter
143 */
144 property alias filter : _browserModel.filter
145
146 /**
147 * @brief The list of strings values to filter the location contents.
148 * @see MauiKit::MauiModel::filter BrowserView::filters
149 */
150 property alias filters: _browserModel.filters
151
152 /**
153 * @brief Emitted when an entry item has been clicked.
154 * @param index the index position of the item
155 * @note To correctly map and index position to the actual item entry in the model use the `currentFMModel.get()` method, this will take care of correctly mapping the indexes in case the content has been filtered or sorted.
156 */
157 signal itemClicked(int index)
158
159 /**
160 * @brief Emitted when an entry has been double clicked.
161 * @param index the index position of the item
162 */
163 signal itemDoubleClicked(int index)
164
165 /**
166 * @brief Emitted when an entry has been right clicked.
167 * @param index the index position of the item
168 */
169 signal itemRightClicked(int index)
170
171 /**
172 * @brief Emitted when an entry selection has been toggled.
173 * @param index the index position of the item
174 * @param state the checked state
175 */
176 signal itemToggled(int index, bool state)
177
178 /**
179 * @brief Emitted when a set of entries has been selected by using the lasso selection.
180 * @param indexes the list of indexes positions selected
181 */
182 signal itemsSelected(var indexes)
183
184 /**
185 * @brief Emitted when a keyboard key has been pressed
186 * @param event the object with the event information
187 */
188 signal keyPress(var event)
189
190 /**
191 * @brief Emitted when the background area has been clicked
192 * @param mouse the object with the event information
193 */
194 signal areaClicked(var mouse)
195
196 /**
197 * @brief Emitted when the background area has been right clicked. This can be consumed for launching a contextual menu.
198 * @param mouse the object with the event information
199 */
200 signal areaRightClicked(var mouse)
201
202 Connections
203 {
204 target: control.currentView
205 ignoreUnknownSignals: true
206
207 function onKeyPress(event)
208 {
209 control.keyPress(event)
210 }
211
212 function onItemsSelected(indexes)
213 {
214 control.itemsSelected(indexes)
215 }
216
217 function onAreaClicked(mouse)
218 {
219 console.log("Area clicked")
220 control.currentView.forceActiveFocus()
221 control.areaClicked(mouse)
222 }
223
224 function onAreRightClicked(mouse)
225 {
226 console.log("Area right clicked")
227
228 control.currentView.forceActiveFocus()
229 control.areaRightClicked(mouse)
230 }
231 }
232
234 {
235 id: _settings
236 onGroupChanged:
237 {
238 if(settings.group)
239 {
240 groupBy()
241 }
242 else
243 {
244 currentView.section.property = ""
245 }
246 }
247 }
248
249 BrowserHolder
250 {
251 id: _holder
252 browser: _commonFMList
253 }
254
255 Maui.ProgressIndicator
256 {
257 id: _scanningProgress
258 width: parent.width
259 anchors.bottom: parent.bottom
260 visible: control.loading
261 }
262
263 holder.visible: _holder.visible
264 holder.emoji: _holder.emoji
265 holder.title: _holder.title
266 holder.body: _holder.body
267
268 Maui.ContextualMenu
269 {
270 id: _dropMenu
271 property string urls
272 property url target
273
274 MenuItem
275 {
276 Component
277 {
278 id: _mergeDialogComponent
279
280 Maui.InputDialog
281 {
282 id: _mergeDialog
283 property var urls
284
285 readonly property bool dirExists : FB.FM.fileExists(control.path+"/"+textEntry.text)
286
287 onDirExistsChanged:
288 {
289 console.log("DIR EXISTS?", dirExists)
290
291 if(dirExists)
292 {
293 _mergeDialog.alert(i18nd("mauikitfilebrowsing", "Directory already exists."), 2)
294 }else
295 {
296 _mergeDialog.alert(i18nd("mauikitfilebrowsing", "Looks good."), 0)
297 }
298 }
299
300 title: i18nd("mauikitfilebrowsing", "Merge %1 files", urls.length)
301 message:i18nd("mauikitfilebrowsing", "Give a name to the new directory where all files will be merge.")
302
303 textEntry.placeholderText: i18nd("mauikitfilebrowsing", "Directory name")
304
305 onFinished:
306 {
307 FB.FM.group(_mergeDialog.urls, control.path, text)
308 }
309 }
310 }
311
312 enabled: !FB.FM.isDir(_dropMenu.target) && !control.readOnly
313 text: i18nd("mauikitfilebrowsing", "Merge Here")
314 icon.name: "edit-group"
315 onTriggered:
316 {
317 var urls = _dropMenu.urls.split(",")
318 urls.push(_dropMenu.target)
319 _dialogLoader.sourceComponent = _mergeDialogComponent
320 dialog.urls = urls
321 dialog.open()
322 }
323 }
324
325 MenuItem
326 {
327 enabled: FB.FM.isDir(_dropMenu.target) && !control.readOnly
328 text: i18nd("mauikitfilebrowsing", "Copy Here")
329 icon.name: "edit-copy"
330 onTriggered:
331 {
332 const urls = _dropMenu.urls.split(",")
333 FB.FM.copy(urls, _dropMenu.target)
334 }
335 }
336
337 MenuItem
338 {
339 enabled: FB.FM.isDir(_dropMenu.target) && !control.readOnly
340 text: i18nd("mauikitfilebrowsing", "Move Here")
341 icon.name: "edit-move"
342 onTriggered:
343 {
344 const urls = _dropMenu.urls.split(",")
345 FB.FM.cut(urls, _dropMenu.target)
346 }
347 }
348
349 MenuItem
350 {
351 enabled: FB.FM.isDir(_dropMenu.target) && !control.readOnly
352 text: i18nd("mauikitfilebrowsing", "Link Here")
353 icon.name: "edit-link"
354 onTriggered:
355 {
356 const urls = _dropMenu.urls.split(",")
357 for(var i in urls)
358 FB.FM.createSymlink(url[i], _dropMenu.target)
359 }
360 }
361
362 MenuSeparator {}
363
364 MenuItem
365 {
366 text: i18nd("mauikitfilebrowsing", "Cancel")
367 icon.name: "dialog-cancel"
368 onTriggered: _dropMenu.close()
369 }
370 }
371
372 listView.section.delegate: Maui.LabelDelegate
373 {
374 id: delegate
375 width: ListView.view.width
376 height: Maui.Style.toolBarHeightAlt
377 text: control.listView.section.property == "date" || control.listView.section.property === "modified" ? Qt.formatDateTime(new Date(section), "d MMM yyyy") : section
378 isSection: true
379 }
380
381 listDelegate: Maui.ListBrowserDelegate
382 {
383 id: delegate
384 readonly property string path : model.path
385
386 width: ListView.view.width
387
388 iconSource: model.icon
389
390 label1.text: model.label ? model.label : ""
391 label2.text: control.objectName === "searchView" ? model.path : ""
392 label3.text : model.mime ? (model.mime === "inode/directory" ? (model.count ? model.count + i18nd("mauikitfilebrowsing", " items") : "") : Maui.Handy.formatSize(model.size)) : ""
393 label4.text: model.modified ? Maui.Handy.formatDate(model.modified, "MM/dd/yyyy") : ""
394
395 template.isMask: iconSizeHint <= 22
396 iconSizeHint: _private.listIconSize
397
398 tooltipText: model.path
399
400 checkable: control.selectionMode || checked
401 imageSource: settings.showThumbnails && height > 32 ? model.thumbnail : ""
402 checked: selectionBar ? selectionBar.contains(model.path) : false
403 template.iconContainer.opacity: model.hidden == "true" ? 0.5 : 1
404 draggable: true
405
406 Drag.keys: ["text/uri-list"]
407 Drag.mimeData: {
408 "text/uri-list": filterSelection(control.path, model.path)
409 }
410
411 Item
412 {
413 Layout.fillHeight: true
414 Layout.preferredWidth: height
415 visible: (model.issymlink == true) || (model.issymlink == "true")
416
417 Maui.Icon
418 {
419 source: "link"
420 height: Maui.Style.iconSizes.small
421 width: Maui.Style.iconSizes.small
422 anchors.centerIn: parent
423 isMask: true
424 color: label1.color
425 }
426 }
427
428 Loader
429 {
430 id: _injectorLoader
431 asynchronous: true
432 property var itemData : model
433 sourceComponent: control.delegateInjector
434 Layout.fillHeight: true
435 Layout.preferredWidth: visible ? height : 0
436 active: control.delegateInjector
437 visible: active
438 }
439
440 onClicked: (mouse) =>
441 {
442 control.currentIndex = index
443
444 if ((mouse.button == Qt.LeftButton) && (mouse.modifiers & Qt.ControlModifier))
445 {
446 control.itemsSelected([index])
447 }else if((mouse.button == Qt.LeftButton) && (mouse.modifiers & Qt.ShiftModifier))
448 {
449 var lastSelectedIndex = findLastSelectedIndex(control.listView.flickable, index)
450
451 if(lastSelectedIndex < 0)
452 {
453 return
454 }
455 control.itemsSelected(control.range(lastSelectedIndex, index))
456
457 }else
458 {
459 control.itemClicked(index)
460 }
461 }
462
463 onDoubleClicked:
464 {
465 control.currentIndex = index
466 control.itemDoubleClicked(index)
467 }
468
469 onPressAndHold:
470 {
471 if(!Maui.Handy.isTouch)
472 return
473
474 control.currentIndex = index
475 control.itemRightClicked(index)
476 }
477
478 onRightClicked:
479 {
480 control.currentIndex = index
481 control.itemRightClicked(index)
482 }
483
484 onToggled: (state) =>
485 {
486 control.currentIndex = index
487 control.itemToggled(index, state)
488 }
489
490 onContentDropped: (drop) =>
491 {
492 _dropMenu.urls = drop.urls.join(",")
493 _dropMenu.target = model.path
494 _dropMenu.show()
495 }
496
497 ListView.onRemove:
498 {
499 if(selectionBar)
500 {
501 selectionBar.removeAtUri(delegate.path)
502 }
503 }
504
505 Connections
506 {
507 target: selectionBar
508
509 function onUriRemoved(uri)
510 {
511 if(uri === model.path)
512 delegate.checked = false
513 }
514
515 function onUriAdded(uri)
516 {
517 if(uri === model.path)
518 delegate.checked = true
519 }
520
521 function onCleared()
522 {
523 delegate.checked = false
524 }
525 }
526 }
527
528 gridDelegate: Item
529 {
530 height: GridView.view.cellHeight
531 width: GridView.view.cellWidth
532 readonly property alias checked : delegate.checked
533
534 GridView.onRemove:
535 {
536 if(selectionBar)
537 {
538 selectionBar.removeAtUri(delegate.path)
539 }
540 }
541
542 Maui.GridBrowserDelegate
543 {
544 id: delegate
545 readonly property string path : model.path
546
547 template.imageWidth: control.gridView.itemSize
548 template.imageHeight: control.gridView.itemSize
549
550 anchors.fill: parent
551 anchors.margins: Maui.Handy.isMobile ? Maui.Style.space.tiny : Maui.Style.space.medium
552
553 template.labelSizeHint: 42
554 iconSizeHint: _private.gridIconSize
555 imageSource: settings.showThumbnails ? model.thumbnail : ""
556 template.fillMode: Image.PreserveAspectFit
557 template.maskRadius: 0
558 iconSource: model.icon
559 label1.text: model.label
560 label2.visible: delegate.height > 160 && model.mime
561 label2.font.pointSize: Maui.Style.fontSizes.tiny
562 label2.text: model.mime ? (model.mime === "inode/directory" ? (model.count ? model.count + i18nd("mauikitfilebrowsing", " items") : "") : Maui.Handy.formatSize(model.size)) : ""
563
564 isCurrentItem: parent.GridView.isCurrentItem || checked
565 tooltipText: model.label
566 checkable: control.selectionMode || checked
567 checked: (selectionBar ? selectionBar.contains(model.path) : false)
568 draggable: true
569 template.iconContainer.opacity: model.hidden == "true" ? 0.5 : 1
570
571 Drag.keys: ["text/uri-list"]
572 Drag.mimeData: {
573 "text/uri-list": filterSelection(control.path, model.path)
574 }
575
576 Maui.Icon
577 {
578 anchors.right: parent.right
579 anchors.top: parent.top
580 anchors.margins: Maui.Style.space.medium
581
582 visible: (model.issymlink == true) || (model.issymlink == "true")
583
584 source: "link"
585 color: Maui.Theme.textColor
586 isMask: true
587 height: Maui.Style.iconSizes.small
588 }
589
590 Loader
591 {
592 id: _injectorLoader
593 asynchronous: true
594 property var itemData : model
595 sourceComponent: control.delegateInjector
596 anchors.fill: parent
597 active: control.delegateInjector
598 }
599
600 onClicked: (mouse) =>
601 {
602 if ((mouse.button == Qt.LeftButton) && (mouse.modifiers & Qt.ControlModifier))
603 {
604 control.itemsSelected([index])
605 }else if((mouse.button == Qt.LeftButton) && (mouse.modifiers & Qt.ShiftModifier))
606 {
607 var lastSelectedIndex = findLastSelectedIndex(control.gridView.flickable, index)
608
609 if(lastSelectedIndex < 0)
610 {
611 return
612 }
613 control.itemsSelected(control.range(lastSelectedIndex, index))
614
615 }else
616 {
617 control.itemClicked(index)
618 }
619 control.currentIndex = index
620 }
621
622 onDoubleClicked:
623 {
624 control.currentIndex = index
625 control.itemDoubleClicked(index)
626 }
627
628 onPressAndHold:
629 {
630 if(!Maui.Handy.isTouch)
631 return
632
633 control.currentIndex = index
634 control.itemRightClicked(index)
635 }
636
637 onRightClicked:
638 {
639 control.currentIndex = index
640 control.itemRightClicked(index)
641 }
642
643 onToggled: (state) =>
644 {
645 control.currentIndex = index
646
647 control.itemToggled(index, state)
648 }
649
650 onContentDropped: (drop) =>
651 {
652 _dropMenu.urls = drop.urls.join(",")
653 _dropMenu.target = model.path
654 _dropMenu.show()
655 }
656
657 Connections
658 {
659 target: selectionBar
660
661 function onUriRemoved(uri)
662 {
663 if(uri === model.path)
664 delegate.checked = false
665 }
666
667 function onUriAdded(uri)
668 {
669 if(uri === model.path)
670 delegate.checked = true
671 }
672
673 function onCleared(uri)
674 {
675 delegate.checked = false
676 }
677 }
678 }
679 }
680
681 /**
682 * @brief Forces the view to re-organize the content entries into subgroups, that will depend on the current sorting key o group the entries by name, size, or date, etc.
683 * @note When this is invoked the view will go into the list mode - grouping is not supported in the grid view mode.
684 */
685 function groupBy()
686 {
687 var prop = ""
688 var criteria = ViewSection.FullString
689
690 switch(control.currentFMList.sortBy)
691 {
692 case FB.FMList.LABEL:
693 prop = "label"
694 criteria = ViewSection.FirstCharacter
695 break;
696 case FB.FMList.MIME:
697 prop = "mime"
698 break;
699 case FB.FMList.SIZE:
700 prop = "size"
701 break;
702 case FB.FMList.DATE:
703 prop = "date"
704 break;
705 case FB.FMList.MODIFIED:
706 prop = "modified"
707 break;
708 }
709
710 if(!prop)
711 {
712 control.currentView.section.property = ""
713 return
714 }
715
716 control.settings.viewType = FB.FMList.LIST_VIEW
717 control.currentView.section.property = prop
718 control.currentView.section.criteria = criteria
719 }
720
721 /**
722 * @private
723 */
724 function findLastSelectedIndex(view, limit)
725 {
726 var res = -1;
727 for(var i = 0; i < limit; i++)
728 {
729 if(view.itemAtIndex(i).checked)
730 {
731 res = i
732 }
733 }
734
735 return res;
736 }
737
738 /**
739 * @private
740 */
741 function range(start, end)
742 {
743 const isReverse = (start > end);
744 const targetLength = isReverse ? (start - end) + 1 : (end - start ) + 1;
745 const arr = new Array(targetLength);
746 const b = Array.apply(null, arr);
747 const result = b.map((discard, n) => {
748 return (isReverse) ? n + end : n + start;
749 });
750
751 return (isReverse) ? result.reverse() : result;
752 }
753
754 /**
755 * @brief Forces to focus the current browsing view first element.
756 */
757 function forceActiveFocus()
758 {
759 control.currentView.forceActiveFocus()
760 }
761}
A group of properties to tweak the browser in the FileBrowser component.
alias view
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nd(const char *domain, const char *text, const TYPE &arg...)
KIOWIDGETS_EXPORT DropJob * drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags=DefaultFlags)
QString path(const QString &relativePath)
QString label(StandardShortcut id)
const QList< QKeySequence > & end()
QString & fill(QChar ch, qsizetype size)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:51:27 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.