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 Maui.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 onTriggered:
485 {
486 control.currentFMList.removeFiles(urls)
487 close()
488 }
489 },
490 Action
491 {
492 text: i18nd("mauikitfilebrowsing", "Trash")
493 enabled: Maui.Handy.isLinux
494 onTriggered:
495 {
496 control.currentFMList.moveToTrash(urls)
497 close()
498 }
499 }
500 ]
501
502 function calculateFreedSpace(urls)
503 {
504 var size = 0
505 for(var url of urls)
506 {
507 size += parseFloat(FB.FM.getFileInfo(url).size)
508 }
509
510 return size
511 }
512 }
513 }
514
515 Component
516 {
517 id: newDialogComponent
518 //
519 Maui.InputDialog
520 {
521 id: _newDialog
522
523 title: _newDirOp.checked ? i18nd("mauikitfilebrowsing", "New folder") : i18nd("mauikitfilebrowsing", "New file")
524 message: i18nd("mauikitfilebrowsing", "Create a new folder or a file with a custom name.")
525
526 template.iconSource: _newDirOp.checked ? "folder" : FB.FM.getIconName(textEntry.text)
527 template.iconVisible: true
528
529 onFinished: (text) =>
530 {
531 if(_newDirOp.checked)
532 {
533 control.currentFMList.createDir(text)
534 return
535 }
536 if(_newFileOp.checked)
537 {
538 control.currentFMList.createFile(text)
539 return
540 }
541 }
542
543 textEntry.placeholderText: i18nd("mauikitfilebrowsing", "Name")
544
546 {
547 id: _newActions
548 expanded: true
549 autoExclusive: true
550 display: ToolButton.TextBesideIcon
551
552 Action
553 {
554 id: _newDirOp
555 icon.name: "folder-new"
556 text: i18nd("mauikitfilebrowsing", "Folder")
557 checked: String(_newDialog.textEntry.text).indexOf(".") < 0
558 }
559
560 Action
561 {
562 id: _newFileOp
563 icon.name: "document-new"
564 text: i18nd("mauikitfilebrowsing", "File")
565 checked: String(_newDialog.textEntry.text).indexOf(".") >= 0
566 }
567 }
568 }
569 }
570
571 Component
572 {
573 id: renameDialogComponent
574
575 Maui.InputDialog
576 {
577 id: _renameDialog
578
579 property var item : ({})
580
581 title: i18nd("mauikitfilebrowsing", "Rename")
582 message: i18nd("mauikitfilebrowsing", "Change the name of a file or folder. Write a new name and click Rename to apply the change.")
583
584 // headBar.visible: false
585
586 template.iconSource: item.icon
587 template.imageSource: item.thumbnail
588 template.iconSizeHint: Maui.Style.iconSizes.huge
589
590 textEntry.text: item.label
591 textEntry.placeholderText: i18nd("mauikitfilebrowsing", "New name")
592
593 onFinished: control.currentFMList.renameFile(item.path, textEntry.text)
594 onRejected: close()
595
596 // acceptButton.text: i18nd("mauikitfilebrowsing", "Rename")
597 // rejectButton.text: i18nd("mauikitfilebrowsing", "Cancel")
598
599 onOpened:
600 {
601 item = control.currentFMModel.get(control.currentIndex)
602
603 if(_renameDialog.textEntry.text.lastIndexOf(".") >= 0)
604 {
605 _renameDialog.textEntry.select(0, _renameDialog.textEntry.text.lastIndexOf("."))
606 }else
607 {
608 _renameDialog.textEntry.selectAll()
609 }
610 }
611 }
612 }
613
614 Component
615 {
616 id: _newTagDialogComponent
617 FB.NewTagDialog {}
618 }
619
620 /**
621 * @private
622 */
623 property string typingQuery
624
625 Maui.Chip
626 {
627 z: control.z + 99999
628 Maui.Theme.colorSet:Maui.Theme.Complementary
629 visible: _typingTimer.running
630 label.text: typingQuery
631 anchors.left: parent.left
632 anchors.top: parent.top
633 showCloseButton: false
634 anchors.margins: Maui.Style.space.medium
635 }
636
637 Timer
638 {
639 id: _typingTimer
640 interval: 250
641 onTriggered:
642 {
643 const index = control.currentFMList.indexOfName(typingQuery)
644 if(index > -1)
645 {
646 console.log("FOUDN TRYPIGN IDNEX", index)
647 control.currentIndex = control.currentFMModel.mappedFromSource(index)
648 }
649
650 typingQuery = ""
651 }
652 }
653
654 Connections
655 {
656 target: control.currentView
657 ignoreUnknownSignals: true
658
659 function onKeyPress(event)
660 {
661 const index = control.currentIndex
662 const item = control.currentFMModel.get(index)
663
664 var pat = /^([a-zA-Z0-9 _-]+)$/
665
666 if(event.count === 1 && pat.test(event.text))
667 {
668 typingQuery += event.text
669 _typingTimer.restart()
670 event.accepted = true
671 }
672
673 // Shortcuts for refreshing
674 if((event.key === Qt.Key_F5))
675 {
676 control.currentFMList.refresh()
677 event.accepted = true
678 }
679
680 // Shortcuts for renaming
681 if((event.key === Qt.Key_F2))
682 {
683 dialogLoader.sourceComponent = renameDialogComponent
684 dialog.open()
685 event.accepted = true
686 }
687
688 // Shortcuts for selecting file
689 if((event.key === Qt.Key_A) && (event.modifiers & Qt.ControlModifier))
690 {
691 control.selectAll()
692 event.accepted = true
693 }
694
695 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))
696 {
697 if(control.selectionBar && control.selectionBar.contains(item.path))
698 {
699 control.selectionBar.removeAtUri(item.path)
700 }else
701 {
702 control.addToSelection(item)
703 }
704 //event.accepted = true
705 }
706
707 //shortcut for opening files
708 if(event.key === Qt.Key_Return)
709 {
710 control.openItem(index)
711 event.accepted = true
712 }
713
714 // Shortcut for pasting an item
715 if((event.key == Qt.Key_V) && (event.modifiers & Qt.ControlModifier))
716 {
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)
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 Add an item to the selection
1163 * @param item the item object/map representing the file to be added to the selection
1164 * @warning For this to work the implementation needs to have passed a `selectionBar`
1165 * @see selectionBar
1166 **/
1167 function addToSelection(item)
1168 {
1169 if(control.selectionBar == null || item.path.startsWith("tags://") || item.path.startsWith("applications://"))
1170 {
1171 return
1172 }
1173
1174 control.selectionBar.append(item.path, item)
1175 }
1176
1177 /**
1178 * @brief Given a list of indexes add them to the selection
1179 * @param indexes list of index positions
1180 **/
1181 function selectIndexes(indexes)
1182 {
1183 if(control.selectionBar == null)
1184 {
1185 return
1186 }
1187
1188 for(var i in indexes)
1189 addToSelection(control.currentFMModel.get(indexes[i]))
1190 }
1191
1192 /**
1193 * @brief Forces to select all the entries
1194 *
1195 * @bug If there are too many entries, this could freeze the UI
1196 **/
1197 function selectAll() //TODO for now dont select more than 100 items so things dont freeze or break
1198 {
1199 if(control.selectionBar == null)
1200 {
1201 return
1202 }
1203
1204 selectIndexes([...Array( control.currentFMList.count ).keys()])
1205 }
1206
1207 /**
1208 * @brief Add a bookmark to a given list of paths
1209 * @param paths a group of directory URLs to be bookmarked
1210 **/
1211 function bookmarkFolder(paths) //multiple paths
1212 {
1213 for(var i in paths)
1214 {
1215 FB.FM.bookmark(paths[i])
1216 }
1217 }
1218
1219 /**
1220 * @brief Open/close the search bar
1221 */
1222 function toggleSearchBar() //only opens the searchbar toolbar, not the search view page
1223 {
1224 if(control.settings.searchBarVisible)
1225 {
1226 control.settings.searchBarVisible = false
1227 quitSearch()
1228 _browser.forceActiveFocus()
1229 }else
1230 {
1231 control.settings.searchBarVisible = true
1232 _searchField.forceActiveFocus()
1233 }
1234 }
1235
1236 /**
1237 * @brief Forces to open the search view and start a search.
1238 **/
1239 function openSearch() //opens the search view and focuses the search field
1240 {
1241 if(!control.isSearchView)
1242 {
1243 _stackView.push(_searchBrowserComponent)
1244 }
1245 control.settings.searchBarVisible = true
1246 _searchField.forceActiveFocus()
1247 }
1248
1249 /**
1250 * @brief Forces to close the search view, and return to the browsing view.
1251 **/
1252 function quitSearch()
1253 {
1254 if(control.currentView.loading)
1255 {
1256 dialogLoader.sourceComponent = _quitSearchDialogComponent
1257 control.dialog.open()
1258 return
1259 }
1260
1261 _stackView.pop()
1262 _browser.forceActiveFocus()
1263 }
1264
1265 /**
1266 * @brief Perform a recursive search starting form the current location and down to all the children directories.
1267 * @param query the text query to match against
1268 **/
1269 function search(query)
1270 {
1271 openSearch()
1272 _searchField.text = query
1273
1274 _stackView.currentItem.title = i18nd("mauikitfilebrowsing", "Search: %1", query)
1275 _stackView.currentItem.currentFMList.search(query, true)
1276
1277 _stackView.currentItem.forceActiveFocus()
1278 }
1279
1280 /**
1281 * @brief Opens a dialog for typing the name of the new item.
1282 * The new item can be a file or directory.
1283 **/
1284 function newItem()
1285 {
1286 if(control.isSearchView)
1287 return;
1288
1289 dialogLoader.sourceComponent = newDialogComponent
1290 dialog.open()
1291 dialog.forceActiveFocus()
1292 }
1293
1294 /**
1295 * @brief Opens a dialog for typing the new name of the item
1296 * This will target the current selected item in the browser view
1297 **/
1298 function renameItem()
1299 {
1300 if(control.isSearchView)
1301 return;
1302
1303 dialogLoader.sourceComponent= renameDialogComponent
1304 dialog.open()
1305 dialog.forceActiveFocus()
1306 }
1307
1308 /**
1309 * @brief Opens a dialog to confirm this operation
1310 * This will target the current selected item in the browser view
1311 **/
1312 function removeItem()
1313 {
1314 if(control.isSearchView)
1315 return;
1316
1317 dialogLoader.sourceComponent= renameDialogComponent
1318 dialog.open()
1319 dialog.forceActiveFocus()
1320 }
1321
1322 /**
1323 * @brief Filters the contents of the selection to the current path.
1324 * @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.
1325 *
1326 * @param currentPath The currentPath must be a directory, so the selection entries can be compared as its parent directory.
1327 * @param itemPath The itemPath is a default item path in case the selectionBar is empty
1328 *
1329 * @return the list of entries in the selection that match the currentPath as their parent directory
1330 **/
1331 function filterSelection(currentPath, itemPath)
1332 {
1333 var res = []
1334
1335 if(selectionBar && selectionBar.count > 0 && selectionBar.contains(itemPath))
1336 {
1337 const uris = selectionBar.uris
1338 for(var uri of uris)
1339 {
1340 if(String(FB.FM.parentDir(uri)) === currentPath)
1341 {
1342 res.push(uri)
1343 }
1344 }
1345
1346 } else
1347 {
1348 res = [itemPath]
1349 }
1350
1351 return res
1352 }
1353
1354 /**
1355 * @brief Forces to focus the current view.
1356 */
1357 function forceActiveFocus()
1358 {
1359 control.currentView.forceActiveFocus()
1360 }
1361}
bool visible
The FM class stands for File Management, and exposes methods for file listing, browsing and handling,...
Definition fm.h:102
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)
bool remove(const QString &column, const QVariant &value)
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 * close(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)
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 May 17 2024 11:51:27 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.