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())
260 }
261
262 Action
263 {
264 icon.name: "folder"
265 }
266
267 Action
268 {
269 icon.name: "list-add"
270 }
271
272 onExitClicked: clear()
273 }
274}
275 * @endcode
276 */
277 property Maui.SelectionBar selectionBar : null
278
279 /**
280 * @brief An alias to the currently loaded dialog, if not dialog is loaded then null.
281 *
282 * @property Dialog FileBrowser::dialog
283 */
284 readonly property alias dialog : dialogLoader.item
285
286 /**
287 * @brief Whether the browser is on a read only mode, and modifications are now allowed, such as pasting, moving, removing or renaming.
288 * @property bool FileBrowser::readOnly
289 */
290 property alias readOnly : _browser.readOnly
291
293 /**
294 * @brief Emitted when an item has been clicked.
295 * @param index the index position of the item
296 */
297 signal itemClicked(int index)
298
299 /**
300 * @brief Emitted when an item has been double clicked.
301 * @param index the index position of the item
302 */
303 signal itemDoubleClicked(int index)
304
305 /**
306 * @brief Emitted when an item has been right clicked. On mobile devices this is translated from a long press and release.
307 * @param index the index position of the item
308 */
309 signal itemRightClicked(int index)
310
311 /**
312 * @brief Emitted when an item left emblem badge has been clicked.
313 * This is actually a signal to checkable item being toggled.
314 * @param index the index position of the item
315 */
316 signal itemLeftEmblemClicked(int index)
317
318
319 /**
320 * @brief Emitted when an empty area of the browser has been right clicked.
321 */
322 signal rightClicked()
323
324 /**
325 * @brief Emitted when an empty area of the browser has been clicked.
326 * @param mouse the object with the event information
327 */
328 signal areaClicked(var mouse)
329
330
331 /**
332 * @brief A key, physical or not, has been pressed.
333 * @param event the event object contains the relevant information
334 */
335 signal keyPress(var event)
336
337 /**
338 * @brief Emitted when a list of file URLS has been dropped onto the file browser area.
339 * @param urls the list of URLs of the files dropped
340 */
341 signal urlsDropped(var urls)
342
343 headBar.forceCenterMiddleContent: isWide
344 headBar.visible: control.settings.searchBarVisible
345 headBar.leftContent: Loader
346 {
347 asynchronous: true
348 active: control.isSearchView
349 visible: active
350 sourceComponent: ToolButton
351 {
352 text: i18nd("mauikitfilebrowsing", "Back")
353 icon.name: "go-previous"
354 onClicked: control.quitSearch()
355 }
356 }
357
358 headBar.middleContent: Maui.SearchField
359 {
360 id: _searchField
361 focus: true
362 Layout.fillWidth: true
363 Layout.maximumWidth: 500
364 Layout.alignment: Qt.AlignCenter
365 placeholderText: _filterButton.checked ? i18nd("mauikitfilebrowsing", "Filter") : ("Search")
366 inputMethodHints: Qt.ImhNoAutoUppercase
367
368 onAccepted:
369 {
370 if(_filterButton.checked)
371 {
372 if(text.includes(","))
373 {
374 control.view.filters = text.split(",")
375 }else
376 {
377 control.view.filter = text
378 }
379
380 }else
381 {
382 control.search(text)
383 }
384 }
385
386 onCleared:
387 {
388 if(_filterButton.checked)
389 {
390 control.currentFMModel.clearFilters()
391 }
392 }
393
394 onTextChanged:
395 {
396 if(_filterButton.checked)
397 _searchField.accepted()
398 }
399
400 Keys.enabled: _filterButton.checked
401 Keys.onPressed:
402 {
403 // Shortcut for clearing selection
404 if(event.key == Qt.Key_Up)
405 {
406 control.currentView.forceActiveFocus()
407 }
408 }
409
410 actions: Action
411 {
412 id: _filterButton
413 icon.name: "view-filter"
414 // text: i18nd("mauikitfilebrowsing", "Filter")
415 checkable: true
416 checked: true
417 onTriggered:
418 {
419 control.view.filter = ""
420 _searchField.clear()
421 _searchField.forceActiveFocus()
422 }
423 }
424 }
425
426 footBar.visible: control.currentPath.startsWith("trash:/")
427
428 footerPositioning: ListView.InlineFooter
429
430 footBar.rightContent: Button
431 {
432 visible: control.currentPath.startsWith("trash:/")
433 icon.name: "trash-empty"
434 text: i18nd("mauikitfilebrowsing", "Empty Trash")
435 onClicked: FB.FM.emptyTrash()
436 }
437
438 Loader
439 {
440 id: dialogLoader
441 }
442
443 Component
444 {
445 id: _quitSearchDialogComponent
446
447 Maui.InfoDialog
448 {
449 title: i18n("Quit")
450 message: i18n("Are you sure you want to quit the current search in progress?")
451 onAccepted:
452 {
453 _stackView.pop()
454 _browser.forceActiveFocus()
455 }
456
457 onRejected: close()
458 }
459 }
460
461 Component
462 {
463 id: removeDialogComponent
464
465 FB.FileListingDialog
466 {
467 id: _removeDialog
468
469 property double freedSpace : calculateFreedSpace(urls)
470
471 title: i18nd("mauikitfilebrowsing", "Removing %1 files", urls.length)
472 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))
473
474 actions: [
475 Action
476 {
477 text: i18nd("mauikitfilebrowsing", "Cancel")
478 onTriggered: _removeDialog.close()
479 },
480
481 Action
482 {
483 text: i18nd("mauikitfilebrowsing", "Delete")
484 Maui.Controls.status: Maui.Controls.Negative
485 onTriggered:
486 {
487 control.currentFMList.removeFiles(urls)
488 close()
489 }
490 },
491 Action
492 {
493 text: i18nd("mauikitfilebrowsing", "Trash")
494 Maui.Controls.status: Maui.Controls.Neutral
495 enabled: Maui.Handy.isLinux
496 onTriggered:
497 {
498 control.currentFMList.moveToTrash(urls)
499 close()
500 }
501 }
502 ]
503
504 function calculateFreedSpace(urls)
505 {
506 var size = 0
507 for(var url of urls)
508 {
509 size += parseFloat(FB.FM.getFileInfo(url).size)
510 }
511
512 return size
513 }
514 }
515 }
516
517 Component
518 {
519 id: newDialogComponent
520 //
521 Maui.InputDialog
522 {
523 id: _newDialog
524
525 // title: _newDirOp.checked ? i18nd("mauikitfilebrowsing", "New folder") : i18nd("mauikitfilebrowsing", "New file")
526 message: i18nd("mauikitfilebrowsing", "Create a new folder or a file with a custom name.")
527
528 template.iconSource: _newDirOp.checked ? "folder" : FB.FM.getIconName(textEntry.text)
529 template.iconVisible: true
530
531 onFinished: (text) =>
532 {
533 if(_newDirOp.checked)
534 {
535 control.currentFMList.createDir(text)
536 return
537 }
538 if(_newFileOp.checked)
539 {
540 control.currentFMList.createFile(text)
541 return
542 }
543 }
544
545 textEntry.placeholderText: i18nd("mauikitfilebrowsing", "Name")
546
548 {
549 id: _newActions
550 expanded: true
551 autoExclusive: true
552 display: ToolButton.TextBesideIcon
553
554 Action
555 {
556 id: _newDirOp
557 icon.name: "folder-new"
558 text: i18nd("mauikitfilebrowsing", "Folder")
559 checked: String(_newDialog.textEntry.text).indexOf(".") < 0
560 }
561
562 Action
563 {
564 id: _newFileOp
565 icon.name: "document-new"
566 text: i18nd("mauikitfilebrowsing", "File")
567 checked: String(_newDialog.textEntry.text).indexOf(".") >= 0
568 }
569 }
570 }
571 }
572
573 Component
574 {
575 id: renameDialogComponent
576
577 Maui.InputDialog
578 {
579 id: _renameDialog
580
581 property var item : ({})
582
583 // title: i18nd("mauikitfilebrowsing", "Rename")
584 message: i18nd("mauikitfilebrowsing", "Enter the new name for the file.")
585
586 template.iconSource: item.icon
587 template.imageSource: item.thumbnail
588
589 textEntry.text: item.label
590 textEntry.placeholderText: i18nd("mauikitfilebrowsing", "New name")
591
592 onFinished: control.currentFMList.renameFile(item.path, textEntry.text)
593 onRejected: close()
594
595 // acceptButton.text: i18nd("mauikitfilebrowsing", "Rename")
596 // rejectButton.text: i18nd("mauikitfilebrowsing", "Cancel")
597
598 onOpened:
599 {
600 item = control.currentFMModel.get(control.currentIndex)
601
602 if(_renameDialog.textEntry.text.lastIndexOf(".") >= 0)
603 {
604 _renameDialog.textEntry.select(0, _renameDialog.textEntry.text.lastIndexOf("."))
605 }else
606 {
607 _renameDialog.textEntry.selectAll()
608 }
609 }
610 }
611 }
612
613 Component
614 {
615 id: _newTagDialogComponent
616 FB.NewTagDialog {}
617 }
618
619 /**
620 * @private
621 */
622 property string typingQuery
623
624 Maui.Chip
625 {
626 z: control.z + 99999
627 Maui.Theme.colorSet:Maui.Theme.Complementary
628 visible: _typingTimer.running
629 label.text: typingQuery
630 anchors.left: parent.left
631 anchors.top: parent.top
632 showCloseButton: false
633 anchors.margins: Maui.Style.space.medium
634 }
635
636 Timer
637 {
638 id: _typingTimer
639 interval: 250
640 onTriggered:
641 {
642 const index = control.currentFMList.indexOfName(typingQuery)
643 if(index > -1)
644 {
645 console.log("FOUDN TRYPIGN IDNEX", index)
646 control.currentIndex = control.currentFMModel.mappedFromSource(index)
647 }
648
649 typingQuery = ""
650 }
651 }
652
653 Connections
654 {
655 target: control.currentView
656 ignoreUnknownSignals: true
657
658 function onKeyPress(event)
659 {
660 const index = control.currentIndex
661 const item = control.currentFMModel.get(index)
662
663 var pat = /^([a-zA-Z0-9 _-]+)$/
664
665 if(event.count === 1 && pat.test(event.text))
666 {
667 typingQuery += event.text
668 _typingTimer.restart()
669 event.accepted = true
670 }
671
672 // Shortcuts for refreshing
673 if((event.key === Qt.Key_F5))
674 {
675 control.currentFMList.refresh()
676 event.accepted = true
677 }
678
679 // Shortcuts for renaming
680 if((event.key === Qt.Key_F2))
681 {
682 dialogLoader.sourceComponent = renameDialogComponent
683 dialog.open()
684 event.accepted = true
685 }
686
687 // Shortcuts for selecting file
688 if((event.key === Qt.Key_A) && (event.modifiers & Qt.ControlModifier))
689 {
690 control.selectAll()
691 event.accepted = true
692 }
693
694 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))
695 {
696 if(control.selectionBar && control.selectionBar.contains(item.path))
697 {
698 control.selectionBar.removeAtUri(item.path)
699 }else
700 {
701 control.addToSelection(item)
702 }
703 //event.accepted = true
704 }
705
706 //shortcut for opening files
707 if(event.key === Qt.Key_Return)
708 {
709 control.openItem(index)
710 event.accepted = true
711 }
712
713 // Shortcut for pasting an item
714 if((event.key == Qt.Key_V) && (event.modifiers & Qt.ControlModifier))
715 {
716 console.log("trying to do paste with keyboard shorcut")
717 control.paste()
718 event.accepted = true
719 }
720
721 // Shortcut for cutting an item
722 if((event.key == Qt.Key_X) && (event.modifiers & Qt.ControlModifier))
723 {
724 const urls = filterSelection(control.currentPath, item.path)
725 control.cut(urls)
726 event.accepted = true
727 }
728
729 // Shortcut for copying an item
730 if((event.key == Qt.Key_C) && (event.modifiers & Qt.ControlModifier))
731 {
732 const urls = filterSelection(control.currentPath, item.path)
733 control.copy(urls)
734 event.accepted = true
735 }
736
737 // Shortcut for removing an item
738 if(event.key === Qt.Key_Delete)
739 {
740 const urls = filterSelection(control.currentPath, item.path)
741 control.remove(urls)
742 event.accepted = true
743 }
744
745 // Shortcut for going back in browsing history
746 if(event.key === Qt.Key_Backspace || event.key == Qt.Key_Back)
747 {
748 if(control.selectionBar && control.selectionBar.count> 0)
749 {
750 control.selectionBar.clear()
751 }
752 else
753 {
754 control.goBack()
755 }
756 event.accepted = true
757 }
758
759 // Shortcut for clearing selection and filtering
760 if(event.key === Qt.Key_Escape) //TODO not working, the event is not catched or emitted or is being accepted else where?
761 {
762 if(control.selectionBar && control.selectionBar.count > 0)
763 {
764 control.selectionBar.clear()
765 }
766
767 control.view.filter = ""
768 event.accepted = true
769 }
770
771 //Shortcut for opening filtering
772 if((event.key === Qt.Key_F) && (event.modifiers & Qt.ControlModifier))
773 {
774 control.toggleSearchBar()
775 event.accepted = true
776 }
777
778 control.keyPress(event)
779 }
780
781 function onItemsSelected(indexes)
782 {
783 if(indexes.length)
784 {
785 control.currentIndex = indexes[0]
786 control.selectIndexes(indexes)
787 }
788 }
789
790 function onItemClicked(index)
791 {
792 control.currentIndex = index
793 control.itemClicked(index)
794 control.currentView.forceActiveFocus()
795 }
796
797 function onItemDoubleClicked(index)
798 {
799 control.currentIndex = index
800 control.itemDoubleClicked(index)
801 control.currentView.forceActiveFocus()
802 }
803
804 function onItemRightClicked(index)
805 {
806 control.currentIndex = index
807 control.itemRightClicked(index)
808 control.currentView.forceActiveFocus()
809 }
810
811 function onItemToggled(index, state)
812 {
813 const item = control.currentFMModel.get(index)
814
815 if(control.selectionBar && control.selectionBar.contains(item.path))
816 {
817 control.selectionBar.removeAtUri(item.path)
818 }else
819 {
820 control.addToSelection(item)
821 }
822 control.itemLeftEmblemClicked(index)
823 control.currentView.forceActiveFocus()
824 }
825
826 function onAreaClicked(mouse)
827 {
828 if(control.isSearchView)
829 return
830
831 if(!Maui.Handy.isMobile && mouse.button === Qt.RightButton)
832 {
833 control.rightClicked()
834 }
835
836 control.areaClicked(mouse)
837 control.currentView.forceActiveFocus()
838 }
839 }
840
841 Maui.ContextualMenu
842 {
843 id: _dropMenu
844 property string urls
845 enabled: !control.isSearchView
846
847 MenuItem
848 {
849 enabled: !control.readOnly
850 text: i18nd("mauikitfilebrowsing", "Copy Here")
851 icon.name: "edit-copy"
852 onTriggered:
853 {
854 const urls = _dropMenu.urls.split(",")
855 console.log("COPY THESE URLS,", urls, _dropMenu.urls)
856 control.currentFMList.copyInto(urls)
857 }
858 }
859
860 MenuItem
861 {
862 enabled: !control.readOnly
863 text: i18nd("mauikitfilebrowsing", "Move Here")
864 icon.name: "edit-move"
865 onTriggered:
866 {
867 const urls = _dropMenu.urls.split(",")
868 control.currentFMList.cutInto(urls)
869 }
870 }
871
872 MenuItem
873 {
874 enabled: !control.readOnly
875
876 text: i18nd("mauikitfilebrowsing", "Link Here")
877 icon.name: "edit-link"
878 onTriggered:
879 {
880 const urls = _dropMenu.urls.split(",")
881 for(var i in urls)
882 control.currentFMList.createSymlink(urls[i])
883 }
884 }
885
886 MenuItem
887 {
888 enabled: FB.FM.isDir(_dropMenu.urls.split(",")[0])
889 text: i18nd("mauikitfilebrowsing", "Open Here")
890 icon.name: "folder-open"
891 onTriggered:
892 {
893 const urls = _dropMenu.urls.split(",")
894 control.browser.path = urls[0]
895 }
896 }
897
898 MenuSeparator {}
899
900 MenuItem
901 {
902 text: i18nd("mauikitfilebrowsing", "Cancel")
903 icon.name: "dialog-cancel"
904 onTriggered: _dropMenu.close()
905 }
906 }
907
908 StackView
909 {
910 id: _stackView
911 anchors.fill: parent
912
913 initialItem: DropArea
914 {
915 id: _dropArea
916
917 readonly property alias browser : _browser
918 readonly property alias currentFMList : _browser.currentFMList
919 readonly property alias currentFMModel: _browser.currentFMModel
920 property alias filter: _browser.filter
921 property alias filters: _browser.filters
922 readonly property alias title : _browser.title
923
924 onDropped: (drop) =>
925 {
926 if(drop.hasUrls)
927 {
928 _dropMenu.urls = drop.urls.join(",")
929 _dropMenu.show()
930 control.urlsDropped(drop.urls)
931 }
932 }
933
934 opacity: _dropArea.containsDrag ? 0.5 : 1
935
936 Private.BrowserView
937 {
938 id: _browser
939 anchors.fill: parent
940
941 Binding on currentIndex
942 {
943 value: control.currentIndex
944 restoreMode: Binding.RestoreBindingOrValue
945 }
946
947 Loader
948 {
949 active: (control.currentPath === "tags://" || control.currentPath === "tags:///") && !control.readOnly
950 visible: active
951 asynchronous: true
952
953 anchors.right: parent.right
954 anchors.bottom: parent.bottom
955 anchors.rightMargin: Maui.Style.toolBarHeight
956 anchors.bottomMargin: Maui.Style.toolBarHeight + control.flickable.bottomMargin
957
958 sourceComponent: Maui.FloatingButton
959 {
960 icon.name : "list-add"
961 onClicked:
962 {
963 dialogLoader.sourceComponent = _newTagDialogComponent
964 dialog.open()
965 }
966 }
967 }
968 }
969 }
970
971 Component
972 {
973 id: _searchBrowserComponent
974
975 Private.BrowserView
976 {
977 id: _searchBrowser
978 property alias browser : _searchBrowser
979 readOnly: true
980 path: control.currentPath
981 Binding on currentIndex
982 {
983 value: control.currentIndex
984 restoreMode: Binding.RestoreBindingOrValue
985 }
986
987 objectName: "searchView"
988 gridItemSize: control.gridItemSize
989 listItemSize: control.listItemSize
990
991 currentFMList.autoLoad: false
992 settings.viewType: control.settings.viewType
993 settings.sortBy: control.settings.sortBy
994 settings.showHiddenFiles: control.settings.showHiddenFiles
995 settings.group: control.settings.group
996 settings.foldersFirst: control.settings.foldersFirst
997
998 }
999 }
1000 }
1001
1002 Component.onCompleted:
1003 {
1004 control.currentView.forceActiveFocus()
1005 }
1006
1007 /**
1008 * @brief Copy the given file URLs to the clipboard
1009 * @param urls the set of URLs to be copied
1010 **/
1011 function copy(urls)
1012 {
1013 if(urls.length <= 0)
1014 {
1015 return
1016 }
1017
1018 Maui.Handy.copyToClipboard({"urls": urls}, false)
1019 }
1020
1021 /**
1022 * @brief Add the given URLs to the clipboard and mark them as a cut operation
1023 * @param urls the set of URLs to cut
1024 **/
1025 function cut(urls)
1026 {
1027 if(control.readOnly)
1028 return
1029
1030 if(urls.length <= 0)
1031 {
1032 return
1033 }
1034
1035 Maui.Handy.copyToClipboard({"urls": urls}, true)
1036 }
1037
1038 /**
1039 * Paste the contents of the clipboard into the current location, if supported.
1040 **/
1041 function paste()
1042 {
1043 control.currentFMList.paste()
1044 }
1045
1046 /**
1047 * Remove the given URLs.Array()this will launch a dialog to confirm this action.
1048 * @param urls the set of URLs to be removed
1049 **/
1050 function remove(urls)
1051 {
1052 if(urls.length <= 0)
1053 {
1054 return
1055 }
1056
1057 dialogLoader.sourceComponent = removeDialogComponent
1058 dialog.urls = urls
1059 dialog.open()
1060 }
1061
1062 /**
1063 * @brief Given an index position of a element, try to open it, it can be a directory, a file or an executable.
1064 *
1065 **/
1066 function openItem(index)
1067 {
1068 const item = control.currentFMModel.get(index)
1069 const path = item.path
1070
1071 switch(control.currentFMList.pathType)
1072 {
1073 case FB.FMList.CLOUD_PATH: //TODO deprecrated and needs to be removed or clean up for 1.1
1074 if(item.isdir === "true")
1075 {
1076 control.openFolder(path)
1077 }
1078 else
1079 {
1080 FB.FM.openCloudItem(item)
1081 }
1082 break;
1083 default:
1084 if(control.selectionMode && item.isdir == "false")
1085 {
1086 if(control.selectionBar && control.selectionBar.contains(item.path))
1087 {
1088 control.selectionBar.removeAtPath(item.path)
1089 }else
1090 {
1091 control.addToSelection(item)
1092 }
1093 }
1094 else
1095 {
1096 if(item.isdir == "true")
1097 {
1098 control.openFolder(path)
1099 }
1100 else
1101 {
1102 control.openFile(path)
1103 }
1104 }
1105 }
1106 }
1107
1108 /**
1109 * @brief Open a file of the given path URL in the dedicated application
1110 * @param path The URL of the file
1111 **/
1112 function openFile(path)
1113 {
1114 FB.FM.openUrl(path)
1115 }
1116
1117 /**
1118 * @brief Open a folder location
1119 * @param path the URL of the folder location
1120 **/
1121 function openFolder(path)
1122 {
1123 if(!String(path).length)
1124 {
1125 return;
1126 }
1127
1128 if(control.isSearchView)
1129 {
1130 control.quitSearch()
1131 }
1132
1133 control.currentPath = path
1134 _browser.forceActiveFocus()
1135 }
1136
1137 /**
1138 * @brief Go back to the previous location
1139 **/
1140 function goBack()
1141 {
1142 openFolder(control.currentFMList.previousPath())
1143 }
1144
1145 /**
1146 * @brief Go forward to the location before going back
1147 **/
1148 function goForward()
1149 {
1150 openFolder(control.currentFMList.posteriorPath())
1151 }
1152
1153 /**
1154 * @brief Go to the location parent directory
1155 **/
1156 function goUp()
1157 {
1158 openFolder(control.currentFMList.parentPath)
1159 }
1160
1161 /**
1162 * @brief Selects the next item in the view
1163 */
1164 function nextItem()
1165 {
1166 if(_browser.viewType === Maui.AltBrowser.ViewType.List)
1167 _browser.currentView.flickable.incrementCurrentIndex()
1168 else
1169 _browser.currentView.flickable.moveCurrentIndexRight()
1170
1171 }
1172
1173 /**
1174 * @brief Selects the previous item in the view
1175 */
1176 function previousItem()
1177 {
1178 if(_browser.viewType === Maui.AltBrowser.ViewType.List)
1179 _browser.currentView.flickable.decrementCurrentIndex()
1180 else
1181 _browser.currentView.flickable.moveCurrentIndexLeft()
1182 }
1183
1184 /**
1185 * @brief Add an item to the selection
1186 * @param item the item object/map representing the file to be added to the selection
1187 * @warning For this to work the implementation needs to have passed a `selectionBar`
1188 * @see selectionBar
1189 **/
1190 function addToSelection(item)
1191 {
1192 if(control.selectionBar == null || item.path.startsWith("tags://") || item.path.startsWith("applications://"))
1193 {
1194 return
1195 }
1196
1197 control.selectionBar.append(item.path, item)
1198 }
1199
1200 /**
1201 * @brief Given a list of indexes add them to the selection
1202 * @param indexes list of index positions
1203 **/
1204 function selectIndexes(indexes)
1205 {
1206 if(control.selectionBar == null)
1207 {
1208 return
1209 }
1210
1211 for(var i in indexes)
1212 addToSelection(control.currentFMModel.get(indexes[i]))
1213 }
1214
1215 /**
1216 * @brief Forces to select all the entries
1217 *
1218 * @bug If there are too many entries, this could freeze the UI
1219 **/
1220 function selectAll() //TODO for now dont select more than 100 items so things dont freeze or break
1221 {
1222 if(control.selectionBar == null)
1223 {
1224 return
1225 }
1226
1227 selectIndexes([...Array( control.currentFMList.count ).keys()])
1228 }
1229
1230 /**
1231 * @brief Add a bookmark to a given list of paths
1232 * @param paths a group of directory URLs to be bookmarked
1233 **/
1234 function bookmarkFolder(paths) //multiple paths
1235 {
1236 for(var i in paths)
1237 {
1238 FB.FM.bookmark(paths[i])
1239 }
1240 }
1241
1242 /**
1243 * @brief Open/close the search bar
1244 */
1245 function toggleSearchBar() //only opens the searchbar toolbar, not the search view page
1246 {
1247 if(control.settings.searchBarVisible)
1248 {
1249 control.settings.searchBarVisible = false
1250 quitSearch()
1251 _browser.forceActiveFocus()
1252 }else
1253 {
1254 control.settings.searchBarVisible = true
1255 _searchField.forceActiveFocus()
1256 }
1257 }
1258
1259 /**
1260 * @brief Forces to open the search view and start a search.
1261 **/
1262 function openSearch() //opens the search view and focuses the search field
1263 {
1264 if(!control.isSearchView)
1265 {
1266 _stackView.push(_searchBrowserComponent)
1267 }
1268 control.settings.searchBarVisible = true
1269 _searchField.forceActiveFocus()
1270 }
1271
1272 /**
1273 * @brief Forces to close the search view, and return to the browsing view.
1274 **/
1275 function quitSearch()
1276 {
1277 if(control.currentView.loading)
1278 {
1279 dialogLoader.sourceComponent = _quitSearchDialogComponent
1280 control.dialog.open()
1281 return
1282 }
1283
1284 _stackView.pop()
1285 _browser.forceActiveFocus()
1286 }
1287
1288 /**
1289 * @brief Perform a recursive search starting form the current location and down to all the children directories.
1290 * @param query the text query to match against
1291 **/
1292 function search(query)
1293 {
1294 openSearch()
1295 _searchField.text = query
1296
1297 _stackView.currentItem.title = i18nd("mauikitfilebrowsing", "Search: %1", query)
1298 _stackView.currentItem.currentFMList.search(query, true)
1299
1300 _stackView.currentItem.forceActiveFocus()
1301 }
1302
1303 /**
1304 * @brief Opens a dialog for typing the name of the new item.
1305 * The new item can be a file or directory.
1306 **/
1307 function newItem()
1308 {
1309 if(control.isSearchView)
1310 return;
1311
1312 dialogLoader.sourceComponent = newDialogComponent
1313 dialog.open()
1314 dialog.forceActiveFocus()
1315 }
1316
1317 /**
1318 * @brief Opens a dialog for typing the new name of the item
1319 * This will target the current selected item in the browser view
1320 **/
1321 function renameItem()
1322 {
1323 if(control.isSearchView)
1324 return;
1325
1326 dialogLoader.sourceComponent= renameDialogComponent
1327 dialog.open()
1328 dialog.forceActiveFocus()
1329 }
1330
1331 /**
1332 * @brief Opens a dialog to confirm this operation
1333 * This will target the current selected item in the browser view
1334 **/
1335 function removeItem()
1336 {
1337 if(control.isSearchView)
1338 return;
1339
1340 dialogLoader.sourceComponent= renameDialogComponent
1341 dialog.open()
1342 dialog.forceActiveFocus()
1343 }
1344
1345 /**
1346 * @brief Filters the contents of the selection to the current path.
1347 * @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.
1348 *
1349 * @param currentPath The currentPath must be a directory, so the selection entries can be compared as its parent directory.
1350 * @param itemPath The itemPath is a default item path in case the selectionBar is empty
1351 *
1352 * @return the list of entries in the selection that match the currentPath as their parent directory
1353 **/
1354 function filterSelection(currentPath, itemPath)
1355 {
1356 var res = []
1357
1358 if(selectionBar && selectionBar.count > 0 && selectionBar.contains(itemPath))
1359 {
1360 const uris = selectionBar.uris
1361 for(var uri of uris)
1362 {
1363 if(String(FB.FM.parentDir(uri)) === currentPath)
1364 {
1365 res.push(uri)
1366 }
1367 }
1368
1369 } else
1370 {
1371 res = [itemPath]
1372 }
1373
1374 return res
1375 }
1376
1377 /**
1378 * @brief Forces to focus the current view.
1379 */
1380 function forceActiveFocus()
1381 {
1382 control.currentView.forceActiveFocus()
1383 }
1384}
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 dialog
An alias to the currently loaded dialog, if not dialog is loaded then null.
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 Apr 25 2025 11:51:37 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.