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

KDE's Doxygen guidelines are available online.