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")
485 onTriggered:
486 {
487 control.currentFMList.removeFiles(urls)
488 close()
489 }
490 },
491 Action
492 {
493 text: i18nd("mauikitfilebrowsing", "Trash")
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)
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 Add an item to the selection
1165 * @param item the item object/map representing the file to be added to the selection
1166 * @warning For this to work the implementation needs to have passed a `selectionBar`
1167 * @see selectionBar
1168 **/
1169 function addToSelection(item)
1170 {
1171 if(control.selectionBar == null || item.path.startsWith("tags://") || item.path.startsWith("applications://"))
1172 {
1173 return
1174 }
1175
1176 control.selectionBar.append(item.path, item)
1177 }
1178
1179 /**
1180 * @brief Given a list of indexes add them to the selection
1181 * @param indexes list of index positions
1182 **/
1183 function selectIndexes(indexes)
1184 {
1185 if(control.selectionBar == null)
1186 {
1187 return
1188 }
1189
1190 for(var i in indexes)
1191 addToSelection(control.currentFMModel.get(indexes[i]))
1192 }
1193
1194 /**
1195 * @brief Forces to select all the entries
1196 *
1197 * @bug If there are too many entries, this could freeze the UI
1198 **/
1199 function selectAll() //TODO for now dont select more than 100 items so things dont freeze or break
1200 {
1201 if(control.selectionBar == null)
1202 {
1203 return
1204 }
1205
1206 selectIndexes([...Array( control.currentFMList.count ).keys()])
1207 }
1208
1209 /**
1210 * @brief Add a bookmark to a given list of paths
1211 * @param paths a group of directory URLs to be bookmarked
1212 **/
1213 function bookmarkFolder(paths) //multiple paths
1214 {
1215 for(var i in paths)
1216 {
1217 FB.FM.bookmark(paths[i])
1218 }
1219 }
1220
1221 /**
1222 * @brief Open/close the search bar
1223 */
1224 function toggleSearchBar() //only opens the searchbar toolbar, not the search view page
1225 {
1226 if(control.settings.searchBarVisible)
1227 {
1228 control.settings.searchBarVisible = false
1229 quitSearch()
1230 _browser.forceActiveFocus()
1231 }else
1232 {
1233 control.settings.searchBarVisible = true
1234 _searchField.forceActiveFocus()
1235 }
1236 }
1237
1238 /**
1239 * @brief Forces to open the search view and start a search.
1240 **/
1241 function openSearch() //opens the search view and focuses the search field
1242 {
1243 if(!control.isSearchView)
1244 {
1245 _stackView.push(_searchBrowserComponent)
1246 }
1247 control.settings.searchBarVisible = true
1248 _searchField.forceActiveFocus()
1249 }
1250
1251 /**
1252 * @brief Forces to close the search view, and return to the browsing view.
1253 **/
1254 function quitSearch()
1255 {
1256 if(control.currentView.loading)
1257 {
1258 dialogLoader.sourceComponent = _quitSearchDialogComponent
1259 control.dialog.open()
1260 return
1261 }
1262
1263 _stackView.pop()
1264 _browser.forceActiveFocus()
1265 }
1266
1267 /**
1268 * @brief Perform a recursive search starting form the current location and down to all the children directories.
1269 * @param query the text query to match against
1270 **/
1271 function search(query)
1272 {
1273 openSearch()
1274 _searchField.text = query
1275
1276 _stackView.currentItem.title = i18nd("mauikitfilebrowsing", "Search: %1", query)
1277 _stackView.currentItem.currentFMList.search(query, true)
1278
1279 _stackView.currentItem.forceActiveFocus()
1280 }
1281
1282 /**
1283 * @brief Opens a dialog for typing the name of the new item.
1284 * The new item can be a file or directory.
1285 **/
1286 function newItem()
1287 {
1288 if(control.isSearchView)
1289 return;
1290
1291 dialogLoader.sourceComponent = newDialogComponent
1292 dialog.open()
1293 dialog.forceActiveFocus()
1294 }
1295
1296 /**
1297 * @brief Opens a dialog for typing the new name of the item
1298 * This will target the current selected item in the browser view
1299 **/
1300 function renameItem()
1301 {
1302 if(control.isSearchView)
1303 return;
1304
1305 dialogLoader.sourceComponent= renameDialogComponent
1306 dialog.open()
1307 dialog.forceActiveFocus()
1308 }
1309
1310 /**
1311 * @brief Opens a dialog to confirm this operation
1312 * This will target the current selected item in the browser view
1313 **/
1314 function removeItem()
1315 {
1316 if(control.isSearchView)
1317 return;
1318
1319 dialogLoader.sourceComponent= renameDialogComponent
1320 dialog.open()
1321 dialog.forceActiveFocus()
1322 }
1323
1324 /**
1325 * @brief Filters the contents of the selection to the current path.
1326 * @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.
1327 *
1328 * @param currentPath The currentPath must be a directory, so the selection entries can be compared as its parent directory.
1329 * @param itemPath The itemPath is a default item path in case the selectionBar is empty
1330 *
1331 * @return the list of entries in the selection that match the currentPath as their parent directory
1332 **/
1333 function filterSelection(currentPath, itemPath)
1334 {
1335 var res = []
1336
1337 if(selectionBar && selectionBar.count > 0 && selectionBar.contains(itemPath))
1338 {
1339 const uris = selectionBar.uris
1340 for(var uri of uris)
1341 {
1342 if(String(FB.FM.parentDir(uri)) === currentPath)
1343 {
1344 res.push(uri)
1345 }
1346 }
1347
1348 } else
1349 {
1350 res = [itemPath]
1351 }
1352
1353 return res
1354 }
1355
1356 /**
1357 * @brief Forces to focus the current view.
1358 */
1359 function forceActiveFocus()
1360 {
1361 control.currentView.forceActiveFocus()
1362 }
1363}
The FM class stands for File Management, and exposes methods for file listing, browsing and handling,...
Definition fm.h:104
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 & fill(QChar ch, qsizetype size)
QString left(qsizetype n) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) 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-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:11:22 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.