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", "Change the name of a file or folder. Write a new name and click Rename to apply the change.")
585
586 // headBar.visible: false
587
588 template.iconSource: item.icon
589 template.imageSource: item.thumbnail
590 template.iconSizeHint: Maui.Style.iconSizes.huge
591
592 textEntry.text: item.label
593 textEntry.placeholderText: i18nd("mauikitfilebrowsing", "New name")
594
595 onFinished: control.currentFMList.renameFile(item.path, textEntry.text)
596 onRejected: close()
597
598 // acceptButton.text: i18nd("mauikitfilebrowsing", "Rename")
599 // rejectButton.text: i18nd("mauikitfilebrowsing", "Cancel")
600
601 onOpened:
602 {
603 item = control.currentFMModel.get(control.currentIndex)
604
605 if(_renameDialog.textEntry.text.lastIndexOf(".") >= 0)
606 {
607 _renameDialog.textEntry.select(0, _renameDialog.textEntry.text.lastIndexOf("."))
608 }else
609 {
610 _renameDialog.textEntry.selectAll()
611 }
612 }
613 }
614 }
615
616 Component
617 {
618 id: _newTagDialogComponent
619 FB.NewTagDialog {}
620 }
621
622 /**
623 * @private
624 */
625 property string typingQuery
626
627 Maui.Chip
628 {
629 z: control.z + 99999
630 Maui.Theme.colorSet:Maui.Theme.Complementary
631 visible: _typingTimer.running
632 label.text: typingQuery
633 anchors.left: parent.left
634 anchors.top: parent.top
635 showCloseButton: false
636 anchors.margins: Maui.Style.space.medium
637 }
638
639 Timer
640 {
641 id: _typingTimer
642 interval: 250
643 onTriggered:
644 {
645 const index = control.currentFMList.indexOfName(typingQuery)
646 if(index > -1)
647 {
648 console.log("FOUDN TRYPIGN IDNEX", index)
649 control.currentIndex = control.currentFMModel.mappedFromSource(index)
650 }
651
652 typingQuery = ""
653 }
654 }
655
656 Connections
657 {
658 target: control.currentView
659 ignoreUnknownSignals: true
660
661 function onKeyPress(event)
662 {
663 const index = control.currentIndex
664 const item = control.currentFMModel.get(index)
665
666 var pat = /^([a-zA-Z0-9 _-]+)$/
667
668 if(event.count === 1 && pat.test(event.text))
669 {
670 typingQuery += event.text
671 _typingTimer.restart()
672 event.accepted = true
673 }
674
675 // Shortcuts for refreshing
676 if((event.key === Qt.Key_F5))
677 {
678 control.currentFMList.refresh()
679 event.accepted = true
680 }
681
682 // Shortcuts for renaming
683 if((event.key === Qt.Key_F2))
684 {
685 dialogLoader.sourceComponent = renameDialogComponent
686 dialog.open()
687 event.accepted = true
688 }
689
690 // Shortcuts for selecting file
691 if((event.key === Qt.Key_A) && (event.modifiers & Qt.ControlModifier))
692 {
693 control.selectAll()
694 event.accepted = true
695 }
696
697 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))
698 {
699 if(control.selectionBar && control.selectionBar.contains(item.path))
700 {
701 control.selectionBar.removeAtUri(item.path)
702 }else
703 {
704 control.addToSelection(item)
705 }
706 //event.accepted = true
707 }
708
709 //shortcut for opening files
710 if(event.key === Qt.Key_Return)
711 {
712 control.openItem(index)
713 event.accepted = true
714 }
715
716 // Shortcut for pasting an item
717 if((event.key == Qt.Key_V) && (event.modifiers & Qt.ControlModifier))
718 {
719 control.paste()
720 event.accepted = true
721 }
722
723 // Shortcut for cutting an item
724 if((event.key == Qt.Key_X) && (event.modifiers & Qt.ControlModifier))
725 {
726 const urls = filterSelection(control.currentPath, item.path)
727 control.cut(urls)
728 event.accepted = true
729 }
730
731 // Shortcut for copying an item
732 if((event.key == Qt.Key_C) && (event.modifiers & Qt.ControlModifier))
733 {
734 const urls = filterSelection(control.currentPath, item.path)
735 control.copy(urls)
736 event.accepted = true
737 }
738
739 // Shortcut for removing an item
740 if(event.key === Qt.Key_Delete)
741 {
742 const urls = filterSelection(control.currentPath, item.path)
743 control.remove(urls)
744 event.accepted = true
745 }
746
747 // Shortcut for going back in browsing history
748 if(event.key === Qt.Key_Backspace || event.key == Qt.Key_Back)
749 {
750 if(control.selectionBar && control.selectionBar.count> 0)
751 {
752 control.selectionBar.clear()
753 }
754 else
755 {
756 control.goBack()
757 }
758 event.accepted = true
759 }
760
761 // Shortcut for clearing selection and filtering
762 if(event.key === Qt.Key_Escape) //TODO not working, the event is not catched or emitted or is being accepted else where?
763 {
764 if(control.selectionBar && control.selectionBar.count > 0)
765 {
766 control.selectionBar.clear()
767 }
768
769 control.view.filter = ""
770 event.accepted = true
771 }
772
773 //Shortcut for opening filtering
774 if((event.key === Qt.Key_F) && (event.modifiers & Qt.ControlModifier))
775 {
776 control.toggleSearchBar()
777 event.accepted = true
778 }
779
780 control.keyPress(event)
781 }
782
783 function onItemsSelected(indexes)
784 {
785 if(indexes.length)
786 {
787 control.currentIndex = indexes[0]
788 control.selectIndexes(indexes)
789 }
790 }
791
792 function onItemClicked(index)
793 {
794 control.currentIndex = index
795 control.itemClicked(index)
796 control.currentView.forceActiveFocus()
797 }
798
799 function onItemDoubleClicked(index)
800 {
801 control.currentIndex = index
802 control.itemDoubleClicked(index)
803 control.currentView.forceActiveFocus()
804 }
805
806 function onItemRightClicked(index)
807 {
808 control.currentIndex = index
809 control.itemRightClicked(index)
810 control.currentView.forceActiveFocus()
811 }
812
813 function onItemToggled(index, state)
814 {
815 const item = control.currentFMModel.get(index)
816
817 if(control.selectionBar && control.selectionBar.contains(item.path))
818 {
819 control.selectionBar.removeAtUri(item.path)
820 }else
821 {
822 control.addToSelection(item)
823 }
824 control.itemLeftEmblemClicked(index)
825 control.currentView.forceActiveFocus()
826 }
827
828 function onAreaClicked(mouse)
829 {
830 if(control.isSearchView)
831 return
832
833 if(!Maui.Handy.isMobile && mouse.button === Qt.RightButton)
834 {
835 control.rightClicked()
836 }
837
838 control.areaClicked(mouse)
839 control.currentView.forceActiveFocus()
840 }
841 }
842
843 Maui.ContextualMenu
844 {
845 id: _dropMenu
846 property string urls
847 enabled: !control.isSearchView
848
849 MenuItem
850 {
851 enabled: !control.readOnly
852 text: i18nd("mauikitfilebrowsing", "Copy Here")
853 icon.name: "edit-copy"
854 onTriggered:
855 {
856 const urls = _dropMenu.urls.split(",")
857 console.log("COPY THESE URLS,", urls, _dropMenu.urls)
858 control.currentFMList.copyInto(urls)
859 }
860 }
861
862 MenuItem
863 {
864 enabled: !control.readOnly
865 text: i18nd("mauikitfilebrowsing", "Move Here")
866 icon.name: "edit-move"
867 onTriggered:
868 {
869 const urls = _dropMenu.urls.split(",")
870 control.currentFMList.cutInto(urls)
871 }
872 }
873
874 MenuItem
875 {
876 enabled: !control.readOnly
877
878 text: i18nd("mauikitfilebrowsing", "Link Here")
879 icon.name: "edit-link"
880 onTriggered:
881 {
882 const urls = _dropMenu.urls.split(",")
883 for(var i in urls)
884 control.currentFMList.createSymlink(urls[i])
885 }
886 }
887
888 MenuItem
889 {
890 enabled: FB.FM.isDir(_dropMenu.urls.split(",")[0])
891 text: i18nd("mauikitfilebrowsing", "Open Here")
892 icon.name: "folder-open"
893 onTriggered:
894 {
895 const urls = _dropMenu.urls.split(",")
896 control.browser.path = urls[0]
897 }
898 }
899
900 MenuSeparator {}
901
902 MenuItem
903 {
904 text: i18nd("mauikitfilebrowsing", "Cancel")
905 icon.name: "dialog-cancel"
906 onTriggered: _dropMenu.close()
907 }
908 }
909
910 StackView
911 {
912 id: _stackView
913 anchors.fill: parent
914
915 initialItem: DropArea
916 {
917 id: _dropArea
918
919 readonly property alias browser : _browser
920 readonly property alias currentFMList : _browser.currentFMList
921 readonly property alias currentFMModel: _browser.currentFMModel
922 property alias filter: _browser.filter
923 property alias filters: _browser.filters
924 readonly property alias title : _browser.title
925
926 onDropped: (drop) =>
927 {
928 if(drop.hasUrls)
929 {
930 _dropMenu.urls = drop.urls.join(",")
931 _dropMenu.show()
932 control.urlsDropped(drop.urls)
933 }
934 }
935
936 opacity: _dropArea.containsDrag ? 0.5 : 1
937
938 Private.BrowserView
939 {
940 id: _browser
941 anchors.fill: parent
942
943 Binding on currentIndex
944 {
945 value: control.currentIndex
946 restoreMode: Binding.RestoreBindingOrValue
947 }
948
949 Loader
950 {
951 active: (control.currentPath === "tags://" || control.currentPath === "tags:///") && !control.readOnly
952 visible: active
953 asynchronous: true
954
955 anchors.right: parent.right
956 anchors.bottom: parent.bottom
957 anchors.rightMargin: Maui.Style.toolBarHeight
958 anchors.bottomMargin: Maui.Style.toolBarHeight + control.flickable.bottomMargin
959
960 sourceComponent: Maui.FloatingButton
961 {
962 icon.name : "list-add"
963 onClicked:
964 {
965 dialogLoader.sourceComponent = _newTagDialogComponent
966 dialog.open()
967 }
968 }
969 }
970 }
971 }
972
973 Component
974 {
975 id: _searchBrowserComponent
976
977 Private.BrowserView
978 {
979 id: _searchBrowser
980 property alias browser : _searchBrowser
981 readOnly: true
982 path: control.currentPath
983 Binding on currentIndex
984 {
985 value: control.currentIndex
986 restoreMode: Binding.RestoreBindingOrValue
987 }
988
989 objectName: "searchView"
990 gridItemSize: control.gridItemSize
991 listItemSize: control.listItemSize
992
993 currentFMList.autoLoad: false
994 settings.viewType: control.settings.viewType
995 settings.sortBy: control.settings.sortBy
996 settings.showHiddenFiles: control.settings.showHiddenFiles
997 settings.group: control.settings.group
998 settings.foldersFirst: control.settings.foldersFirst
999
1000 }
1001 }
1002 }
1003
1004 Component.onCompleted:
1005 {
1006 control.currentView.forceActiveFocus()
1007 }
1008
1009 /**
1010 * @brief Copy the given file URLs to the clipboard
1011 * @param urls the set of URLs to be copied
1012 **/
1013 function copy(urls)
1014 {
1015 if(urls.length <= 0)
1016 {
1017 return
1018 }
1019
1020 Maui.Handy.copyToClipboard({"urls": urls}, false)
1021 }
1022
1023 /**
1024 * @brief Add the given URLs to the clipboard and mark them as a cut operation
1025 * @param urls the set of URLs to cut
1026 **/
1027 function cut(urls)
1028 {
1029 if(control.readOnly)
1030 return
1031
1032 if(urls.length <= 0)
1033 {
1034 return
1035 }
1036
1037 Maui.Handy.copyToClipboard({"urls": urls}, true)
1038 }
1039
1040 /**
1041 * Paste the contents of the clipboard into the current location, if supported.
1042 **/
1043 function paste()
1044 {
1045 control.currentFMList.paste()
1046 }
1047
1048 /**
1049 * Remove the given URLs.Array()this will launch a dialog to confirm this action.
1050 * @param urls the set of URLs to be removed
1051 **/
1052 function remove(urls)
1053 {
1054 if(urls.length <= 0)
1055 {
1056 return
1057 }
1058
1059 dialogLoader.sourceComponent = removeDialogComponent
1060 dialog.urls = urls
1061 dialog.open()
1062 }
1063
1064 /**
1065 * @brief Given an index position of a element, try to open it, it can be a directory, a file or an executable.
1066 *
1067 **/
1068 function openItem(index)
1069 {
1070 const item = control.currentFMModel.get(index)
1071 const path = item.path
1072
1073 switch(control.currentFMList.pathType)
1074 {
1075 case FB.FMList.CLOUD_PATH: //TODO deprecrated and needs to be removed or clean up for 1.1
1076 if(item.isdir === "true")
1077 {
1078 control.openFolder(path)
1079 }
1080 else
1081 {
1082 FB.FM.openCloudItem(item)
1083 }
1084 break;
1085 default:
1086 if(control.selectionMode && item.isdir == "false")
1087 {
1088 if(control.selectionBar && control.selectionBar.contains(item.path))
1089 {
1090 control.selectionBar.removeAtPath(item.path)
1091 }else
1092 {
1093 control.addToSelection(item)
1094 }
1095 }
1096 else
1097 {
1098 if(item.isdir == "true")
1099 {
1100 control.openFolder(path)
1101 }
1102 else
1103 {
1104 control.openFile(path)
1105 }
1106 }
1107 }
1108 }
1109
1110 /**
1111 * @brief Open a file of the given path URL in the dedicated application
1112 * @param path The URL of the file
1113 **/
1114 function openFile(path)
1115 {
1116 FB.FM.openUrl(path)
1117 }
1118
1119 /**
1120 * @brief Open a folder location
1121 * @param path the URL of the folder location
1122 **/
1123 function openFolder(path)
1124 {
1125 if(!String(path).length)
1126 {
1127 return;
1128 }
1129
1130 if(control.isSearchView)
1131 {
1132 control.quitSearch()
1133 }
1134
1135 control.currentPath = path
1136 _browser.forceActiveFocus()
1137 }
1138
1139 /**
1140 * @brief Go back to the previous location
1141 **/
1142 function goBack()
1143 {
1144 openFolder(control.currentFMList.previousPath())
1145 }
1146
1147 /**
1148 * @brief Go forward to the location before going back
1149 **/
1150 function goForward()
1151 {
1152 openFolder(control.currentFMList.posteriorPath())
1153 }
1154
1155 /**
1156 * @brief Go to the location parent directory
1157 **/
1158 function goUp()
1159 {
1160 openFolder(control.currentFMList.parentPath)
1161 }
1162
1163 /**
1164 * @brief Selects the next item in the view
1165 */
1166 function nextItem()
1167 {
1168 if(_browser.viewType === Maui.AltBrowser.ViewType.List)
1169 _browser.currentView.flickable.incrementCurrentIndex()
1170 else
1171 _browser.currentView.flickable.moveCurrentIndexRight()
1172
1173 }
1174
1175 /**
1176 * @brief Selects the previous item in the view
1177 */
1178 function previousItem()
1179 {
1180 if(_browser.viewType === Maui.AltBrowser.ViewType.List)
1181 _browser.currentView.flickable.decrementCurrentIndex()
1182 else
1183 _browser.currentView.flickable.moveCurrentIndexLeft()
1184 }
1185
1186 /**
1187 * @brief Add an item to the selection
1188 * @param item the item object/map representing the file to be added to the selection
1189 * @warning For this to work the implementation needs to have passed a `selectionBar`
1190 * @see selectionBar
1191 **/
1192 function addToSelection(item)
1193 {
1194 if(control.selectionBar == null || item.path.startsWith("tags://") || item.path.startsWith("applications://"))
1195 {
1196 return
1197 }
1198
1199 control.selectionBar.append(item.path, item)
1200 }
1201
1202 /**
1203 * @brief Given a list of indexes add them to the selection
1204 * @param indexes list of index positions
1205 **/
1206 function selectIndexes(indexes)
1207 {
1208 if(control.selectionBar == null)
1209 {
1210 return
1211 }
1212
1213 for(var i in indexes)
1214 addToSelection(control.currentFMModel.get(indexes[i]))
1215 }
1216
1217 /**
1218 * @brief Forces to select all the entries
1219 *
1220 * @bug If there are too many entries, this could freeze the UI
1221 **/
1222 function selectAll() //TODO for now dont select more than 100 items so things dont freeze or break
1223 {
1224 if(control.selectionBar == null)
1225 {
1226 return
1227 }
1228
1229 selectIndexes([...Array( control.currentFMList.count ).keys()])
1230 }
1231
1232 /**
1233 * @brief Add a bookmark to a given list of paths
1234 * @param paths a group of directory URLs to be bookmarked
1235 **/
1236 function bookmarkFolder(paths) //multiple paths
1237 {
1238 for(var i in paths)
1239 {
1240 FB.FM.bookmark(paths[i])
1241 }
1242 }
1243
1244 /**
1245 * @brief Open/close the search bar
1246 */
1247 function toggleSearchBar() //only opens the searchbar toolbar, not the search view page
1248 {
1249 if(control.settings.searchBarVisible)
1250 {
1251 control.settings.searchBarVisible = false
1252 quitSearch()
1253 _browser.forceActiveFocus()
1254 }else
1255 {
1256 control.settings.searchBarVisible = true
1257 _searchField.forceActiveFocus()
1258 }
1259 }
1260
1261 /**
1262 * @brief Forces to open the search view and start a search.
1263 **/
1264 function openSearch() //opens the search view and focuses the search field
1265 {
1266 if(!control.isSearchView)
1267 {
1268 _stackView.push(_searchBrowserComponent)
1269 }
1270 control.settings.searchBarVisible = true
1271 _searchField.forceActiveFocus()
1272 }
1273
1274 /**
1275 * @brief Forces to close the search view, and return to the browsing view.
1276 **/
1277 function quitSearch()
1278 {
1279 if(control.currentView.loading)
1280 {
1281 dialogLoader.sourceComponent = _quitSearchDialogComponent
1282 control.dialog.open()
1283 return
1284 }
1285
1286 _stackView.pop()
1287 _browser.forceActiveFocus()
1288 }
1289
1290 /**
1291 * @brief Perform a recursive search starting form the current location and down to all the children directories.
1292 * @param query the text query to match against
1293 **/
1294 function search(query)
1295 {
1296 openSearch()
1297 _searchField.text = query
1298
1299 _stackView.currentItem.title = i18nd("mauikitfilebrowsing", "Search: %1", query)
1300 _stackView.currentItem.currentFMList.search(query, true)
1301
1302 _stackView.currentItem.forceActiveFocus()
1303 }
1304
1305 /**
1306 * @brief Opens a dialog for typing the name of the new item.
1307 * The new item can be a file or directory.
1308 **/
1309 function newItem()
1310 {
1311 if(control.isSearchView)
1312 return;
1313
1314 dialogLoader.sourceComponent = newDialogComponent
1315 dialog.open()
1316 dialog.forceActiveFocus()
1317 }
1318
1319 /**
1320 * @brief Opens a dialog for typing the new name of the item
1321 * This will target the current selected item in the browser view
1322 **/
1323 function renameItem()
1324 {
1325 if(control.isSearchView)
1326 return;
1327
1328 dialogLoader.sourceComponent= renameDialogComponent
1329 dialog.open()
1330 dialog.forceActiveFocus()
1331 }
1332
1333 /**
1334 * @brief Opens a dialog to confirm this operation
1335 * This will target the current selected item in the browser view
1336 **/
1337 function removeItem()
1338 {
1339 if(control.isSearchView)
1340 return;
1341
1342 dialogLoader.sourceComponent= renameDialogComponent
1343 dialog.open()
1344 dialog.forceActiveFocus()
1345 }
1346
1347 /**
1348 * @brief Filters the contents of the selection to the current path.
1349 * @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.
1350 *
1351 * @param currentPath The currentPath must be a directory, so the selection entries can be compared as its parent directory.
1352 * @param itemPath The itemPath is a default item path in case the selectionBar is empty
1353 *
1354 * @return the list of entries in the selection that match the currentPath as their parent directory
1355 **/
1356 function filterSelection(currentPath, itemPath)
1357 {
1358 var res = []
1359
1360 if(selectionBar && selectionBar.count > 0 && selectionBar.contains(itemPath))
1361 {
1362 const uris = selectionBar.uris
1363 for(var uri of uris)
1364 {
1365 if(String(FB.FM.parentDir(uri)) === currentPath)
1366 {
1367 res.push(uri)
1368 }
1369 }
1370
1371 } else
1372 {
1373 res = [itemPath]
1374 }
1375
1376 return res
1377 }
1378
1379 /**
1380 * @brief Forces to focus the current view.
1381 */
1382 function forceActiveFocus()
1383 {
1384 control.currentView.forceActiveFocus()
1385 }
1386}
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)
QAction * paste(const QObject *recvr, const char *slot, QObject *parent)
QAction * cut(const QObject *recvr, const char *slot, QObject *parent)
QAction * renameFile(const QObject *recvr, const char *slot, QObject *parent)
QAction * copy(const QObject *recvr, const char *slot, QObject *parent)
QAction * selectAll(const QObject *recvr, const char *slot, QObject *parent)
KGuiItem remove()
KGuiItem close()
QString label(StandardShortcut id)
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 Jan 24 2025 11:49:45 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.