MauiKit File Browsing

FileBrowser.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 QtQml
22
23import QtQuick.Controls
24import QtQuick.Layouts
25
26import org.mauikit.controls as Maui
27import org.mauikit.filebrowsing as FB
28
29import "private" as Private
30
31/**
32 * @inherit MauiKit::Controls::Page
33 * @brief This control displays the file system entries in a given directory, allows subsequent browsing of the file hierarchy and exposes basic file handling functionality.
34 *
35 * This controls inherits from MauiKit Page, to checkout its inherited properties refer to the docs.
36 * @see MauiKit::Page
37 *
38 * @image html filebrowser2.png "FileBrowser control"
39 *
40 * @section features Features
41 *
42 * This control exposes a series of convenient properties and functions for filtering and sorting, for creating new entries, perform searches, and modify the entries.
43 *
44 * There are two different possible ways to display the contents: Grid and List, using the `settings.viewType` property.
45 * @see FMList::VIEW_TYPE
46 *
47 * More browsing properties can be tweaked via the exposed `settings` alias property.
48 * @see settings
49 *
50 * Some basic file item actions are implemented by default, like copy, cut, rename and remove, and exposed as methods.
51 * @see copy
52 * @see cut
53 * @see paste
54 * @see remove
55 *
56 * @note This component functionality can be easily expanded to be more feature rich, see for example the Maui Index application.
57 *
58 * This control allows to perform recursive searches, or simple filtering of elements in the current location by using the search bar. Besides these two, the user can quickly start fly-typing within the browser, to jump quickly to a matching entry.
59 *
60 * @subsection shorcuts Shortcuts
61 * This control supports multiple keyboard shortcuts, for selecting multiple elements, creating new entries, copying, deleting and renaming files.
62 *
63 * The supported keyboard shortcuts are:
64 *
65 * - `Ctrl + N` Create a new entry
66 * - `Ctrl + F` Open the search bar
67 * - `F5` Refresh/reload the content
68 * - `F2` Rename an entry
69 * - `Ctrl + A` Select all the entries
70 * - `Ctrl + N` Create a new entry
71 * - `Ctrl + Shift + ← ↑ → ↓` Select items in any direction
72 * - `Ctrl + Return` Open an entry
73 * - `Ctrl + V` Paste element in the clipboard to the current location
74 * - `Ctrl + X` Cut an entry
75 * - `Ctrl + C` Copy an entry
76 * - `Ctrl + Delete` Remove an entry
77 * - `Ctrl + Backspace` Clear selection/Go back to previous location
78 * - `Ctrl + Esc` Clear the selection/Clear the filter
79 *
80 * @section structure Structure
81 *
82 * This control inherits from MauiKit Page, so it has a header and footer. The header bar has a search field for performing searches, by default it is hidden.
83 *
84 * The footer bar by default has contextual buttons, depending on the current location. For example, for the trash location, it has contextual actions for emptying the trash can, etc.
85 *
86 * @warning It is recommended to not add elements to the footer or header bars, instead - if needed - wrap this control into a MauiKit Page, and utilize its toolbars.
87 *
88 * @code
89 * FB.FileBrowser
90 * {
91 * Maui.Controls.showCSD: true
92 * headBar.visible: true
93 * anchors.fill: parent
94 * currentPath: FB.FM.homePath()
95 * settings.viewType: FB.FMList.GRID_VIEW
96 * }
97 * @endcode
98 *
99 * <a href="https://invent.kde.org/maui/mauikit-filebrowser/examples/FileBrowser.qml">You can find a more complete example at this link.</a>
100 *
101 */
102Maui.Page
103{
104 id: control
105
106 onGoBackTriggered: control.goBack()
107 onGoForwardTriggered: control.goForward()
108
109 title: view.title
110
111 focus: true
112
113 flickable: control.currentView.flickable
115 floatingFooter: false
116
117 showTitle: false
118
119 /**
120 * @brief The current URL path for the directory or location.
121 * To list a directory path, or other location, use the right schema, some of them are `file://`, `webdav://`, `trash://`/, `tags://`, `fish://`.
122 * By default a URL without a well defined schema will be assumed as a local file path.
123 *
124 * @note KDE KIO is used as the back-end technology, so any of the supported plugins by KIO will also work here.
125 *
126 * @property string FileBrowser::currentPath
127 */
128 property alias currentPath : _browser.path
129 onCurrentPathChanged : _searchField.clear()
130
131 /**
132 * @brief A group of properties for controlling the sorting, listing and other behaviour of the browser.
133 * For more details check the BrowserSettings documentation.
134 * @see BrowserSettings
135 *
136 * @code
137 * settings.onlyDirs: true
138 * settings.sortBy: FB.FMList.LABEL
139 * settings.showThumbnails: false
140 * @endcode
141 *
142 * @property BrowserSettings FileBrowser::settings
143 */
144 readonly property alias settings : _browser.settings
146 /**
147 * @brief The browser could be in two different view states: [1]the file browsing or [2]the search view.
148 * This property gives access to the current view in use.
149 */
150 readonly property alias view : _stackView.currentItem
151
152 /**
153 * @brief An alias to the control listing the entries.
154 * This component is handled by BrowserView, and exposed for fine tuning its properties
155 * @property BrowserView FileBrowser::browser
156 */
157 readonly property alias browser : _browser
159 /**
160 * @brief Drop area component, for dropping files.
161 * By default some of the drop actions are handled, for other type of URIs this alias can be used to handle those.
162 *
163 * The urlsDropped signal is emitted once one or multiple valid file URLs are dropped, and a popup contextual menu is opened with the supported default actions.
164 * @property DropArea FileBrowser::dropArea
165 */
166 readonly property alias dropArea : _dropArea
167
168 /**
169 * @brief Current index of the item selected in the file browser.
170 */
171 property int currentIndex : -1
172
173 Binding on currentIndex
174 {
175 value: currentView.currentIndex
176 }
177
178 /**
179 * @brief Current view of the file browser. Possible views are:
180 * - List = MauiKit::ListBrowser
181 * - Grid = MauiKit::GridBrowser
182 * @property Item FileBrowser::currentView
183 */
184 readonly property QtObject currentView : _stackView.currentItem.browser
185
186 /**
187 * @brief The file browser model list.
188 * The List and Grid views use the same FMList.
189 * @see FMList
190 */
191 readonly property FB.FMList currentFMList : view.currentFMList
192
193 /**
194 * @brief The file browser model controller. This is the controller which allows for filtering, index mapping, and sorting.
195 * @see MauiModel
196 */
197 readonly property Maui.BaseModel currentFMModel : view.currentFMModel
198
199 /**
200 * @brief Whether the file browser current view is the search view.
201 */
202 readonly property bool isSearchView : _stackView.currentItem.objectName === "searchView"
204 /**
205 * @brief Whether the file browser enters selection mode, allowing the selection of multiple items.
206 * This will make all the browsing entries checkable.
207 * @property bool FileBrowser::selectionMode
208 */
209 property alias selectionMode: _browser.selectionMode
210
211 /**
212 * @brief Size of the items in the grid view.
213 * The size is for the combined thumbnail/icon and the title label.
214 *
215 * @property int FileBrowser::gridItemSize
216 */
217 property alias gridItemSize : _browser.gridItemSize
218
219 /**
220 * @brief Size of the items in the list view.
221 * @note The size is actually taken as the size of the icon thumbnail. And it is mapped to the nearest size of the standard icon sizes.
222 * @property int FileBrowser::listItemSize
223 */
224 property alias listItemSize : _browser.listItemSize
225
226 /**
227 * @brief The SelectionBar to be used for adding items to the selected group.
228 * This is optional, but if it is not set, then some feature will not work, such as selection mode, selection shortcuts etc.
229 *
230 * @see MauiKit::SelectionBar
231 *
232 * @code
233 * Maui.Page
234{
235 Maui.Controls.showCSD: true
236 anchors.fill: parent
237 floatingFooter: true
238
239 FB.FileBrowser
240 {
241 Maui.Controls.showCSD: true
242
243 anchors.fill: parent
244 currentPath: FB.FM.homePath()
245 settings.viewType: FB.FMList.GRID_VIEW
246 selectionBar: _selectionBar
247 }
248
249 footer: Maui.SelectionBar
250 {
251 id: _selectionBar
252 anchors.horizontalCenter: parent.horizontalCenter
253 width: Math.min(parent.width-(Maui.Style.space.medium*2), implicitWidth)
254 maxListHeight: root.height - (Maui.Style.contentMargins*2)
256 Action
257 {
258 icon.name: "love"
259 onTriggered: console.log(_selectionBar.getSelectedUrisString())
261
262 Action
263 {
264 icon.name: "folder"
266
267 Action
268 {
269 icon.name: "list-add"
271
272 onExitClicked: clear()
273 }
274}
275 * @endcode
276 */
277 property Maui.SelectionBar selectionBar : null
278
279 /**
280 * @brief Whether the browser is on a read only mode, and modifications are now allowed, such as pasting, moving, removing or renaming.
281 * @property bool FileBrowser::readOnly
282 */
283 property alias readOnly : _browser.readOnly
284
286 /**
287 * @brief Emitted when an item has been clicked.
288 * @param index the index position of the item
289 */
290 signal itemClicked(int index)
291
292 /**
293 * @brief Emitted when an item has been double clicked.
294 * @param index the index position of the item
295 */
296 signal itemDoubleClicked(int index)
297
298 /**
299 * @brief Emitted when an item has been right clicked. On mobile devices this is translated from a long press and release.
300 * @param index the index position of the item
301 */
302 signal itemRightClicked(int index)
303
304 /**
305 * @brief Emitted when an item left emblem badge has been clicked.
306 * This is actually a signal to checkable item being toggled.
307 * @param index the index position of the item
308 */
309 signal itemLeftEmblemClicked(int index)
310
311
312 /**
313 * @brief Emitted when an empty area of the browser has been right clicked.
314 */
315 signal rightClicked()
316
317 /**
318 * @brief Emitted when an empty area of the browser has been clicked.
319 * @param mouse the object with the event information
320 */
321 signal areaClicked(var mouse)
322
323
324 /**
325 * @brief A key, physical or not, has been pressed.
326 * @param event the event object contains the relevant information
327 */
328 signal keyPress(var event)
329
330 /**
331 * @brief Emitted when a list of file URLS has been dropped onto the file browser area.
332 * @param urls the list of URLs of the files dropped
333 */
334 signal urlsDropped(var urls)
335
336 headBar.forceCenterMiddleContent: isWide
337 headBar.visible: control.settings.searchBarVisible
338 headBar.leftContent: Loader
339 {
340 asynchronous: true
341 active: control.isSearchView
342 visible: active
343 sourceComponent: ToolButton
344 {
345 text: i18nd("mauikitfilebrowsing", "Back")
346 icon.name: "go-previous"
347 onClicked: control.quitSearch()
348 }
349 }
350
351 headBar.middleContent: Maui.SearchField
352 {
353 id: _searchField
354 focus: true
355 Layout.fillWidth: true
356 Layout.maximumWidth: 500
357 Layout.alignment: Qt.AlignCenter
358 placeholderText: _filterButton.checked ? i18nd("mauikitfilebrowsing", "Filter") : ("Search")
359 inputMethodHints: Qt.ImhNoAutoUppercase
360
361 onAccepted:
362 {
363 if(_filterButton.checked)
364 {
365 if(text.includes(","))
366 {
367 control.view.filters = text.split(",")
368 }else
369 {
370 control.view.filter = text
371 }
372
373 }else
374 {
375 control.search(text)
376 }
377 }
378
379 onCleared:
380 {
381 if(_filterButton.checked)
382 {
383 control.currentFMModel.clearFilters()
384 }
385 }
386
387 onTextChanged:
388 {
389 if(_filterButton.checked)
390 _searchField.accepted()
391 }
392
393 Keys.enabled: _filterButton.checked
394 Keys.onPressed:
395 {
396 // Shortcut for clearing selection
397 if(event.key == Qt.Key_Up)
398 {
399 control.currentView.forceActiveFocus()
400 }
401 }
402
403 actions: Action
404 {
405 id: _filterButton
406 icon.name: "view-filter"
407 // text: i18nd("mauikitfilebrowsing", "Filter")
408 checkable: true
409 checked: true
410 onTriggered:
411 {
412 control.view.filter = ""
413 _searchField.clear()
414 _searchField.forceActiveFocus()
415 }
416 }
417 }
418
419 footBar.visible: control.currentPath.startsWith("trash:/")
420
421 footerPositioning: ListView.InlineFooter
422
423 footBar.rightContent: Button
424 {
425 visible: control.currentPath.startsWith("trash:/")
426 icon.name: "trash-empty"
427 text: i18nd("mauikitfilebrowsing", "Empty Trash")
428 onClicked: FB.FM.emptyTrash()
429 }
430
431 Component
432 {
433 id: _quitSearchDialogComponent
434
435 Maui.InfoDialog
436 {
437 onClosed: destroy()
438 title: i18n("Quit")
439 message: i18n("Are you sure you want to quit the current search in progress?")
440 onAccepted:
441 {
442 _stackView.pop()
443 _browser.forceActiveFocus()
444 }
445
446 onRejected: close()
447 }
448 }
449
450 Component
451 {
452 id: removeDialogComponent
453
454 FB.FileListingDialog
455 {
456 id: _removeDialog
457
458 property double freedSpace : calculateFreedSpace(urls)
459
460 title: i18nd("mauikitfilebrowsing", "Removing %1 files", urls.length)
461 message: i18nd("mauikitfilebrowsing", "Delete %1 \nTotal freed space %2", (Maui.Handy.isLinux ? "or move to trash?" : "? This action can not be undone."), Maui.Handy.formatSize(freedSpace))
462 onClosed: destroy()
463 actions: [
464 Action
465 {
466 text: i18nd("mauikitfilebrowsing", "Cancel")
467 onTriggered: _removeDialog.close()
468 },
469
470 Action
471 {
472 text: i18nd("mauikitfilebrowsing", "Delete")
473 Maui.Controls.status: Maui.Controls.Negative
474 onTriggered:
475 {
476 control.currentFMList.removeFiles(urls)
477 close()
478 }
479 },
480 Action
481 {
482 text: i18nd("mauikitfilebrowsing", "Trash")
483 Maui.Controls.status: Maui.Controls.Neutral
484 enabled: Maui.Handy.isLinux
485 onTriggered:
486 {
487 control.currentFMList.moveToTrash(urls)
488 close()
489 }
490 }
491 ]
492
493 function calculateFreedSpace(urls)
494 {
495 var size = 0
496 for(var url of urls)
497 {
498 size += parseFloat(FB.FM.getFileInfo(url).size)
499 }
500
501 return size
502 }
503 }
504 }
505
506 Component
507 {
508 id: newDialogComponent
509 //
510 Maui.InputDialog
511 {
512 id: _newDialog
513onClosed: destroy()
514 // title: _newDirOp.checked ? i18nd("mauikitfilebrowsing", "New folder") : i18nd("mauikitfilebrowsing", "New file")
515 message: i18nd("mauikitfilebrowsing", "Create a new folder or a file with a custom name.")
516
517 template.iconSource: _newDirOp.checked ? "folder" : FB.FM.getIconName(textEntry.text)
518 template.iconVisible: true
519
520 onFinished: (text) =>
521 {
522 if(_newDirOp.checked)
523 {
524 control.currentFMList.createDir(text)
525 return
526 }
527 if(_newFileOp.checked)
528 {
529 control.currentFMList.createFile(text)
530 return
531 }
532 }
533
534 textEntry.placeholderText: i18nd("mauikitfilebrowsing", "Name")
535
537 {
538 id: _newActions
539 expanded: true
540 autoExclusive: true
541 display: ToolButton.TextBesideIcon
542
543 Action
544 {
545 id: _newDirOp
546 icon.name: "folder-new"
547 text: i18nd("mauikitfilebrowsing", "Folder")
548 checked: String(_newDialog.textEntry.text).indexOf(".") < 0
549 }
550
551 Action
552 {
553 id: _newFileOp
554 icon.name: "document-new"
555 text: i18nd("mauikitfilebrowsing", "File")
556 checked: String(_newDialog.textEntry.text).indexOf(".") >= 0
557 }
558 }
559 }
560 }
561
562 Component
563 {
564 id: renameDialogComponent
565
566 Maui.InputDialog
567 {
568 id: _renameDialog
569
570 property var item : ({})
571onClosed: destroy()
572 // title: i18nd("mauikitfilebrowsing", "Rename")
573 message: i18nd("mauikitfilebrowsing", "Enter the new name for the file.")
574
575 template.iconSource: item.icon
576 template.imageSource: item.thumbnail
577
578 textEntry.text: item.label
579 textEntry.placeholderText: i18nd("mauikitfilebrowsing", "New name")
580
581 onFinished: control.currentFMList.renameFile(item.path, textEntry.text)
582 onRejected: close()
583
584 // acceptButton.text: i18nd("mauikitfilebrowsing", "Rename")
585 // rejectButton.text: i18nd("mauikitfilebrowsing", "Cancel")
586
587 onOpened:
588 {
589 item = control.currentFMModel.get(control.currentIndex)
590
591 if(_renameDialog.textEntry.text.lastIndexOf(".") >= 0)
592 {
593 _renameDialog.textEntry.select(0, _renameDialog.textEntry.text.lastIndexOf("."))
594 }else
595 {
596 _renameDialog.textEntry.selectAll()
597 }
598 }
599 }
600 }
601
602 Component
603 {
604 id: _newTagDialogComponent
605 FB.NewTagDialog {onClosed: destroy()}
606 }
607
608 /**
609 * @private
610 */
611 property string typingQuery
612
613 Maui.Chip
614 {
615 z: control.z + 99999
616 Maui.Theme.colorSet:Maui.Theme.Complementary
617 visible: _typingTimer.running
618 label.text: typingQuery
619 anchors.left: parent.left
620 anchors.top: parent.top
621 showCloseButton: false
622 anchors.margins: Maui.Style.space.medium
623 }
624
625 Timer
626 {
627 id: _typingTimer
628 interval: 250
629 onTriggered:
630 {
631 const index = control.currentFMList.indexOfName(typingQuery)
632 if(index > -1)
633 {
634 console.log("FOUDN TRYPIGN IDNEX", index)
635 control.currentIndex = control.currentFMModel.mappedFromSource(index)
636 }
637
638 typingQuery = ""
639 }
640 }
641
642 Connections
643 {
644 target: control.currentView
645 ignoreUnknownSignals: true
646
647 function onKeyPress(event)
648 {
649 const index = control.currentIndex
650 const item = control.currentFMModel.get(index)
651
652 var pat = /^([a-zA-Z0-9 _-]+)$/
653
654 if(event.count === 1 && pat.test(event.text))
655 {
656 typingQuery += event.text
657 _typingTimer.restart()
658 event.accepted = true
659 }
660
661 // Shortcuts for refreshing
662 if((event.key === Qt.Key_F5))
663 {
664 control.currentFMList.refresh()
665 event.accepted = true
666 }
667
668 // Shortcuts for renaming
669 if((event.key === Qt.Key_F2))
670 {
671 var dialog = renameDialogComponent.createObject(control)
672 dialog.open()
673 event.accepted = true
674 }
675
676 // Shortcuts for selecting file
677 if((event.key === Qt.Key_A) && (event.modifiers & Qt.ControlModifier))
678 {
679 control.selectAll()
680 event.accepted = true
681 }
682
683 if((event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Down || event.key === Qt.Key_Up) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
684 {
685 if(control.selectionBar && control.selectionBar.contains(item.path))
686 {
687 control.selectionBar.removeAtUri(item.path)
688 }else
689 {
690 control.addToSelection(item)
691 }
692 //event.accepted = true
693 }
694
695 //shortcut for opening files
696 if(event.key === Qt.Key_Return)
697 {
698 control.openItem(index)
699 event.accepted = true
700 }
701
702 // Shortcut for pasting an item
703 if((event.key == Qt.Key_V) && (event.modifiers & Qt.ControlModifier))
704 {
705 console.log("trying to do paste with keyboard shorcut")
706 control.paste()
707 event.accepted = true
708 }
709
710 // Shortcut for cutting an item
711 if((event.key == Qt.Key_X) && (event.modifiers & Qt.ControlModifier))
712 {
713 const urls = filterSelection(control.currentPath, item.path)
714 control.cut(urls)
715 event.accepted = true
716 }
717
718 // Shortcut for copying an item
719 if((event.key == Qt.Key_C) && (event.modifiers & Qt.ControlModifier))
720 {
721 const urls = filterSelection(control.currentPath, item.path)
722 control.copy(urls)
723 event.accepted = true
724 }
725
726 // Shortcut for removing an item
727 if(event.key === Qt.Key_Delete)
728 {
729 const urls = filterSelection(control.currentPath, item.path)
730 control.remove(urls)
731 event.accepted = true
732 }
733
734 // Shortcut for going back in browsing history
735 if(event.key === Qt.Key_Backspace || event.key == Qt.Key_Back)
736 {
737 if(control.selectionBar && control.selectionBar.count> 0)
738 {
739 control.selectionBar.clear()
740 }
741 else
742 {
743 control.goBack()
744 }
745 event.accepted = true
746 }
747
748 // Shortcut for clearing selection and filtering
749 if(event.key === Qt.Key_Escape) //TODO not working, the event is not catched or emitted or is being accepted else where?
750 {
751 if(control.selectionBar && control.selectionBar.count > 0)
752 {
753 control.selectionBar.clear()
754 }
755
756 control.view.filter = ""
757 event.accepted = true
758 }
759
760 //Shortcut for opening filtering
761 if((event.key === Qt.Key_F) && (event.modifiers & Qt.ControlModifier))
762 {
763 control.toggleSearchBar()
764 event.accepted = true
765 }
766
767 control.keyPress(event)
768 }
769
770 function onItemsSelected(indexes)
771 {
772 if(indexes.length)
773 {
774 control.currentIndex = indexes[0]
775 control.selectIndexes(indexes)
776 }
777 }
778
779 function onItemClicked(index)
780 {
781 control.currentIndex = index
782 control.itemClicked(index)
783 control.currentView.forceActiveFocus()
784 }
785
786 function onItemDoubleClicked(index)
787 {
788 control.currentIndex = index
789 control.itemDoubleClicked(index)
790 control.currentView.forceActiveFocus()
791 }
792
793 function onItemRightClicked(index)
794 {
795 control.currentIndex = index
796 control.itemRightClicked(index)
797 control.currentView.forceActiveFocus()
798 }
799
800 function onItemToggled(index, state)
801 {
802 const item = control.currentFMModel.get(index)
803
804 if(control.selectionBar && control.selectionBar.contains(item.path))
805 {
806 control.selectionBar.removeAtUri(item.path)
807 }else
808 {
809 control.addToSelection(item)
810 }
811 control.itemLeftEmblemClicked(index)
812 control.currentView.forceActiveFocus()
813 }
814
815 function onAreaClicked(mouse)
816 {
817 if(control.isSearchView)
818 return
819
820 if(!Maui.Handy.isMobile && mouse.button === Qt.RightButton)
821 {
822 control.rightClicked()
823 }
824
825 control.areaClicked(mouse)
826 control.currentView.forceActiveFocus()
827 }
828 }
829
830 Maui.ContextualMenu
831 {
832 id: _dropMenu
833 property string urls
834 enabled: !control.isSearchView
835
836 MenuItem
837 {
838 enabled: !control.readOnly
839 text: i18nd("mauikitfilebrowsing", "Copy Here")
840 icon.name: "edit-copy"
841 onTriggered:
842 {
843 const urls = _dropMenu.urls.split(",")
844 console.log("COPY THESE URLS,", urls, _dropMenu.urls)
845 control.currentFMList.copyInto(urls)
846 }
847 }
848
849 MenuItem
850 {
851 enabled: !control.readOnly
852 text: i18nd("mauikitfilebrowsing", "Move Here")
853 icon.name: "edit-move"
854 onTriggered:
855 {
856 const urls = _dropMenu.urls.split(",")
857 control.currentFMList.cutInto(urls)
858 }
859 }
860
861 MenuItem
862 {
863 enabled: !control.readOnly
864
865 text: i18nd("mauikitfilebrowsing", "Link Here")
866 icon.name: "edit-link"
867 onTriggered:
868 {
869 const urls = _dropMenu.urls.split(",")
870 for(var i in urls)
871 control.currentFMList.createSymlink(urls[i])
872 }
873 }
874
875 MenuItem
876 {
877 enabled: FB.FM.isDir(_dropMenu.urls.split(",")[0])
878 text: i18nd("mauikitfilebrowsing", "Open Here")
879 icon.name: "folder-open"
880 onTriggered:
881 {
882 const urls = _dropMenu.urls.split(",")
883 control.browser.path = urls[0]
884 }
885 }
886
887 MenuSeparator {}
888
889 MenuItem
890 {
891 text: i18nd("mauikitfilebrowsing", "Cancel")
892 icon.name: "dialog-cancel"
893 onTriggered: _dropMenu.close()
894 }
895 }
896
897 StackView
898 {
899 id: _stackView
900 anchors.fill: parent
901
902 initialItem: DropArea
903 {
904 id: _dropArea
905
906 readonly property alias browser : _browser
907 readonly property alias currentFMList : _browser.currentFMList
908 readonly property alias currentFMModel: _browser.currentFMModel
909 property alias filter: _browser.filter
910 property alias filters: _browser.filters
911 readonly property alias title : _browser.title
912
913 onDropped: (drop) =>
914 {
915 if(drop.hasUrls)
916 {
917 _dropMenu.urls = drop.urls.join(",")
918 _dropMenu.show()
919 control.urlsDropped(drop.urls)
920 }
921 }
922
923 opacity: _dropArea.containsDrag ? 0.5 : 1
924
925 Private.BrowserView
926 {
927 id: _browser
928 anchors.fill: parent
929
930 Binding on currentIndex
931 {
932 value: control.currentIndex
933 restoreMode: Binding.RestoreBindingOrValue
934 }
935
936 Loader
937 {
938 active: (control.currentPath === "tags://" || control.currentPath === "tags:///") && !control.readOnly
939 visible: active
940 asynchronous: true
941
942 anchors.right: parent.right
943 anchors.bottom: parent.bottom
944 anchors.rightMargin: Maui.Style.toolBarHeight
945 anchors.bottomMargin: Maui.Style.toolBarHeight + control.flickable.bottomMargin
946
947 sourceComponent: Maui.FloatingButton
948 {
949 icon.name : "list-add"
950 onClicked:
951 {
952 var dialog = _newTagDialogComponent.createObject(control)
953 dialog.open()
954 }
955 }
956 }
957 }
958 }
959
960 Component
961 {
962 id: _searchBrowserComponent
963
964 Private.BrowserView
965 {
966 id: _searchBrowser
967 property alias browser : _searchBrowser
968 readOnly: true
969 path: control.currentPath
970 Binding on currentIndex
971 {
972 value: control.currentIndex
973 restoreMode: Binding.RestoreBindingOrValue
974 }
975
976 objectName: "searchView"
977 gridItemSize: control.gridItemSize
978 listItemSize: control.listItemSize
979
980 currentFMList.autoLoad: false
981 settings.viewType: control.settings.viewType
982 settings.sortBy: control.settings.sortBy
983 settings.showHiddenFiles: control.settings.showHiddenFiles
984 settings.group: control.settings.group
985 settings.foldersFirst: control.settings.foldersFirst
986
987 }
988 }
989 }
990
991 Component.onCompleted:
992 {
993 control.currentView.forceActiveFocus()
994 }
995
996 /**
997 * @brief Copy the given file URLs to the clipboard
998 * @param urls the set of URLs to be copied
999 **/
1000 function copy(urls)
1001 {
1002 if(urls.length <= 0)
1003 {
1004 return
1005 }
1006
1007 Maui.Handy.copyToClipboard({"urls": urls}, false)
1008 }
1009
1010 /**
1011 * @brief Add the given URLs to the clipboard and mark them as a cut operation
1012 * @param urls the set of URLs to cut
1013 **/
1014 function cut(urls)
1015 {
1016 if(control.readOnly)
1017 return
1018
1019 if(urls.length <= 0)
1020 {
1021 return
1022 }
1023
1024 Maui.Handy.copyToClipboard({"urls": urls}, true)
1025 }
1026
1027 /**
1028 * Paste the contents of the clipboard into the current location, if supported.
1029 **/
1030 function paste()
1031 {
1032 control.currentFMList.paste()
1033 }
1034
1035 /**
1036 * Remove the given URLs.Array()this will launch a dialog to confirm this action.
1037 * @param urls the set of URLs to be removed
1038 **/
1039 function remove(urls)
1040 {
1041 if(urls.length <= 0)
1042 {
1043 return
1044 }
1045
1046 var dialog = removeDialogComponent.createObject(control)
1047 dialog.urls = urls
1048 dialog.open()
1049 }
1050
1051 /**
1052 * @brief Given an index position of a element, try to open it, it can be a directory, a file or an executable.
1053 *
1054 **/
1055 function openItem(index)
1056 {
1057 const item = control.currentFMModel.get(index)
1058 const path = item.path
1059
1060 switch(control.currentFMList.pathType)
1061 {
1062 case FB.FMList.CLOUD_PATH: //TODO deprecrated and needs to be removed or clean up for 1.1
1063 if(item.isdir === "true")
1064 {
1065 control.openFolder(path)
1066 }
1067 else
1068 {
1069 FB.FM.openCloudItem(item)
1070 }
1071 break;
1072 default:
1073 if(control.selectionMode && item.isdir == "false")
1074 {
1075 if(control.selectionBar && control.selectionBar.contains(item.path))
1076 {
1077 control.selectionBar.removeAtPath(item.path)
1078 }else
1079 {
1080 control.addToSelection(item)
1081 }
1082 }
1083 else
1084 {
1085 if(item.isdir == "true")
1086 {
1087 control.openFolder(path)
1088 }
1089 else
1090 {
1091 control.openFile(path)
1092 }
1093 }
1094 }
1095 }
1096
1097 /**
1098 * @brief Open a file of the given path URL in the dedicated application
1099 * @param path The URL of the file
1100 **/
1101 function openFile(path)
1102 {
1103 FB.FM.openUrl(path)
1104 }
1105
1106 /**
1107 * @brief Open a folder location
1108 * @param path the URL of the folder location
1109 **/
1110 function openFolder(path)
1111 {
1112 if(!String(path).length)
1113 {
1114 return;
1115 }
1116
1117 if(control.isSearchView)
1118 {
1119 control.quitSearch()
1120 }
1121
1122 control.currentPath = path
1123 _browser.forceActiveFocus()
1124 }
1125
1126 /**
1127 * @brief Go back to the previous location
1128 **/
1129 function goBack()
1130 {
1131 openFolder(control.currentFMList.previousPath())
1132 }
1133
1134 /**
1135 * @brief Go forward to the location before going back
1136 **/
1137 function goForward()
1138 {
1139 openFolder(control.currentFMList.posteriorPath())
1140 }
1141
1142 /**
1143 * @brief Go to the location parent directory
1144 **/
1145 function goUp()
1146 {
1147 openFolder(control.currentFMList.parentPath)
1148 }
1149
1150 /**
1151 * @brief Selects the next item in the view
1152 */
1153 function nextItem()
1154 {
1155 if(_browser.viewType === Maui.AltBrowser.ViewType.List)
1156 _browser.currentView.flickable.incrementCurrentIndex()
1157 else
1158 _browser.currentView.flickable.moveCurrentIndexRight()
1159
1160 }
1161
1162 /**
1163 * @brief Selects the previous item in the view
1164 */
1165 function previousItem()
1166 {
1167 if(_browser.viewType === Maui.AltBrowser.ViewType.List)
1168 _browser.currentView.flickable.decrementCurrentIndex()
1169 else
1170 _browser.currentView.flickable.moveCurrentIndexLeft()
1171 }
1172
1173 /**
1174 * @brief Add an item to the selection
1175 * @param item the item object/map representing the file to be added to the selection
1176 * @warning For this to work the implementation needs to have passed a `selectionBar`
1177 * @see selectionBar
1178 **/
1179 function addToSelection(item)
1180 {
1181 if(control.selectionBar == null || item.path.startsWith("tags://") || item.path.startsWith("applications://"))
1182 {
1183 return
1184 }
1185
1186 control.selectionBar.append(item.path, item)
1187 }
1188
1189 /**
1190 * @brief Given a list of indexes add them to the selection
1191 * @param indexes list of index positions
1192 **/
1193 function selectIndexes(indexes)
1194 {
1195 if(control.selectionBar == null)
1196 {
1197 return
1198 }
1199
1200 for(var i in indexes)
1201 addToSelection(control.currentFMModel.get(indexes[i]))
1202 }
1203
1204 /**
1205 * @brief Forces to select all the entries
1206 *
1207 * @bug If there are too many entries, this could freeze the UI
1208 **/
1209 function selectAll() //TODO for now dont select more than 100 items so things dont freeze or break
1210 {
1211 if(control.selectionBar == null)
1212 {
1213 return
1214 }
1215
1216 selectIndexes([...Array( control.currentFMList.count ).keys()])
1217 }
1218
1219 /**
1220 * @brief Add a bookmark to a given list of paths
1221 * @param paths a group of directory URLs to be bookmarked
1222 **/
1223 function bookmarkFolder(paths) //multiple paths
1224 {
1225 for(var i in paths)
1226 {
1227 FB.FM.bookmark(paths[i])
1228 }
1229 }
1230
1231 /**
1232 * @brief Open/close the search bar
1233 */
1234 function toggleSearchBar() //only opens the searchbar toolbar, not the search view page
1235 {
1236 if(control.settings.searchBarVisible)
1237 {
1238 control.settings.searchBarVisible = false
1239 quitSearch()
1240 _browser.forceActiveFocus()
1241 }else
1242 {
1243 control.settings.searchBarVisible = true
1244 _searchField.forceActiveFocus()
1245 }
1246 }
1247
1248 /**
1249 * @brief Forces to open the search view and start a search.
1250 **/
1251 function openSearch() //opens the search view and focuses the search field
1252 {
1253 if(!control.isSearchView)
1254 {
1255 _stackView.push(_searchBrowserComponent)
1256 }
1257 control.settings.searchBarVisible = true
1258 _searchField.forceActiveFocus()
1259 }
1260
1261 /**
1262 * @brief Forces to close the search view, and return to the browsing view.
1263 **/
1264 function quitSearch()
1265 {
1266 if(control.currentView.loading)
1267 {
1268 var dialog = _quitSearchDialogComponent.createObject(control)
1269 dialog.open()
1270 return
1271 }
1272
1273 _stackView.pop()
1274 _browser.forceActiveFocus()
1275 }
1276
1277 /**
1278 * @brief Perform a recursive search starting form the current location and down to all the children directories.
1279 * @param query the text query to match against
1280 **/
1281 function search(query)
1282 {
1283 openSearch()
1284 _searchField.text = query
1285
1286 _stackView.currentItem.title = i18nd("mauikitfilebrowsing", "Search: %1", query)
1287 _stackView.currentItem.currentFMList.search(query, true)
1288
1289 _stackView.currentItem.forceActiveFocus()
1290 }
1291
1292 /**
1293 * @brief Opens a dialog for typing the name of the new item.
1294 * The new item can be a file or directory.
1295 **/
1296 function newItem()
1297 {
1298 if(control.isSearchView)
1299 return;
1300
1301 var dialog = newDialogComponent.createObject(control)
1302 dialog.open()
1303 dialog.forceActiveFocus()
1304 }
1305
1306 /**
1307 * @brief Opens a dialog for typing the new name of the item
1308 * This will target the current selected item in the browser view
1309 **/
1310 function renameItem()
1311 {
1312 if(control.isSearchView)
1313 return;
1314
1315 var dialog = renameDialogComponent.createObject(control)
1316 dialog.open()
1317 dialog.forceActiveFocus()
1318 }
1319
1320 /**
1321 * @brief Opens a dialog to confirm this operation
1322 * This will target the current selected item in the browser view
1323 **/
1324 function removeItem()
1325 {
1326 if(control.isSearchView)
1327 return;
1328
1329 var dialog = renameDialogComponent.createObject(control)
1330 dialog.open()
1331 dialog.forceActiveFocus()
1332 }
1333
1334 /**
1335 * @brief Filters the contents of the selection to the current path.
1336 * @note Keep in mind that the selection bar can have entries from multiple different locations. With this method only the entries which are inside the `currentPath` will be returned.
1337 *
1338 * @param currentPath The currentPath must be a directory, so the selection entries can be compared as its parent directory.
1339 * @param itemPath The itemPath is a default item path in case the selectionBar is empty
1340 *
1341 * @return the list of entries in the selection that match the currentPath as their parent directory
1342 **/
1343 function filterSelection(currentPath, itemPath)
1344 {
1345 var res = []
1346
1347 if(selectionBar && selectionBar.count > 0 && selectionBar.contains(itemPath))
1348 {
1349 const uris = selectionBar.uris
1350 for(var uri of uris)
1351 {
1352 if(String(FB.FM.parentDir(uri)) === currentPath)
1353 {
1354 res.push(uri)
1355 }
1356 }
1357
1358 } else
1359 {
1360 res = [itemPath]
1361 }
1362
1363 return res
1364 }
1365
1366 /**
1367 * @brief Forces to focus the current view.
1368 */
1369 function forceActiveFocus()
1370 {
1371 control.currentView.forceActiveFocus()
1372 }
1373}
bool visible
The FM class stands for File Management, and exposes methods for file listing, browsing and handling,...
Definition fm.h:104
FBFMList currentFMList
The file browser model list.
void itemClicked(int index)
Emitted when an item has been clicked.
void urlsDropped(var urls)
Emitted when a list of file URLS has been dropped onto the file browser area.
MauiSelectionBar selectionBar
The SelectionBar to be used for adding items to the selected group.
void itemRightClicked(int index)
Emitted when an item has been right clicked.
void keyPress(var event)
A key, physical or not, has been pressed.
MauiBaseModel currentFMModel
The file browser model controller.
alias settings
A group of properties for controlling the sorting, listing and other behaviour of the browser.
QtObject currentView
Current view of the file browser.
void areaClicked(var mouse)
Emitted when an empty area of the browser has been clicked.
alias view
The browser could be in two different view states: [1]the file browsing or [2]the search view.
alias dropArea
Drop area component, for dropping files.
int currentIndex
Current index of the item selected in the file browser.
void itemDoubleClicked(int index)
Emitted when an item has been double clicked.
alias browser
An alias to the control listing the entries.
void rightClicked()
Emitted when an empty area of the browser has been right clicked.
bool isSearchView
Whether the file browser current view is the search view.
void itemLeftEmblemClicked(int index)
Emitted when an item left emblem badge has been clicked.
alias readOnly
Whether the browser is on a read only mode, and modifications are now allowed, such as pasting,...
alias selectionMode
Whether the file browser enters selection mode, allowing the selection of multiple items.
alias gridItemSize
Size of the items in the grid view.
alias listItemSize
Size of the items in the list view.
alias currentPath
The current URL path for the directory or location.
Q_SCRIPTABLE CaptureState status()
QString i18nd(const char *domain, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
KIOWIDGETS_EXPORT DropJob * drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags=DefaultFlags)
QString path(const QString &relativePath)
KGuiItem remove()
const QList< QKeySequence > & close()
const QList< QKeySequence > & cut()
const QList< QKeySequence > & paste()
QString label(StandardShortcut id)
const QList< QKeySequence > & copy()
const QList< QKeySequence > & renameFile()
const QList< QKeySequence > & selectAll()
QString & append(QChar ch)
QString left(qsizetype n) const const
QTextStream & right(QTextStream &stream)
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 11:55:53 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.