diff --git a/src/controls/FileBrowser.qml b/src/controls/FileBrowser.qml index 756ce79..1fff425 100644 --- a/src/controls/FileBrowser.qml +++ b/src/controls/FileBrowser.qml @@ -1,1184 +1,1192 @@ /* * Copyright 2018 Camilo Higuita * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.10 import QtQuick.Controls 2.10 import QtQuick.Layouts 1.3 import QtQml.Models 2.3 import QtQml 2.1 import org.kde.kirigami 2.8 as Kirigami import org.kde.mauikit 1.0 as Maui import org.kde.mauikit 1.1 as MauiLab import "private" Maui.Page { id: control property url currentPath onCurrentPathChanged: { if(control.browserView) control.browserView.path = control.currentPath } property int viewType : Maui.FMList.LIST_VIEW onViewTypeChanged: browserView.viewType = control.viewType property int thumbnailsSize : Maui.Style.iconSizes.large * 1.7 property var indexHistory : [] property bool isCopy : false property bool isCut : false property bool group : false //group properties from the browser since the browser views are loaded async and //their properties can not be accesed inmediately, so they are stored here and then when completed they are set property BrowserSettings settings : BrowserSettings {} property alias selectionBar : selectionBarLoader.item property alias browserView : _browserList.currentItem readonly property Maui.FMList currentFMList : browserView.currentFMList + readonly property Maui.BaseModel currentFMModel : browserView.currentFMModel property alias previewer : previewer property alias menu : browserMenu.contentData property alias itemMenu: itemMenu property alias dialog : dialogLoader.item signal itemClicked(int index) signal itemDoubleClicked(int index) signal itemRightClicked(int index) signal itemLeftEmblemClicked(int index) signal itemRightEmblemClicked(int index) signal rightClicked() signal newBookmark(var paths) Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.inherit: false onGoBackTriggered: control.goBack() onGoForwardTriggered: control.goNext() focus: true footBar.visible: false || String(control.currentPath).startsWith("trash:/") - footBar.leftContent: Label + + footBar.middleContent: Maui.TextField { Layout.fillWidth: true - text: control.currentFMList.count + " " + qsTr("items") - } + visible: control.currentFMList.count > 0 + placeholderText: qsTr("Filter") + " " + control.currentFMList.count + " " + qsTr("files") + onAccepted: control.currentFMModel.filter = text + onCleared: control.currentFMModel.filter = "" + } footBar.rightContent: [ ToolButton { icon.name: "zoom-in" onClicked: zoomIn() }, ToolButton { icon.name: "zoom-out" onClicked: zoomOut() }, ToolButton { visible: String(control.currentPath).startsWith("trash:/") icon.name: "trash-empty" text: qsTr("Empty trash") onClicked: Maui.FM.emptyTrash() } ] headBar.position: Kirigami.Settings.isMobile ? ToolBar.Footer : ToolBar.Header headBar.rightContent:[ ToolButton { icon.name: "item-select" checkable: true checked: settings.selectionMode onClicked: settings.selectionMode = !settings.selectionMode }, Maui.ToolButtonMenu { icon.name: "view-sort" MenuItem { text: qsTr("Folders first") checked: control.currentFMList.foldersFirst checkable: true onTriggered: control.currentFMList.foldersFirst = !control.currentFMList.foldersFirst } MenuSeparator {} MenuItem { text: qsTr("Type") checked: control.currentFMList.sortBy === Maui.FMList.MIME checkable: true onTriggered: control.currentFMList.sortBy = Maui.FMList.MIME autoExclusive: true } MenuItem { text: qsTr("Date") checked: control.currentFMList.sortBy === Maui.FMList.DATE checkable: true onTriggered: control.currentFMList.sortBy = Maui.FMList.DATE autoExclusive: true } MenuItem { text: qsTr("Modified") checkable: true checked: control.currentFMList.sortBy === Maui.FMList.MODIFIED onTriggered: control.currentFMList.sortBy = Maui.FMList.MODIFIED autoExclusive: true } MenuItem { text: qsTr("Size") checkable: true checked: control.currentFMList.sortBy === Maui.FMList.SIZE onTriggered: control.currentFMList.sortBy = Maui.FMList.SIZE autoExclusive: true } MenuItem { text: qsTr("Name") checkable: true checked: control.currentFMList.sortBy === Maui.FMList.LABEL onTriggered: control.currentFMList.sortBy = Maui.FMList.LABEL autoExclusive: true } MenuSeparator{} MenuItem { id: groupAction text: qsTr("Group") checkable: true checked: control.group onTriggered: { control.group = !control.group if(control.group) control.groupBy() else browserView.currentView.section.property = "" } } }, ToolButton { id: _optionsButton icon.name: "overflow-menu" enabled: currentFMList.pathType !== Maui.FMList.TAGS_PATH && currentFMList.pathType !== Maui.FMList.TRASH_PATH && currentFMList.pathType !== Maui.FMList.APPS_PATH onClicked: { if(browserMenu.visible) browserMenu.close() else browserMenu.show(_optionsButton, 0, height) } checked: browserMenu.visible checkable: false } ] headBar.leftContent: [ ToolButton { icon.name: "go-previous" onClicked: control.goBack() }, ToolButton { icon.name: "go-next" onClicked: control.goNext() }, Maui.ToolActions { direction: Qt.Vertical currentAction: switch(browserView.viewType) { case Maui.FMList.ICON_VIEW: return actions[0] case Maui.FMList.LIST_VIEW: return actions[1] case Maui.FMList.MILLERS_VIEW: return actions[2] } Action { icon.name: "view-list-icons" text: qsTr("Grid") onTriggered: control.viewType = Maui.FMList.ICON_VIEW checked: browserView.viewType === Maui.FMList.ICON_VIEW icon.width: Maui.Style.iconSizes.medium } Action { icon.name: "view-list-details" text: qsTr("List") onTriggered: control.viewType = Maui.FMList.LIST_VIEW icon.width: Maui.Style.iconSizes.medium checkable: true checked: browserView.viewType === Maui.FMList.LIST_VIEW } Action { icon.name: "view-file-columns" text: qsTr("Columns") onTriggered: control.viewType = Maui.FMList.MILLERS_VIEW icon.width: Maui.Style.iconSizes.medium checkable: true checked: browserView.viewType === Maui.FMList.MILLERS_VIEW } } ] Loader { id: dialogLoader } Component { id: removeDialogComponent Maui.Dialog { property var urls: [] title: qsTr(String("Removing %1 files").arg(urls.length.toString())) message: Maui.Handy.isAndroid ? qsTr("This action will completely remove your files from your system. This action can not be undone.") : qsTr("You can move the file to the Trash or Delete it completely from your system. Which one you preffer?") rejectButton.text: qsTr("Delete") acceptButton.text: qsTr("Trash") acceptButton.visible: !Kirigami.Settings.isMobile page.padding: Maui.Style.space.huge onRejected: { if(control.selectionBar && control.selectionBar.visible) { control.selectionBar.animate() control.clearSelection() } for(var i in urls) Maui.FM.removeFile(urls[i]) close() } onAccepted: { if(control.selectionBar && control.selectionBar.visible) { control.selectionBar.animate() control.clearSelection() } for(var i in urls) Maui.FM.moveToTrash(urls[i]) close() } } } Component { id: newFolderDialogComponent Maui.NewDialog { title: qsTr("New folder") message: qsTr("Create a new folder with a custom name") acceptButton.text: qsTr("Create") onFinished: control.currentFMList.createDir(text) rejectButton.visible: false textEntry.placeholderText: qsTr("Folder name...") } } Component { id: newFileDialogComponent Maui.NewDialog { title: qsTr("New file") message: qsTr("Create a new file with a custom name and extension") acceptButton.text: qsTr("Create") onFinished: Maui.FM.createFile(control.currentPath, text) rejectButton.visible: false textEntry.placeholderText: qsTr("File name...") } } Component { id: renameDialogComponent Maui.NewDialog { title: qsTr("Rename file") message: qsTr("Rename a file or folder to a new custom name") textEntry.text: itemMenu.item.label textEntry.placeholderText: qsTr("New name...") onFinished: Maui.FM.rename(itemMenu.item.path, textEntry.text) onRejected: close() acceptText: qsTr("Rename") rejectText: qsTr("Cancel") } } Component { id: shareDialogComponent MauiLab.ShareDialog {} } Component { id: tagsDialogComponent Maui.TagsDialog { taglist.strict: false onTagsReady: { composerList.updateToUrls(tags) if(control.previewer.visible) control.previewer.tagBar.list.refresh() } } } Component { id: _configDialogComponent Maui.Dialog { maxHeight: _configLayout.implicitHeight * 1.5 maxWidth: 300 defaultButtons: false Kirigami.FormLayout { id: _configLayout width: parent.width anchors.centerIn: parent Kirigami.Separator { Kirigami.FormData.label: qsTr("Navigation") Kirigami.FormData.isSection: true } Switch { icon.name: "image-preview" checkable: true checked: settings.showThumbnails Kirigami.FormData.label: qsTr("Thumbnails") onToggled: settings.showThumbnails = !settings.showThumbnails } Switch { Kirigami.FormData.label: qsTr("Hidden files") checkable: true checked: control.currentFMList.hidden onToggled: control.currentFMList.hidden = !control.currentFMList.hidden } Kirigami.Separator { Kirigami.FormData.label: qsTr("Others") Kirigami.FormData.isSection: true } Switch { Kirigami.FormData.label: qsTr("Status bar") checkable: true checked: control.footBar.visible onToggled: control.footBar.visible = !control.footBar.visible } } } } Maui.FilePreviewer { id: previewer onShareButtonClicked: control.shareFiles([url]) } BrowserMenu { id: browserMenu } FileMenu { id: itemMenu width: Maui.Style.unit *200 onBookmarkClicked: control.bookmarkFolder([item.path]) onCopyClicked: { if(item) control.copy([item.path]) } onCutClicked: { if(item) control.cut([item.path]) } onTagsClicked: { if(item) { dialogLoader.sourceComponent = tagsDialogComponent dialog.composerList.urls = [item.path] dialog.open() } } onRenameClicked: { dialogLoader.sourceComponent = renameDialogComponent dialog.open() } onRemoveClicked: { + console.log("REMOVE", item.path) control.remove([item.path]) } onShareClicked: control.shareFiles([item.path]) } Connections { target: browserView.currentView onKeyPress: { const index = browserView.currentView.currentIndex const item = control.currentFMList.get(index) // Shortcuts for refreshing if((event.key == Qt.Key_F5)) { control.currentFMList.refresh() } // Shortcuts for selecting file if((event.key == Qt.Key_A) && (event.modifiers & Qt.ControlModifier)) { control.selectAll() } if(event.key == Qt.Key_S) { if(control.selectionBar && control.selectionBar.contains(item.path)) { control.selectionBar.removeAtUri(item.path) }else { control.addToSelection(item) } } 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)) { if(control.selectionBar && control.selectionBar.contains(item.path)) { control.selectionBar.removeAtUri(item.path) }else { control.addToSelection(item) } } // Shortcut for pasting an item if((event.key == Qt.Key_V) && (event.modifiers & Qt.ControlModifier)) { control.paste(Maui.Handy.getClipboard().urls) } // Shortcut for cutting an item if((event.key == Qt.Key_X) && (event.modifiers & Qt.ControlModifier)) { var urls = [] if(control.selectionBar) { urls = control.selectionBar.uris } else { urls = [item.path] } control.cut(urls) } // Shortcut for copying an item if((event.key == Qt.Key_C) && (event.modifiers & Qt.ControlModifier)) { var urls = [] if(control.selectionBar) { urls = control.selectionBar.uris } else { urls = [item.path] } control.copy(urls) } // Shortcut for removing an item if(event.key == Qt.Key_Delete) { var urls = [] if(control.selectionBar) { urls = control.selectionBar.uris } else { urls = [item.path] } control.remove(urls) } // Shortcut for opening new tab if((event.key == Qt.Key_T) && (event.modifiers & Qt.ControlModifier)) { console.log("OPEN TAB") control.openTab(currentPath) } // Shortcut for closing tab if((event.key == Qt.Key_W) && (event.modifiers & Qt.ControlModifier)) { if(tabsBar.count > 1) control.closeTab(tabsBar.currentIndex) } // Shortcut for opening files in new tab , previewing or launching if((event.key == Qt.Key_Return) && (event.modifiers & Qt.ControlModifier)) { if(item.isdir == "true") control.openTab(item.path) }else if((event.key == Qt.Key_Return) && (event.modifiers & Qt.AltModifier)) { control.previewer.show(control.currentFMList.get(index).path) }else if(event.key == Qt.Key_Return) { indexHistory.push(index) control.itemClicked(index) } // Shortcut for going back in browsing history if(event.key == Qt.Key_Backspace || event.key == Qt.Key_Back) { if(control.selectionBar) control.clearSelection() else control.goBack() } // Shortcut for clearing selection if(event.key == Qt.Key_Escape) { if(control.selectionBar) control.clearSelection() } } onItemClicked: { browserView.currentView.currentIndex = index indexHistory.push(index) control.itemClicked(index) } onItemDoubleClicked: { browserView.currentView.currentIndex = index indexHistory.push(index) control.itemDoubleClicked(index) } onItemRightClicked: { if(control.currentFMList.pathType !== Maui.FMList.TRASH_PATH && control.currentFMList.pathType !== Maui.FMList.REMOTE_PATH) + { itemMenu.show(index) - control.itemRightClicked(index) + } + control.itemRightClicked(index) } onLeftEmblemClicked: { const item = control.currentFMList.get(index) if(control.selectionBar && control.selectionBar.contains(item.path)) { control.selectionBar.removeAtUri(item.path) }else { control.addToSelection(item) } control.itemLeftEmblemClicked(index) } onRightEmblemClicked: { Maui.Handy.isAndroid ? Maui.Android.shareDialog([control.currentFMList.get(index).path]) : shareDialog.show([control.currentFMList.get(index).path]) control.itemRightEmblemClicked(index) } onAreaClicked: { if(!Kirigami.Settings.isMobile && mouse.button === Qt.RightButton) browserMenu.show(control) else return control.rightClicked() } onAreaRightClicked: browserMenu.show(control) // onWarning: // { // notify("dialog-information", "An error happened", message) // } // onProgress: // { // if(percent === 100) // _progressBar.value = 0 // else // _progressBar.value = percent/100 // } } Component { id: selectionBarComponent MauiLab.SelectionBar { id: _selectionBar singleSelection: settings.singleSelection onCountChanged: { if(_selectionBar.count < 1) control.clearSelection() } onExitClicked: control.clearSelection() listDelegate: ListBrowserDelegate { Kirigami.Theme.inherit: true width: parent.width height: Maui.Style.iconSizes.big + Maui.Style.space.big label1.text: model.label label2.text: model.path showEmblem: true showThumbnails: true leftEmblem: "list-remove" folderSize: Maui.Style.iconSizes.big onLeftEmblemClicked: _selectionBar.removeAtIndex(index) keepEmblemOverlay: Kirigami.Settings.isMobile background: null onClicked: control.previewer.show(model.path) onPressAndHold: removeAtIndex(index) } Action { text: qsTr("Open") icon.name: "document-open" onTriggered: { if(control.selectionBar) { for(var i in uris) openFile(uris[i]) } } } Action { text: qsTr("Copy") icon.name: "edit-copy" onTriggered: if(control.selectionBar) { control.selectionBar.animate() control.copy(uris) } } Action { text: qsTr("Cut") icon.name: "edit-cut" onTriggered: if(control.selectionBar) { control.selectionBar.animate() control.cut(uris) } } Action { text: qsTr("Tags") icon.name: "tag" onTriggered: if(control.selectionBar) { dialogLoader.sourceComponent = tagsDialogComponent dialog.composerList.urls = uris dialog.open() } } Action { text: qsTr("Share") icon.name: "document-share" onTriggered: { control.shareFiles(uris) } } Action { text: qsTr("Remove") icon.name: "edit-delete" Kirigami.Theme.textColor: Kirigami.Theme.negativeTextColor onTriggered: { control.remove(uris) } } } } ObjectModel { id: tabsObjectModel } ColumnLayout { id: _layout anchors.fill: parent spacing: 0 Maui.TabBar { id: tabsBar visible: _browserList.count > 1 Layout.fillWidth: true Layout.preferredHeight: tabsBar.implicitHeight position: TabBar.Header currentIndex : _browserList.currentIndex ListModel { id: tabsListModel } Keys.onPressed: { if(event.key == Qt.Key_Return) { _browserList.currentIndex = currentIndex control.currentPath = tabsObjectModel.get(currentIndex).path } } Repeater { id: _repeater model: tabsListModel Maui.TabButton { id: _tabButton implicitHeight: tabsBar.implicitHeight implicitWidth: Math.max(control.width / _repeater.count, 120) checked: index === _browserList.currentIndex text: tabsObjectModel.get(index).currentFMList.pathName onClicked: { _browserList.currentIndex = index control.currentPath = tabsObjectModel.get(index).path } onCloseClicked: control.closeTab(index) } } } ListView { id: _browserList Layout.margins: 0 Layout.fillWidth: true Layout.fillHeight: true clip: true focus: true orientation: ListView.Horizontal model: tabsObjectModel snapMode: ListView.SnapOneItem spacing: 0 interactive: Kirigami.Settings.isMobile && tabsObjectModel.count > 1 highlightFollowsCurrentItem: true highlightMoveDuration: 0 onMovementEnded: _browserList.currentIndex = indexAt(contentX, contentY) // DropArea // { // id: _dropArea // anchors.fill: parent // z: parent.z -2 // onDropped: // { // const urls = drop.urls // for(var i in urls) // { // const item = Maui.FM.getFileInfo(urls[i]) // if(item.isdir == "true") // { // control.openTab(urls[i]) // } // } // } // } } Loader { id: selectionBarLoader Layout.alignment: Qt.AlignCenter Layout.margins: Maui.Style.space.medium Layout.preferredHeight: control.selectionBar && control.selectionBar.visible ? control.selectionBar.barHeight: 0 Layout.maximumWidth: 500 Layout.minimumWidth: 100 Layout.fillWidth: true Layout.bottomMargin: control.selectionBar && control.selectionBar.visible ? Maui.Style.contentMargins*2 : 0 } ProgressBar { id: _progressBar Layout.fillWidth: true Layout.alignment: Qt.AlignBottom Layout.preferredHeight: visible ? Maui.Style.iconSizes.medium : 0 visible: value > 0 } } Component.onCompleted: { openTab(Maui.FM.homePath()) browserView.currentView.forceActiveFocus() } onThumbnailsSizeChanged: { if(settings.trackChanges && settings.saveDirProps) Maui.FM.setDirConf(currentPath+"/.directory", "MAUIFM", "IconSize", thumbnailsSize) else Maui.FM.saveSettings("IconSize", thumbnailsSize, "SETTINGS") if(control.viewType === Maui.FMList.ICON_VIEW) browserView.currentView.adaptGrid() } function closeTab(index) { tabsObjectModel.remove(index) tabsListModel.remove(index) } function openTab(path) { if(path) { const component = Qt.createComponent("private/BrowserView.qml"); if (component.status === Component.Ready) { const object = component.createObject(tabsObjectModel, {'path': path}); tabsObjectModel.append(object) tabsListModel.append({"path": path}) _browserList.currentIndex = tabsObjectModel.count - 1 browserView.viewType = control.viewType control.currentPath = path } } } function shareFiles(urls) { if(urls.length <= 0) return; dialogLoader.sourceComponent= shareDialogComponent dialog.urls = urls dialog.open() } function openItem(index) { const item = control.currentFMList.get(index) const path = item.path switch(control.currentFMList.pathType) { case Maui.FMList.CLOUD_PATH: if(item.isdir === "true") { control.openFolder(path) } else { Maui.FM.openCloudItem(item) } break; default: if(settings.selectionMode && item.isdir == "false") { if(control.selectionBar && control.selectionBar.contains(item.path)) { control.selectionBar.removeAtPath(item.path) }else { control.addToSelection(item) } } else { if(item.isdir == "true") { control.openFolder(path) } else { if (Kirigami.Settings.isMobile) { control.previewer.show(path) } else { control.openFile(path) } } } } } function openFile(path) { Maui.FM.openUrl(path) } function openFolder(path) { if(!String(path).length) return; control.currentPath = path } function goBack() { openFolder(control.currentFMList.previousPath) // browserView.currentView.currentIndex = indexHistory.pop() } function goNext() { openFolder(control.currentFMList.posteriorPath) } function goUp() { openFolder(control.currentFMList.parentPath) } function refresh() { const pos = browserView.currentView.contentY browserView.currentView.contentY = pos } function addToSelection(item) { if(item.path.startsWith("tags://") || item.path.startsWith("applications://") ) return if(!control.selectionBar) selectionBarLoader.sourceComponent = selectionBarComponent control.selectionBar.append(item.path, item) } function clearSelection() { if(control.selectionBar) { control.selectionBar.clear() selectionBarLoader.sourceComponent = null settings.selectionMode = false } } function copy(urls) { Maui.Handy.copyToClipboard({"urls": urls}) control.isCut = false control.isCopy = true } function cut(urls) { Maui.Handy.copyToClipboard({"urls": urls}) control.isCut = true control.isCopy = false } function paste() { const urls = Maui.Handy.getClipboard().urls if(!urls) return if(control.isCut) { control.currentFMList.cutInto(urls) control.clearSelection() }else { control.currentFMList.copyInto(urls) } } function remove(urls) { dialogLoader.sourceComponent= removeDialogComponent dialog.urls = urls dialog.open() } function selectAll() //TODO for now dont select more than 100 items so things dont freeze or break { for(var i = 0; i < Math.min(control.currentFMList.count, 100); i++) addToSelection(control.currentFMList.get(i)) } function bookmarkFolder(paths) //multiple paths { control.newBookmark(paths) } function zoomIn() { control.thumbnailsSize = control.thumbnailsSize + 8 } function zoomOut() { const newSize = control.thumbnailsSize - 8 if(newSize >= Maui.Style.iconSizes.small) control.thumbnailsSize = newSize } function groupBy() { var prop = "" var criteria = ViewSection.FullString switch(control.currentFMList.sortBy) { case Maui.FMList.LABEL: prop = "label" criteria = ViewSection.FirstCharacter break; case Maui.FMList.MIME: prop = "mime" break; case Maui.FMList.SIZE: prop = "size" break; case Maui.FMList.DATE: prop = "date" break; case Maui.FMList.MODIFIED: prop = "modified" break; } if(!prop) { control.browserView.currentView.section.property = "" return } control.browserView.viewType = Maui.FMList.LIST_VIEW control.browserView.currentView.section.property = prop control.browserView.currentView.section.criteria = criteria } function openConfigDialog() { dialogLoader.sourceComponent = _configDialogComponent control.dialog.open() } } diff --git a/src/controls/labs/SelectionBar.qml b/src/controls/labs/SelectionBar.qml index 04de247..3f6c9b9 100644 --- a/src/controls/labs/SelectionBar.qml +++ b/src/controls/labs/SelectionBar.qml @@ -1,433 +1,433 @@ /* * Copyright 2018 Camilo Higuita * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.10 import QtQuick.Controls 2.10 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import QtGraphicalEffects 1.0 Item { id: control focus: true default property list actions Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.Complementary readonly property int barHeight : Maui.Style.iconSizes.large + Maui.Style.space.medium readonly property alias uris: _private._uris readonly property alias items: _private._items property alias selectionList : selectionList property alias count : selectionList.count readonly property QtObject m_private : QtObject { id: _private property var _uris : [] property var _items : [] } property Component listDelegate: Maui.ItemDelegate { id: delegate height: Maui.Style.rowHeight * 1.5 width: parent.width Kirigami.Theme.backgroundColor: "transparent" Kirigami.Theme.textColor: control.Kirigami.Theme.textColor onClicked: control.itemClicked(index) onPressAndHold: control.itemPressAndHold(index) RowLayout { anchors.fill: parent Item { Layout.fillHeight: true Layout.preferredWidth: _badge.height + Maui.Style.space.small Maui.Badge { id: _badge anchors.centerIn: parent size: Maui.Style.iconSizes.small iconName: "list-remove" onClicked: control.removeAtIndex(index) } } Maui.ListItemTemplate { id: _template Layout.fillWidth: true Layout.fillHeight: true iconVisible: false labelsVisible: true label1.text: model.uri } } } /** * if singleSelection is set to true then only a single item is selected * at time, and replaced with a newe item appended **/ property bool singleSelection: false signal iconClicked() signal cleared() signal exitClicked() signal itemClicked(int index) signal itemPressAndHold(int index) signal itemAdded(var item) signal itemRemoved(var item) signal uriAdded(string uri) signal uriRemoved(string uri) signal clicked(var mouse) signal rightClicked(var mouse) implicitHeight: barHeight implicitWidth: _layout.implicitWidth + Maui.Style.space.big - visible: control.count > 0 + visible: control.count > 1 DropShadow { id: rectShadow anchors.fill: _listContainer cached: true horizontalOffset: 0 verticalOffset: 0 radius: 8.0 samples: 16 color: "#333" smooth: true source: _listContainer } Rectangle { id: _listContainer property bool showList : false height: showList ? Math.min(Math.min(400, control.parent.parent.height), selectionList.contentHeight) + control.height + Maui.Style.space.big : 0 width: showList ? parent.width : 0 color: Qt.lighter(Kirigami.Theme.backgroundColor) radius: Maui.Style.radiusV focus: true y: ((height) * -1) + control.height x: 0 opacity: showList ? 1 : .97 Behavior on height { NumberAnimation { duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } } Behavior on width { NumberAnimation { duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } } Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration easing.type: Easing.InOutQuad } } ListView { id: selectionList anchors.fill: parent anchors.margins: Maui.Style.space.medium anchors.bottomMargin: control.height visible: _listContainer.height > 10 highlightFollowsCurrentItem: true highlightMoveDuration: 0 keyNavigationEnabled: true interactive: Kirigami.Settings.isMobile boundsBehavior: !Kirigami.Settings.isMobile? Flickable.StopAtBounds : Flickable.OvershootBounds orientation: ListView.Vertical clip: true focus: true spacing: Maui.Style.space.small ScrollBar.vertical: ScrollBar { policy: Qt.ScrollBarAsNeeded } model: ListModel{} delegate: control.listDelegate } } Rectangle { id: bg anchors.fill: parent color: Kirigami.Theme.backgroundColor radius: Maui.Style.radiusV MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.LeftButton onClicked: { if(!Kirigami.Settings.isMobile && mouse.button === Qt.RightButton) control.rightClicked(mouse) else control.clicked(mouse) } onPressAndHold : { if(Kirigami.Settings.isMobile) control.rightClicked(mouse) } } } RowLayout { anchors.fill: parent ToolButton { icon.name: "dialog-close" Layout.fillHeight: true Layout.preferredWidth: height onClicked: control.exitClicked() Kirigami.Theme.colorSet: control.Kirigami.Theme.colorSet } Maui.ToolBar { id: _layout background: null Layout.fillHeight: true Layout.fillWidth: true middleContent: [ // Kirigami.ActionToolBar // { // display: control.width > Kirigami.Units.gridUnit * 25 ? ToolButton.TextUnderIcon : ToolButton.IconOnly // actions: control.actions // Layout.fillWidth: true // Layout.fillHeight: true // }, Repeater { model: control.actions ToolButton { action: modelData Layout.preferredWidth: implicitWidth Layout.fillHeight: true // display: control.width > Kirigami.Units.gridUnit * 25 ? ToolButton.TextUnderIcon : ToolButton.IconOnly Kirigami.Theme.colorSet: control.Kirigami.Theme.colorSet display: ToolButton.TextUnderIcon onClicked : _listContainer.showList = false } } ] } Maui.Badge { id: _counter Layout.fillHeight: true Layout.preferredWidth: height Layout.margins: Maui.Style.space.medium text: selectionList.count radius: Maui.Style.radiusV Kirigami.Theme.backgroundColor: _listContainer.showList ? Kirigami.Theme.highlightColor : Qt.darker(bg.color) border.color: "transparent" onClicked: { _listContainer.showList = !_listContainer.showList } Component.onCompleted: { _counter.item.font.pointSize= Maui.Style.fontSizes.big } SequentialAnimation { id: anim // PropertyAnimation // { // target: _counter // property: "opacity" // easing.type: Easing.InOutQuad // from: 0.5 // to: 1 // duration: 600 // } // PropertyAnimation { target: _counter property: "radius" easing.type: Easing.InOutQuad from: target.height to: Maui.Style.radiusV duration: 200 } } } } Keys.onEscapePressed: { control.exitClicked(); event.accepted = true } Keys.onBackPressed: { control.exitClicked(); event.accepted = true } function clear() { _private._uris = [] _private._items = [] selectionList.model.clear() control.cleared() } function itemAt(index) { if(index < 0 || index > selectionList.count) return return selectionList.model.get(index) } function removeAtIndex(index) { if(index < 0) return const item = selectionList.model.get(index) const uri = item.uri if(contains(uri)) { _private._uris.splice(index, 1) _private._items.splice(index, 1) selectionList.model.remove(index) control.itemRemoved(item) control.uriRemoved(uri) } } function removeAtUri(uri) { removeAtIndex(indexOf(uri)) } function indexOf(uri) { return _private._uris.indexOf(uri) } function append(uri, item) { const index = _private._uris.indexOf(uri) if(index < 0) { if(control.singleSelection) clear() _private._items.push(item) _private._uris.push(uri) item.uri = uri selectionList.model.append(item) selectionList.positionViewAtEnd() selectionList.currentIndex = selectionList.count - 1 control.itemAdded(item) control.uriAdded(uri) }else { selectionList.currentIndex = index // notify(item.icon, qsTr("Item already selected!"), String("The item '%1' is already in the selection box").arg(item.label), null, 4000) } animate() } function animate() { anim.running = true } function getSelectedUrisString() { return String(""+_private._uris.join(",")) } function contains(uri) { return _private._uris.includes(uri) } } diff --git a/src/controls/private/BrowserView.qml b/src/controls/private/BrowserView.qml index 8de636c..0242184 100644 --- a/src/controls/private/BrowserView.qml +++ b/src/controls/private/BrowserView.qml @@ -1,647 +1,664 @@ import QtQuick 2.9 import QtQuick.Controls 2.9 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui Maui.Page { id: control property url path onPathChanged: control.currentView.currentIndex = 0 property Maui.FMList currentFMList - + property Maui.BaseModel currentFMModel + property alias currentView : viewLoader.item property int viewType height: _browserList.height width: _browserList.width function setCurrentFMList() { - control.currentFMList = currentView.currentFMList - currentView.forceActiveFocus() + control.currentFMList = currentView.currentFMList + control.currentFMModel = currentView.currentFMModel + currentView.forceActiveFocus() } Menu { id: _dropMenu property string urls property url target enabled: Maui.FM.getFileInfo(target).isdir == "true" && !urls.includes(target.toString()) MenuItem { text: qsTr("Copy here") onTriggered: { const urls = _dropMenu.urls.split(",") for(var i in urls) Maui.FM.copy(urls[i], _dropMenu.target, false) } } MenuItem { text: qsTr("Move here") onTriggered: { const urls = _dropMenu.urls.split(",") for(var i in urls) Maui.FM.cut(urls[i], _dropMenu.target) } } MenuItem { text: qsTr("Link here") onTriggered: { const urls = _dropMenu.urls.split(",") for(var i in urls) Maui.FM.createSymlink(_dropMenu.source[i], urls.target) } } } Loader { id: viewLoader anchors.fill: parent focus: true sourceComponent: switch(control.viewType) { case Maui.FMList.ICON_VIEW: return gridViewBrowser case Maui.FMList.LIST_VIEW: return listViewBrowser case Maui.FMList.MILLERS_VIEW: return millerViewBrowser } onLoaded: setCurrentFMList() } Maui.FMList { id: _commonFMList path: control.path onSortByChanged: if(group) groupBy() onlyDirs: settings.onlyDirs filterType: settings.filterType filters: settings.filters sortBy: settings.sortBy } Component { id: listViewBrowser Maui.ListBrowser { id: _listViewBrowser property alias currentFMList : _browserModel.list + property alias currentFMModel : _browserModel topMargin: Maui.Style.contentMargins showPreviewThumbnails: settings.showThumbnails keepEmblemOverlay: settings.selectionMode showDetailsInfo: true supportsRefreshing: true BrowserHolder { id: _holder browser: currentFMList } holder.visible: _holder.visible holder.emoji: _holder.emoji holder.title: _holder.title holder.body: _holder.body holder.emojiSize: _holder.emojiSize model: Maui.BaseModel { id: _browserModel list: _commonFMList + recursiveFilteringEnabled: true + sortCaseSensitivity: Qt.CaseInsensitive + filterCaseSensitivity: Qt.CaseInsensitive } section.delegate: Maui.LabelDelegate { id: delegate width: parent.width height: Maui.Style.toolBarHeightAlt label: String(section).toUpperCase() labelTxt.font.pointSize: Maui.Style.fontSizes.big isSection: true } delegate: Maui.ListBrowserDelegate { id: delegate width: _listViewBrowser.width height: _listViewBrowser.itemSize + Maui.Style.space.big leftPadding: Maui.Style.space.small rightPadding: Maui.Style.space.small padding: 0 showDetailsInfo: _listViewBrowser.showDetailsInfo folderSize : _listViewBrowser.itemSize showTooltip: true showEmblem: _listViewBrowser.showEmblem keepEmblemOverlay : _listViewBrowser.keepEmblemOverlay showThumbnails: _listViewBrowser.showPreviewThumbnails rightEmblem: _listViewBrowser.rightEmblem isSelected: selectionBar ? selectionBar.contains(model.path) : false leftEmblem: isSelected ? "list-remove" : "list-add" draggable: true Maui.Badge { iconName: "link" anchors.left: parent.left anchors.bottom: parent.bottom visible: (model.issymlink == true) || (model.issymlink == "true") } Connections { target: selectionBar onUriRemoved: { if(uri === model.path) delegate.isSelected = false } onUriAdded: { if(uri === model.path) delegate.isSelected = true } onCleared: delegate.isSelected = false } Connections { target: delegate onClicked: { _listViewBrowser.currentIndex = index _listViewBrowser.itemClicked(index) } onDoubleClicked: { _listViewBrowser.currentIndex = index _listViewBrowser.itemDoubleClicked(index) } onPressAndHold: { _listViewBrowser.currentIndex = index _listViewBrowser.itemRightClicked(index) } onRightClicked: { _listViewBrowser.currentIndex = index _listViewBrowser.itemRightClicked(index) } onRightEmblemClicked: { _listViewBrowser.currentIndex = index _listViewBrowser.rightEmblemClicked(index) } onLeftEmblemClicked: { _listViewBrowser.currentIndex = index _listViewBrowser.leftEmblemClicked(index) } onContentDropped: { _dropMenu.urls = drop.urls.join(",") _dropMenu.target = model.path _dropMenu.popup() } } } } } Component { id: gridViewBrowser Maui.GridBrowser { id: _gridViewBrowser property alias currentFMList : _browserModel.list + property alias currentFMModel : _browserModel itemSize : thumbnailsSize + Maui.Style.fontSizes.default cellHeight: itemSize * 1.5 keepEmblemOverlay: settings.selectionMode showPreviewThumbnails: settings.showThumbnails supportsRefreshing: true BrowserHolder { id: _holder browser: currentFMList } holder.visible: _holder.visible holder.emoji: _holder.emoji holder.title: _holder.title holder.body: _holder.body holder.emojiSize: _holder.emojiSize model: Maui.BaseModel { id: _browserModel list: _commonFMList + recursiveFilteringEnabled: true + sortCaseSensitivity: Qt.CaseInsensitive + filterCaseSensitivity: Qt.CaseInsensitive } delegate: Maui.GridBrowserDelegate { id: delegate folderSize: height * 0.5 height: _gridViewBrowser.cellHeight width: _gridViewBrowser.cellWidth padding: Maui.Style.space.tiny showTooltip: true showEmblem: _gridViewBrowser.showEmblem keepEmblemOverlay: _gridViewBrowser.keepEmblemOverlay showThumbnails: _gridViewBrowser.showPreviewThumbnails rightEmblem: _gridViewBrowser.rightEmblem isSelected: selectionBar ? selectionBar.contains(model.path) : false leftEmblem: isSelected ? "list-remove" : "list-add" draggable: true Maui.Badge { iconName: "link" anchors.left: parent.left anchors.bottom: parent.bottom anchors.bottomMargin: Maui.Style.space.big visible: (model.issymlink == true) || (model.issymlink == "true") } Connections { target: selectionBar onUriRemoved: { if(uri === model.path) delegate.isSelected = false } onUriAdded: { if(uri === model.path) delegate.isSelected = true } onCleared: delegate.isSelected = false } Connections { target: delegate onClicked: { _gridViewBrowser.currentIndex = index _gridViewBrowser.itemClicked(index) } onDoubleClicked: { _gridViewBrowser.currentIndex = index _gridViewBrowser.itemDoubleClicked(index) } onPressAndHold: { _gridViewBrowser.currentIndex = index _gridViewBrowser.itemRightClicked(index) } onRightClicked: { _gridViewBrowser.currentIndex = index _gridViewBrowser.itemRightClicked(index) } onRightEmblemClicked: { _gridViewBrowser.currentIndex = index _gridViewBrowser.rightEmblemClicked(index) } onLeftEmblemClicked: { _gridViewBrowser.currentIndex = index _gridViewBrowser.leftEmblemClicked(index) } onContentDropped: { _dropMenu.urls = drop.urls.join(",") _dropMenu.target = model.path _dropMenu.popup() } } } } } Component { id: millerViewBrowser Item { id: _millerControl property Maui.FMList currentFMList + property Maui.BaseModel currentFMModel property int currentIndex signal itemClicked(int index) signal itemDoubleClicked(int index) signal itemRightClicked(int index) signal keyPress(var event) signal rightEmblemClicked(int index) signal leftEmblemClicked(int index) signal areaClicked(var mouse) signal areaRightClicked() ListView { id: _millerColumns anchors.fill: parent boundsBehavior: !Maui.Handy.isTouch? Flickable.StopAtBounds : Flickable.OvershootBounds keyNavigationEnabled: true interactive: Maui.Handy.isTouch orientation: ListView.Horizontal snapMode: ListView.SnapToItem ScrollBar.horizontal: ScrollBar { id: _scrollBar snapMode: ScrollBar.SnapAlways policy: ScrollBar.AlwaysOn contentItem: Rectangle { implicitWidth: _scrollBar.interactive ? 13 : 4 implicitHeight: _scrollBar.interactive ? 13 : 4 color: "#333" opacity: _scrollBar.pressed ? 0.7 : _scrollBar.interactive && _scrollBar.hovered ? 0.5 : 0.2 radius: 0 } background: Rectangle { implicitWidth: _scrollBar.interactive ? 16 : 4 implicitHeight: _scrollBar.interactive ? 16 : 4 color: "#0e000000" opacity: 0.0 visible: _scrollBar.interactive radius: 0 } } onCurrentItemChanged: { - _millerControl.currentFMList = currentItem.currentFMList - control.setCurrentFMList() + _millerControl.currentFMList = currentItem.currentFMList + _millerControl.currentFMModel = currentItem.currentFMModel + control.setCurrentFMList() currentItem.forceActiveFocus() } onCountChanged: { _millerColumns.currentIndex = _millerColumns.count-1 _millerColumns.positionViewAtEnd() } Maui.PathList { id: _millerList path: control.path onPathChanged: { _millerColumns.currentIndex = _millerColumns.count-1 _millerColumns.positionViewAtEnd() } } model: Maui.BaseModel { id: _millerModel list: _millerList } delegate: Item { - property alias currentFMList : _millersFMList - property int _index : index + property alias currentFMList : _millersFMList + property alias currentFMModel : _millersFMModel + property int _index : index width: Math.min(Kirigami.Units.gridUnit * 22, control.width) height: parent.height focus: true function forceActiveFocus() { _millerListView.forceActiveFocus() } Kirigami.Separator { anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right width: 1 z: 999 } Maui.FMList { id: _millersFMList path: model.path onlyDirs: settings.onlyDirs filterType: settings.filterType filters: settings.filters sortBy: settings.sortBy } Maui.ListBrowser { id: _millerListView anchors.fill: parent topMargin: Maui.Style.contentMargins showPreviewThumbnails: settings.showThumbnails keepEmblemOverlay: settings.selectionMode showDetailsInfo: true onKeyPress: _millerControl.keyPress(event) currentIndex : 0 onCurrentIndexChanged: _millerControl.currentIndex = currentIndex BrowserHolder { id: _holder browser: currentFMList } holder.visible: _holder.visible holder.emoji: _holder.emoji holder.title: _holder.title holder.body: _holder.body holder.emojiSize: _holder.emojiSize section.delegate: Maui.LabelDelegate { id: delegate width: parent.width height: Maui.Style.toolBarHeightAlt label: String(section).toUpperCase() labelTxt.font.pointSize: Maui.Style.fontSizes.big isSection: true } onAreaClicked: { _millerColumns.currentIndex = _index _millerControl.areaClicked(mouse) } onAreaRightClicked: { _millerColumns.currentIndex = _index _millerControl.areaRightClicked() } model: Maui.BaseModel { + id: _millersFMModel list: _millersFMList + recursiveFilteringEnabled: true + sortCaseSensitivity: Qt.CaseInsensitive + filterCaseSensitivity: Qt.CaseInsensitive } delegate: Maui.ListBrowserDelegate { id: delegate width: parent.width height: _millerListView.itemSize + Maui.Style.space.big leftPadding: Maui.Style.space.small rightPadding: Maui.Style.space.small padding: 0 showDetailsInfo: _millerListView.showDetailsInfo folderSize : _millerListView.itemSize showTooltip: true showEmblem: _millerListView.showEmblem keepEmblemOverlay : _millerListView.keepEmblemOverlay showThumbnails: _millerListView.showPreviewThumbnails rightEmblem: _millerListView.rightEmblem isSelected: selectionBar ? selectionBar.contains(model.path) : false leftEmblem: isSelected ? "list-remove" : "list-add" draggable: true Maui.Badge { iconName: "link" anchors.left: parent.left anchors.bottom: parent.bottom visible: (model.issymlink == true) || (model.issymlink == "true") } Connections { target: selectionBar onUriRemoved: { if(uri === model.path) delegate.isSelected = false } onUriAdded: { if(uri === model.path) delegate.isSelected = true } onCleared: delegate.isSelected = false } Connections { target: delegate onClicked: { _millerColumns.currentIndex = _index _millerListView.currentIndex = index _millerControl.itemClicked(index) } onDoubleClicked: { _millerColumns.currentIndex = _index _millerListView.currentIndex = index _millerControl.itemDoubleClicked(index) } onPressAndHold: { _millerColumns.currentIndex = _index _millerListView.currentIndex = index _millerControl.itemRightClicked(index) } onRightClicked: { _millerColumns.currentIndex = _index _millerListView.currentIndex = index _millerControl.itemRightClicked(index) } onRightEmblemClicked: { _millerColumns.currentIndex = _index _millerListView.currentIndex = index _millerControl.rightEmblemClicked(index) } onLeftEmblemClicked: { _millerColumns.currentIndex = _index _millerListView.currentIndex = index _millerControl.leftEmblemClicked(index) } onContentDropped: { _dropMenu.urls = drop.urls.join(",") _dropMenu.target = model.path _dropMenu.popup() } } } } } } } } } diff --git a/src/controls/private/FileMenu.qml b/src/controls/private/FileMenu.qml index d919298..fd83a73 100644 --- a/src/controls/private/FileMenu.qml +++ b/src/controls/private/FileMenu.qml @@ -1,194 +1,195 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import org.kde.mauikit 1.0 as Maui import org.kde.kirigami 2.2 as Kirigami Menu { id: control implicitWidth: colorBar.implicitWidth + Maui.Style.space.big property var item : ({}) property int index : -1 property bool isDir : false property bool isExec : false property bool isFav: false signal bookmarkClicked(var item) signal removeClicked(var item) signal shareClicked(var item) signal copyClicked(var item) signal cutClicked(var item) signal renameClicked(var item) signal tagsClicked(var item) MenuItem { visible: !control.isExec text: qsTr("Select") icon.name: "edit-select" onTriggered: { addToSelection(currentFMList.get(index)) if(Kirigami.Settings.isMobile) settings.selectionMode = true } } MenuItem { text: control.isFav ? qsTr("UnFav") : qsTr("Fav") icon.name: "love" onTriggered: { if(currentFMList.favItem(item.path)) control.isFav = !control.isFav } } MenuSeparator{} MenuItem { visible: control.isDir icon.name: "tab-new" text: qsTr("Open in tab") onTriggered: openTab(item.path) } MenuSeparator{visible: isDir} MenuItem { visible: !control.isExec text: qsTr("Copy") icon.name: "edit-copy" onTriggered: { copyClicked(control.item) close() } } MenuItem { visible: !control.isExec text: qsTr("Cut") icon.name: "edit-cut" onTriggered: { cutClicked(control.item) close() } } MenuItem { visible: !control.isExec text: qsTr("Rename") icon.name: "edit-rename" onTriggered: { renameClicked(control.item) close() } } MenuSeparator{} MenuItem { visible: !control.isExec && control.isDir text: qsTr("Bookmark") icon.name: "bookmark-new" onTriggered: { bookmarkClicked(control.item) close() } } MenuItem { visible: !control.isExec text: qsTr("Tags") icon.name: "tag" onTriggered: { tagsClicked(control.item) close() } } MenuItem { visible: !control.isExec text: qsTr("Share") icon.name: "document-share" onTriggered: { shareClicked(control.item) close() } } MenuItem { visible: !control.isExec text: qsTr("Preview") icon.name: "view-preview" onTriggered: { previewer.show(control.item.path) close() } } MenuSeparator{} MenuItem { text: qsTr("Remove") Kirigami.Theme.textColor: Kirigami.Theme.negativeTextColor icon.name: "edit-delete" onTriggered: { removeClicked(control.item) close() } } MenuSeparator{ visible: colorBar.visible } MenuItem { width: parent.width height: visible ? Maui.Style.iconSizes.medium + Maui.Style.space.big : 0 visible: control.isDir Maui.ColorsBar { id: colorBar visible: parent.visible anchors.centerIn: parent size: Maui.Style.iconSizes.medium onColorPicked: currentFMList.setDirIcon(index, color) } } function show(index) { control.item = currentFMList.get(index) if(item.path.startsWith("tags://") || item.path.startsWith("applications://") ) return if(item) { + console.log("GOT ITEM FILE", index, item.path) control.index = index control.isDir = item.isdir == true || item.isdir == "true" control.isExec = item.executable == true || item.executable == "true" control.isFav = currentFMList.itemIsFav(item.path) popup() } } } diff --git a/src/fm/fm.cpp b/src/fm/fm.cpp index 3d8e96d..88c6de1 100644 --- a/src/fm/fm.cpp +++ b/src/fm/fm.cpp @@ -1,505 +1,509 @@ /* * Copyright 2018 Camilo Higuita * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "fm.h" #ifdef COMPONENT_TAGGING #include "tagging.h" #endif #ifdef COMPONENT_SYNCING #include "syncing.h" #endif #include #include #include #include #include #include #include #include #include #include #include #if defined(Q_OS_ANDROID) #include "mauiandroid.h" #elif defined Q_OS_LINUX #include "mauikde.h" #include #include #include #include #include #include #include #include #endif FM::FM(QObject *parent) : QObject(parent) - #ifdef COMPONENT_SYNCING - ,sync(new Syncing(this)) - #endif - #ifdef COMPONENT_TAGGING - ,tag(Tagging::getInstance()) - #endif - #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) - ,dirLister(new KCoreDirLister(this)) - #endif -{ - -#ifdef Q_OS_ANDROID - MAUIAndroid::checkRunTimePermissions({"android.permission.WRITE_EXTERNAL_STORAGE"}); -#endif - -#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) - this->dirLister->setAutoUpdate(true); - connect(dirLister, static_cast(&KCoreDirLister::completed), [&](QUrl url) - { - qDebug()<< "PATH CONTENT READY" << url; - - FMH::PATH_CONTENT res; - FMH::MODEL_LIST content; - for(const auto &kfile : dirLister->items()) - { - content << FMH::MODEL{ {FMH::MODEL_KEY::LABEL, kfile.name()}, - {FMH::MODEL_KEY::NAME, kfile.name()}, - {FMH::MODEL_KEY::DATE, kfile.time(KFileItem::FileTimes::CreationTime).toString(Qt::TextDate)}, - {FMH::MODEL_KEY::MODIFIED, kfile.time(KFileItem::FileTimes::ModificationTime).toString(Qt::TextDate)}, - {FMH::MODEL_KEY::LAST_READ, kfile.time(KFileItem::FileTimes::AccessTime).toString(Qt::TextDate)}, - {FMH::MODEL_KEY::PATH, kfile.mostLocalUrl().toString()}, - {FMH::MODEL_KEY::THUMBNAIL, kfile.localPath()}, - {FMH::MODEL_KEY::SYMLINK, kfile.linkDest()}, - {FMH::MODEL_KEY::IS_SYMLINK, QVariant(kfile.isLink()).toString()}, - {FMH::MODEL_KEY::HIDDEN, QVariant(kfile.isHidden()).toString()}, - {FMH::MODEL_KEY::IS_DIR, QVariant(kfile.isDir()).toString()}, - {FMH::MODEL_KEY::IS_FILE, QVariant(kfile.isFile()).toString()}, - {FMH::MODEL_KEY::WRITABLE, QVariant(kfile.isWritable()).toString()}, - {FMH::MODEL_KEY::READABLE, QVariant(kfile.isReadable()).toString()}, - {FMH::MODEL_KEY::EXECUTABLE, QVariant(kfile.isDesktopFile()).toString()}, - {FMH::MODEL_KEY::MIME, kfile.mimetype()}, - {FMH::MODEL_KEY::GROUP, kfile.group()}, - {FMH::MODEL_KEY::ICON, kfile.iconName()}, - {FMH::MODEL_KEY::SIZE, QString::number(kfile.size())}, - {FMH::MODEL_KEY::THUMBNAIL, kfile.mostLocalUrl().toString()}, - {FMH::MODEL_KEY::OWNER, kfile.user()}, - // {FMH::MODEL_KEY::FAVORITE, QVariant(this->urlTagExists(kfile.mostLocalUrl().toString(), "fav")).toString()}, - {FMH::MODEL_KEY::COUNT, kfile.isLocalFile() && kfile.isDir() ? QString::number(QDir(kfile.localPath()).count() - 2) : "0"} - }; - } - - res.path = url; - res.content = content; - - emit this->pathContentReady(res); - }); - - connect(dirLister, static_cast(&KCoreDirLister::itemsAdded), [&]() - { - qDebug()<< "MORE ITEMS WERE ADDED"; - emit this->pathContentChanged(dirLister->url()); - }); - - connect(dirLister, static_cast(&KCoreDirLister::newItems), [&]() - { - qDebug()<< "MORE NEW ITEMS WERE ADDED"; - emit this->pathContentChanged(dirLister->url()); - }); - - connect(dirLister, static_cast(&KCoreDirLister::itemsDeleted), [&]() - { - qDebug()<< "ITEMS WERE DELETED"; - dirLister->updateDirectory(dirLister->url()); - // emit this->pathContentChanged(dirLister->url()); // changes when dleted items are not that important? - }); - - connect(dirLister, static_cast > &items)>(&KCoreDirLister::refreshItems), [&]() - { - qDebug()<< "ITEMS WERE REFRESHED"; - dirLister->updateDirectory(dirLister->url()); - emit this->pathContentChanged(dirLister->url()); - - }); -#endif - - -#ifdef COMPONENT_SYNCING - connect(this->sync, &Syncing::listReady, [this](const FMH::MODEL_LIST &list, const QUrl &url) - { - emit this->cloudServerContentReady(list, url); - }); - - connect(this->sync, &Syncing::itemReady, [this](const FMH::MODEL &item, const QUrl &url, const Syncing::SIGNAL_TYPE &signalType) - { - switch(signalType) - { - case Syncing::SIGNAL_TYPE::OPEN: - FMStatic::openUrl(item[FMH::MODEL_KEY::PATH]); - break; - - case Syncing::SIGNAL_TYPE::DOWNLOAD: - emit this->cloudItemReady(item, url); - break; - - case Syncing::SIGNAL_TYPE::COPY: - { - QVariantMap data; - for(auto key : item.keys()) - data.insert(FMH::MODEL_NAME[key], item[key]); - - // this->copy(QVariantList {data}, this->sync->getCopyTo()); - break; - } - default: return; - } - }); - - connect(this->sync, &Syncing::error, [this](const QString &message) - { - emit this->warningMessage(message); - }); - - connect(this->sync, &Syncing::progress, [this](const int &percent) - { - emit this->loadProgress(percent); - }); - - connect(this->sync, &Syncing::dirCreated, [this](const FMH::MODEL &dir, const QUrl &url) - { - emit this->newItem(dir, url); - }); - - connect(this->sync, &Syncing::uploadReady, [this](const FMH::MODEL &item, const QUrl &url) - { - emit this->newItem(item, url); - }); -#endif - } - - void FM::getPathContent(const QUrl& path, const bool &hidden, const bool &onlyDirs, const QStringList& filters, const QDirIterator::IteratorFlags &iteratorFlags) - { - qDebug()<< "Getting async path contents"; - -#if defined Q_OS_ANDROID || defined Q_OS_WIN32 - QFutureWatcher *watcher = new QFutureWatcher; - connect(watcher, &QFutureWatcher::finished, [this, watcher = std::move(watcher)]() - { - emit this->pathContentReady(watcher->future().result()); - watcher->deleteLater(); - }); - - QFuture t1 = QtConcurrent::run([=]() -> FMH::PATH_CONTENT - { - FMH::PATH_CONTENT res; - res.path = path; - - FMH::MODEL_LIST content; - - if (FMStatic::isDir(path)) - { - QDir::Filters dirFilter; - - dirFilter = (onlyDirs ? QDir::AllDirs | QDir::NoDotDot | QDir::NoDot : - QDir::Files | QDir::AllDirs | QDir::NoDotDot | QDir::NoDot); - - if(hidden) - dirFilter = dirFilter | QDir::Hidden | QDir::System; - - QDirIterator it (path.toLocalFile(), filters, dirFilter, iteratorFlags); - while (it.hasNext()) - content << FMH::getFileInfoModel(QUrl::fromLocalFile(it.next())); - } - - res.content = content; - return res; - }); - watcher->setFuture(t1); -#else - - this->dirLister->setShowingDotFiles(hidden); - this->dirLister->setDirOnlyMode(onlyDirs); - - this->dirLister->setNameFilter(filters.join(" ")); - qDebug() << "NAME FILTERS SET" << this->dirLister->nameFilter(); - // if(this->dirLister->url() == path) - // { - // this->dirLister->emitChanges(); - // return; - // } - - if(this->dirLister->openUrl(path)) - qDebug()<< "GETTING PATH CONTENT" << path; - -#endif - - } - - FMH::MODEL_LIST FM::getAppsPath() - { -#if defined Q_OS_ANDROID || defined Q_OS_WIN32 - return FMH::MODEL_LIST(); -#else - - return FMH::MODEL_LIST - { - FMH::MODEL - { - {FMH::MODEL_KEY::ICON, "system-run"}, - {FMH::MODEL_KEY::LABEL, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::APPS_PATH]}, - {FMH::MODEL_KEY::PATH, FMH::PATHTYPE_URI[FMH::PATHTYPE_KEY::APPS_PATH]}, - {FMH::MODEL_KEY::TYPE, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::PLACES_PATH]} - } - }; -#endif - } - - FMH::MODEL_LIST FM::getTags(const int &limit) - { - Q_UNUSED(limit); - FMH::MODEL_LIST data; -#ifdef COMPONENT_TAGGING - if(this->tag) - { - for(const auto &tag : this->tag->getAllTags(false)) - { - QVariantMap item = tag.toMap(); - const auto label = item.value(TAG::KEYMAP[TAG::KEYS::TAG]).toString(); - data << FMH::MODEL - { - {FMH::MODEL_KEY::PATH, FMH::PATHTYPE_URI[FMH::PATHTYPE_KEY::TAGS_PATH]+label}, - {FMH::MODEL_KEY::ICON, "tag"}, - {FMH::MODEL_KEY::MODIFIED, QDateTime::fromString(item.value(TAG::KEYMAP[TAG::KEYS::ADD_DATE]).toString(), Qt::TextDate).toString()}, - {FMH::MODEL_KEY::IS_DIR, "true"}, - {FMH::MODEL_KEY::LABEL, label}, - {FMH::MODEL_KEY::TYPE, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::TAGS_PATH]} - }; - } - } -#endif - - return data; - } - - bool FM::getCloudServerContent(const QUrl &path, const QStringList &filters, const int &depth) - { -#ifdef COMPONENT_SYNCING - const auto __list = path.toString().replace("cloud:///", "/").split("/"); - - if(__list.isEmpty() || __list.size() < 2) - { - qWarning()<< "Could not parse username to get cloud server content"; - return false; - } - - auto user = __list[1]; - // auto data = this->get(QString("select * from clouds where user = '%1'").arg(user)); - QVariantList data; - if(data.isEmpty()) - return false; - - auto map = data.first().toMap(); - - user = map[FMH::MODEL_NAME[FMH::MODEL_KEY::USER]].toString(); - auto server = map[FMH::MODEL_NAME[FMH::MODEL_KEY::SERVER]].toString(); - auto password = map[FMH::MODEL_NAME[FMH::MODEL_KEY::PASSWORD]].toString(); - this->sync->setCredentials(server, user, password); - - this->sync->listContent(path, filters, depth); - return true; -#else - return false; -#endif - } - - void FM::createCloudDir(const QString &path, const QString &name) - { -#ifdef COMPONENT_SYNCING - this->sync->createDir(path, name); -#endif - } - - void FM::openCloudItem(const QVariantMap &item) - { -#ifdef COMPONENT_SYNCING - FMH::MODEL data; - for(const auto &key : item.keys()) - data.insert(FMH::MODEL_NAME_KEY[key], item[key].toString()); - - this->sync->resolveFile(data, Syncing::SIGNAL_TYPE::OPEN); -#endif - } - - void FM::getCloudItem(const QVariantMap &item) - { #ifdef COMPONENT_SYNCING - this->sync->resolveFile(FMH::toModel(item), Syncing::SIGNAL_TYPE::DOWNLOAD); -#endif - } - - QString FM::resolveUserCloudCachePath(const QString &server, const QString &user) - { - return FMH::CloudCachePath+"opendesktop/"+user; - } - - QString FM::resolveLocalCloudPath(const QString& path) - { -#ifdef COMPONENT_SYNCING - return QString(path).replace(FMH::PATHTYPE_URI[FMH::PATHTYPE_KEY::CLOUD_PATH]+this->sync->getUser(), ""); -#else - return QString(); -#endif - } - - static bool doNameFilter(const QString &name, const QStringList &filters) - { - for(const auto &filter : std::accumulate(filters.constBegin(), filters.constEnd(), QVector {}, [](QVector &res, const QString &filter) -> QVector - { res.append(QRegExp(filter, Qt::CaseInsensitive, QRegExp::Wildcard)); return res; })) - { - if(filter.exactMatch(name)) - { - return true; - } - } - return false; - } - - FMH::MODEL_LIST FM::getTagContent(const QString &tag, const QStringList &filters) - { - FMH::MODEL_LIST content; -#ifdef COMPONENT_TAGGING - if(tag.isEmpty()) - { - return this->getTags(); - }else - { - for(const auto &data : this->tag->getUrls(tag, false, [filters](QVariantMap &item) -> bool - { return filters.isEmpty() ? true : doNameFilter(FMH::mapValue(item, FMH::MODEL_KEY::URL), filters); })) - { - const auto url = QUrl(data.toMap()[TAG::KEYMAP[TAG::KEYS::URL]].toString()); - if(url.isLocalFile() && !FMH::fileExists(url)) - continue; - - content << FMH::getFileInfoModel(url); - } - } -#endif - return content; - } - - FMH::MODEL_LIST FM::getUrlTags(const QUrl &url) - { - FMH::MODEL_LIST content; -#ifdef COMPONENT_TAGGING - content = FMH::toModelList(this->tag->getUrlTags(url.toString(), false)); +,sync(new Syncing(this)) #endif - return content; - } - - bool FM::urlTagExists(const QUrl& url, const QString tag) - { -#ifdef COMPONENT_TAGGING - return this->tag->urlTagExists(url.toString(), tag, false); -#endif - } - - bool FM::addTagToUrl(const QString tag, const QUrl& url) - { -#ifdef COMPONENT_TAGGING - return this->tag->tagUrl(url.toString(), tag); -#endif - } - - bool FM::removeTagToUrl(const QString tag, const QUrl& url) - { #ifdef COMPONENT_TAGGING - return this->tag->removeUrlTag(url.toString(), tag); +,tag(Tagging::getInstance()) #endif - } - - bool FM::cut(const QList &urls, const QUrl &where) - { - - for(const auto &url : urls) - { - if(FMStatic::isCloud(url.toString())) - { -#ifdef COMPONENT_SYNCING - this->sync->setCopyTo(where.toString()); - // this->sync->resolveFile(url, Syncing::SIGNAL_TYPE::COPY); +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +,dirLister(new KCoreDirLister(this)) #endif - }else - { - FMStatic::cut(url, where); - } - } - - return true; - } +{ + + #ifdef Q_OS_ANDROID + MAUIAndroid::checkRunTimePermissions({"android.permission.WRITE_EXTERNAL_STORAGE"}); + #endif + + #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + this->dirLister->setAutoUpdate(true); + + const static auto packItem = [](const KFileItem &kfile) -> FMH::MODEL + { + return FMH::MODEL{ {FMH::MODEL_KEY::LABEL, kfile.name()}, + {FMH::MODEL_KEY::NAME, kfile.name()}, + {FMH::MODEL_KEY::DATE, kfile.time(KFileItem::FileTimes::CreationTime).toString(Qt::TextDate)}, + {FMH::MODEL_KEY::MODIFIED, kfile.time(KFileItem::FileTimes::ModificationTime).toString(Qt::TextDate)}, + {FMH::MODEL_KEY::LAST_READ, kfile.time(KFileItem::FileTimes::AccessTime).toString(Qt::TextDate)}, + {FMH::MODEL_KEY::PATH, kfile.mostLocalUrl().toString()}, + {FMH::MODEL_KEY::THUMBNAIL, kfile.localPath()}, + {FMH::MODEL_KEY::SYMLINK, kfile.linkDest()}, + {FMH::MODEL_KEY::IS_SYMLINK, QVariant(kfile.isLink()).toString()}, + {FMH::MODEL_KEY::HIDDEN, QVariant(kfile.isHidden()).toString()}, + {FMH::MODEL_KEY::IS_DIR, QVariant(kfile.isDir()).toString()}, + {FMH::MODEL_KEY::IS_FILE, QVariant(kfile.isFile()).toString()}, + {FMH::MODEL_KEY::WRITABLE, QVariant(kfile.isWritable()).toString()}, + {FMH::MODEL_KEY::READABLE, QVariant(kfile.isReadable()).toString()}, + {FMH::MODEL_KEY::EXECUTABLE, QVariant(kfile.isDesktopFile()).toString()}, + {FMH::MODEL_KEY::MIME, kfile.mimetype()}, + {FMH::MODEL_KEY::GROUP, kfile.group()}, + {FMH::MODEL_KEY::ICON, kfile.iconName()}, + {FMH::MODEL_KEY::SIZE, QString::number(kfile.size())}, + {FMH::MODEL_KEY::THUMBNAIL, kfile.mostLocalUrl().toString()}, + {FMH::MODEL_KEY::OWNER, kfile.user()}, + // {FMH::MODEL_KEY::FAVORITE, QVariant(this->urlTagExists(kfile.mostLocalUrl().toString(), "fav")).toString()}, + {FMH::MODEL_KEY::COUNT, kfile.isLocalFile() && kfile.isDir() ? QString::number(QDir(kfile.localPath()).count() - 2) : "0"} + }; + }; + + const static auto packItems = [](const KFileItemList &items) -> FMH::MODEL_LIST + { + return std::accumulate(items.constBegin(), items.constEnd(), FMH::MODEL_LIST(), [](FMH::MODEL_LIST &res, const KFileItem &item) ->FMH::MODEL_LIST + { + res << packItem(item); + return res; + }); + }; + + connect(dirLister, static_cast(&KCoreDirLister::completed), [&](QUrl url) + { + qDebug()<< "PATH CONTENT READY" << url; + emit this->pathContentReady(url); + }); + + connect(dirLister, static_cast(&KCoreDirLister::itemsAdded), [&](QUrl dirUrl, KFileItemList items) + { + qDebug()<< "MORE ITEMS WERE ADDED"; + emit this->pathContentItemsReady({dirUrl, packItems(items)}); + }); + + // connect(dirLister, static_cast(&KCoreDirLister::newItems), [&](KFileItemList items) + // { + // qDebug()<< "MORE NEW ITEMS WERE ADDED"; + // for(const auto &item : items) + // qDebug()<< "MORE <<" << item.url(); + // + // emit this->pathContentChanged(dirLister->url()); + // }); + + connect(dirLister, static_cast(&KCoreDirLister::itemsDeleted), [&](KFileItemList items) + { + qDebug()<< "ITEMS WERE DELETED"; + emit this->pathContentItemsRemoved({dirLister->url(), packItems(items)}); + }); + + connect(dirLister, static_cast > &items)>(&KCoreDirLister::refreshItems), [&](QList< QPair< KFileItem, KFileItem > > items) + { + qDebug()<< "ITEMS WERE REFRESHED"; + + const auto res = std::accumulate(items.constBegin(), items.constEnd(), QVector>(), [](QVector> &list, const QPair &pair) -> QVector> + { + list << QPair{packItem(pair.first), packItem(pair.second)}; + return list; + }); + + emit this->pathContentItemsChanged(res); + }); + #endif + + #ifdef COMPONENT_SYNCING + connect(this->sync, &Syncing::listReady, [this](const FMH::MODEL_LIST &list, const QUrl &url) + { + emit this->cloudServerContentReady(list, url); + }); + + connect(this->sync, &Syncing::itemReady, [this](const FMH::MODEL &item, const QUrl &url, const Syncing::SIGNAL_TYPE &signalType) + { + switch(signalType) + { + case Syncing::SIGNAL_TYPE::OPEN: + FMStatic::openUrl(item[FMH::MODEL_KEY::PATH]); + break; + + case Syncing::SIGNAL_TYPE::DOWNLOAD: + emit this->cloudItemReady(item, url); + break; + + case Syncing::SIGNAL_TYPE::COPY: + { + QVariantMap data; + for(auto key : item.keys()) + data.insert(FMH::MODEL_NAME[key], item[key]); + + // this->copy(QVariantList {data}, this->sync->getCopyTo()); + break; + } + default: return; + } + }); + + connect(this->sync, &Syncing::error, [this](const QString &message) + { + emit this->warningMessage(message); + }); + + connect(this->sync, &Syncing::progress, [this](const int &percent) + { + emit this->loadProgress(percent); + }); + + connect(this->sync, &Syncing::dirCreated, [this](const FMH::MODEL &dir, const QUrl &url) + { + emit this->newItem(dir, url); + }); + + connect(this->sync, &Syncing::uploadReady, [this](const FMH::MODEL &item, const QUrl &url) + { + emit this->newItem(item, url); + }); + #endif +} + +void FM::getPathContent(const QUrl& path, const bool &hidden, const bool &onlyDirs, const QStringList& filters, const QDirIterator::IteratorFlags &iteratorFlags) +{ + qDebug()<< "Getting async path contents"; + + #if defined Q_OS_ANDROID || defined Q_OS_WIN32 + QFutureWatcher *watcher = new QFutureWatcher; + connect(watcher, &QFutureWatcher::finished, [this, watcher = std::move(watcher)]() + { + emit this->pathContentItemsReady(watcher->future().result()); + watcher->deleteLater(); + }); + + QFuture t1 = QtConcurrent::run([=]() -> FMH::PATH_CONTENT + { + FMH::PATH_CONTENT res; + res.path = path; + + FMH::MODEL_LIST content; + + if (FMStatic::isDir(path)) + { + QDir::Filters dirFilter; + + dirFilter = (onlyDirs ? QDir::AllDirs | QDir::NoDotDot | QDir::NoDot : + QDir::Files | QDir::AllDirs | QDir::NoDotDot | QDir::NoDot); + + if(hidden) + dirFilter = dirFilter | QDir::Hidden | QDir::System; + + QDirIterator it (path.toLocalFile(), filters, dirFilter, iteratorFlags); + while (it.hasNext()) + content << FMH::getFileInfoModel(QUrl::fromLocalFile(it.next())); + } + + res.content = content; + return res; + }); + watcher->setFuture(t1); + #else + + this->dirLister->setShowingDotFiles(hidden); + this->dirLister->setDirOnlyMode(onlyDirs); + + this->dirLister->setNameFilter(filters.join(" ")); + + if(this->dirLister->openUrl(path)) + qDebug()<< "GETTING PATH CONTENT" << path; + + #endif + +} + +FMH::MODEL_LIST FM::getAppsPath() +{ + #if defined Q_OS_ANDROID || defined Q_OS_WIN32 + return FMH::MODEL_LIST(); + #else + + return FMH::MODEL_LIST + { + FMH::MODEL + { + {FMH::MODEL_KEY::ICON, "system-run"}, + {FMH::MODEL_KEY::LABEL, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::APPS_PATH]}, + {FMH::MODEL_KEY::PATH, FMH::PATHTYPE_URI[FMH::PATHTYPE_KEY::APPS_PATH]}, + {FMH::MODEL_KEY::TYPE, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::PLACES_PATH]} + } + }; + #endif +} + +FMH::MODEL_LIST FM::getTags(const int &limit) +{ + Q_UNUSED(limit); + FMH::MODEL_LIST data; + #ifdef COMPONENT_TAGGING + if(this->tag) + { + for(const auto &tag : this->tag->getAllTags(false)) + { + QVariantMap item = tag.toMap(); + const auto label = item.value(TAG::KEYMAP[TAG::KEYS::TAG]).toString(); + data << FMH::MODEL + { + {FMH::MODEL_KEY::PATH, FMH::PATHTYPE_URI[FMH::PATHTYPE_KEY::TAGS_PATH]+label}, + {FMH::MODEL_KEY::ICON, "tag"}, + {FMH::MODEL_KEY::MODIFIED, QDateTime::fromString(item.value(TAG::KEYMAP[TAG::KEYS::ADD_DATE]).toString(), Qt::TextDate).toString()}, + {FMH::MODEL_KEY::IS_DIR, "true"}, + {FMH::MODEL_KEY::LABEL, label}, + {FMH::MODEL_KEY::TYPE, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::TAGS_PATH]} + }; + } + } + #endif + + return data; +} + +bool FM::getCloudServerContent(const QUrl &path, const QStringList &filters, const int &depth) +{ + #ifdef COMPONENT_SYNCING + const auto __list = path.toString().replace("cloud:///", "/").split("/"); + + if(__list.isEmpty() || __list.size() < 2) + { + qWarning()<< "Could not parse username to get cloud server content"; + return false; + } + + auto user = __list[1]; + // auto data = this->get(QString("select * from clouds where user = '%1'").arg(user)); + QVariantList data; + if(data.isEmpty()) + return false; + + auto map = data.first().toMap(); + + user = map[FMH::MODEL_NAME[FMH::MODEL_KEY::USER]].toString(); + auto server = map[FMH::MODEL_NAME[FMH::MODEL_KEY::SERVER]].toString(); + auto password = map[FMH::MODEL_NAME[FMH::MODEL_KEY::PASSWORD]].toString(); + this->sync->setCredentials(server, user, password); + + this->sync->listContent(path, filters, depth); + return true; + #else + return false; + #endif +} + +void FM::createCloudDir(const QString &path, const QString &name) +{ + #ifdef COMPONENT_SYNCING + this->sync->createDir(path, name); + #endif +} - bool FM::copy(const QList &urls, const QUrl &where) - { - QStringList cloudPaths; - for(const auto &url : urls) - { - if(FMStatic::isDir(url)) - { - FMStatic::copy(url, where.toString()+"/"+QFileInfo(url.toLocalFile()).fileName(), false); +void FM::openCloudItem(const QVariantMap &item) +{ + #ifdef COMPONENT_SYNCING + FMH::MODEL data; + for(const auto &key : item.keys()) + data.insert(FMH::MODEL_NAME_KEY[key], item[key].toString()); + + this->sync->resolveFile(data, Syncing::SIGNAL_TYPE::OPEN); + #endif +} + +void FM::getCloudItem(const QVariantMap &item) +{ + #ifdef COMPONENT_SYNCING + this->sync->resolveFile(FMH::toModel(item), Syncing::SIGNAL_TYPE::DOWNLOAD); + #endif +} - }else if(FMStatic::isCloud(url)) - { -#ifdef COMPONENT_SYNCING - this->sync->setCopyTo(where.toString()); - // this->sync->resolveFile(item, Syncing::SIGNAL_TYPE::COPY); -#endif - }else - { - if(FMStatic::isCloud(where)) - cloudPaths << url.toString(); - else - FMStatic::copy(url, where.toString()+"/"+FMH::getFileInfoModel(url)[FMH::MODEL_KEY::LABEL], false); - } - } +QString FM::resolveUserCloudCachePath(const QString &server, const QString &user) +{ + return FMH::CloudCachePath+"opendesktop/"+user; +} -#ifdef COMPONENT_SYNCING - if(!cloudPaths.isEmpty()) - { - qDebug()<<"UPLOAD QUEUE" << cloudPaths; - const auto firstPath = cloudPaths.takeLast(); - this->sync->setUploadQueue(cloudPaths); +QString FM::resolveLocalCloudPath(const QString& path) +{ + #ifdef COMPONENT_SYNCING + return QString(path).replace(FMH::PATHTYPE_URI[FMH::PATHTYPE_KEY::CLOUD_PATH]+this->sync->getUser(), ""); + #else + return QString(); + #endif +} + +static bool doNameFilter(const QString &name, const QStringList &filters) +{ + for(const auto &filter : std::accumulate(filters.constBegin(), filters.constEnd(), QVector {}, [](QVector &res, const QString &filter) -> QVector + { res.append(QRegExp(filter, Qt::CaseInsensitive, QRegExp::Wildcard)); return res; })) + { + if(filter.exactMatch(name)) + { + return true; + } + } + return false; +} + +FMH::MODEL_LIST FM::getTagContent(const QString &tag, const QStringList &filters) +{ + FMH::MODEL_LIST content; + #ifdef COMPONENT_TAGGING + if(tag.isEmpty()) + { + return this->getTags(); + }else + { + for(const auto &data : this->tag->getUrls(tag, false, [filters](QVariantMap &item) -> bool + { return filters.isEmpty() ? true : doNameFilter(FMH::mapValue(item, FMH::MODEL_KEY::URL), filters); })) + { + const auto url = QUrl(data.toMap()[TAG::KEYMAP[TAG::KEYS::URL]].toString()); + if(url.isLocalFile() && !FMH::fileExists(url)) + continue; + + content << FMH::getFileInfoModel(url); + } + } + #endif + return content; +} + +FMH::MODEL_LIST FM::getUrlTags(const QUrl &url) +{ + FMH::MODEL_LIST content; + #ifdef COMPONENT_TAGGING + content = FMH::toModelList(this->tag->getUrlTags(url.toString(), false)); + #endif + return content; +} + +bool FM::urlTagExists(const QUrl& url, const QString tag) +{ + #ifdef COMPONENT_TAGGING + return this->tag->urlTagExists(url.toString(), tag, false); + #endif +} - if(where.toString().split("/").last().contains(".")) - { - QStringList whereList = where.toString().split("/"); - whereList.removeLast(); - auto whereDir = whereList.join("/"); - qDebug()<< "Trying ot copy to cloud" << where << whereDir; +bool FM::addTagToUrl(const QString tag, const QUrl& url) +{ + #ifdef COMPONENT_TAGGING + return this->tag->tagUrl(url.toString(), tag); + #endif +} - this->sync->upload(this->resolveLocalCloudPath(whereDir), firstPath); - } else - this->sync->upload(this->resolveLocalCloudPath(where.toString()), firstPath); - } -#endif +bool FM::removeTagToUrl(const QString tag, const QUrl& url) +{ + #ifdef COMPONENT_TAGGING + return this->tag->removeUrlTag(url.toString(), tag); + #endif +} - return true; - } +bool FM::cut(const QList &urls, const QUrl &where) +{ + + for(const auto &url : urls) + { + if(FMStatic::isCloud(url.toString())) + { + #ifdef COMPONENT_SYNCING + this->sync->setCopyTo(where.toString()); + // this->sync->resolveFile(url, Syncing::SIGNAL_TYPE::COPY); + #endif + }else + { + FMStatic::cut(url, where); + } + } + + return true; +} + +bool FM::copy(const QList &urls, const QUrl &where) +{ + QStringList cloudPaths; + for(const auto &url : urls) + { + if(FMStatic::isDir(url)) + { + FMStatic::copy(url, where.toString()+"/"+QFileInfo(url.toLocalFile()).fileName(), false); + + }else if(FMStatic::isCloud(url)) + { + #ifdef COMPONENT_SYNCING + this->sync->setCopyTo(where.toString()); + // this->sync->resolveFile(item, Syncing::SIGNAL_TYPE::COPY); + #endif + }else + { + if(FMStatic::isCloud(where)) + cloudPaths << url.toString(); + else + FMStatic::copy(url, where.toString()+"/"+FMH::getFileInfoModel(url)[FMH::MODEL_KEY::LABEL], false); + } + } + + #ifdef COMPONENT_SYNCING + if(!cloudPaths.isEmpty()) + { + qDebug()<<"UPLOAD QUEUE" << cloudPaths; + const auto firstPath = cloudPaths.takeLast(); + this->sync->setUploadQueue(cloudPaths); + + if(where.toString().split("/").last().contains(".")) + { + QStringList whereList = where.toString().split("/"); + whereList.removeLast(); + auto whereDir = whereList.join("/"); + qDebug()<< "Trying ot copy to cloud" << where << whereDir; + + this->sync->upload(this->resolveLocalCloudPath(whereDir), firstPath); + } else + this->sync->upload(this->resolveLocalCloudPath(where.toString()), firstPath); + } + #endif + + return true; +} diff --git a/src/fm/fm.h b/src/fm/fm.h index 14ae04c..62cadc1 100644 --- a/src/fm/fm.h +++ b/src/fm/fm.h @@ -1,90 +1,93 @@ #ifndef FM_H #define FM_H #include #include #include #include #include #include #include "fmh.h" #include "fmstatic.h" #ifndef STATIC_MAUIKIT #include "mauikit_export.h" #endif #if defined(Q_OS_ANDROID) #include "mauiandroid.h" #endif #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) class KCoreDirLister; #endif class Syncing; class Tagging; #ifdef STATIC_MAUIKIT class FM : public QObject #else class MAUIKIT_EXPORT FM : public QObject #endif { Q_OBJECT public: Syncing *sync; FM(QObject *parent = nullptr); FMH::MODEL_LIST getTags(const int &limit = 5); FMH::MODEL_LIST getTagContent(const QString &tag, const QStringList &filters = {}); FMH::MODEL_LIST getUrlTags(const QUrl &url); bool urlTagExists(const QUrl& url, const QString tag); bool addTagToUrl(const QString tag, const QUrl &url); bool removeTagToUrl(const QString tag, const QUrl &url); /** Syncing **/ bool getCloudServerContent(const QUrl &server, const QStringList &filters= QStringList(), const int &depth = 0); Q_INVOKABLE void createCloudDir(const QString &path, const QString &name); void getPathContent(const QUrl &path, const bool &hidden = false, const bool &onlyDirs = false, const QStringList &filters = QStringList(), const QDirIterator::IteratorFlags &iteratorFlags = QDirIterator::NoIteratorFlags); QString resolveLocalCloudPath(const QString &path); static FMH::MODEL_LIST getAppsPath(); static QString resolveUserCloudCachePath(const QString &server, const QString &user); private: #ifdef COMPONENT_TAGGING Tagging *tag; #endif #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) KCoreDirLister *dirLister; #endif signals: void cloudServerContentReady(FMH::MODEL_LIST list, const QUrl &url); void cloudItemReady(FMH::MODEL item, QUrl path); //when a item is downloaded and ready - void pathContentReady(FMH::PATH_CONTENT list); + void pathContentReady(QUrl path); + void pathContentItemsReady(FMH::PATH_CONTENT list); void pathContentChanged(QUrl path); + void pathContentItemsChanged(QVector> items); + void pathContentItemsRemoved(FMH::PATH_CONTENT list); void warningMessage(QString message); void loadProgress(int percent); void dirCreated(FMH::MODEL dir); void newItem(FMH::MODEL item, QUrl path); // when a new item is created public slots: void openCloudItem(const QVariantMap &item); void getCloudItem(const QVariantMap &item); /* ACTIONS */ bool copy(const QList &urls, const QUrl &where); bool cut(const QList &urls, const QUrl &where); friend class FMStatic; }; #endif // FM_H diff --git a/src/fm/fmlist.cpp b/src/fm/fmlist.cpp index c996b06..14645d6 100644 --- a/src/fm/fmlist.cpp +++ b/src/fm/fmlist.cpp @@ -1,780 +1,869 @@ /* * * Copyright (C) 2018 camilo higuita * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "fmlist.h" #include "fm.h" #include "utils.h" #include #include #ifdef COMPONENT_SYNCING #include "syncing.h" #endif #if defined Q_OS_LINUX && !defined Q_OS_ANDROID #include #endif #include #include #include #include FMList::FMList(QObject *parent) : MauiList(parent), fm(new FM(this)), watcher(new QFileSystemWatcher(this)) { connect(this->fm, &FM::cloudServerContentReady, [&](const FMH::MODEL_LIST &list, const QUrl &url) { if(this->path == url) { this->assignList(list); } }); - connect(this->fm, &FM::pathContentReady, [&](const FMH::PATH_CONTENT &res) + connect(this->fm, &FM::pathContentReady, [&](QUrl path) { -// if(res.path != this->path) -// return; + emit this->preListChanged(); + this->sortList(); + this->setStatus({STATUS_CODE::READY, this->list.isEmpty() ? "Nothing here!" : "", this->list.isEmpty() ? "This place seems to be empty" : "",this->list.isEmpty() ? "folder-add" : "", this->list.isEmpty(), true}); + emit this->postListChanged(); - this->assignList(res.content); + }); + + connect(this->fm, &FM::pathContentItemsChanged, [&](QVector> res) + { + for(const auto &item : res) + { + const auto index = this->indexOf(FMH::MODEL_KEY::PATH, item.first[FMH::MODEL_KEY::PATH]); + + if(index > this->list.size() || index < 0) + return; + + this->list[index] = item.second; + this->updateModel(index, FMH::modelRoles(item.second)); + } + }); + + connect(this->fm, &FM::pathContentItemsReady, [&](FMH::PATH_CONTENT res) + { + this->appendToList(res.content); + }); + + connect(this->fm, &FM::pathContentItemsRemoved, [&](FMH::PATH_CONTENT res) + { + if(res.path != this->path) + return; + + for(const auto &item : res.content) + { + const auto index = this->indexOf(FMH::MODEL_KEY::PATH, item[FMH::MODEL_KEY::PATH]); + qDebug() << "SUPOSSED TO REMOVED THIS FORM THE LIST" << index << item[FMH::MODEL_KEY::PATH] << this->list[index]; + ; + + this->remove(index); + } }); connect(this->fm, &FM::warningMessage, [&](const QString &message) { emit this->warning(message); }); connect(this->fm, &FM::loadProgress, [&](const int &percent) { emit this->progress(percent); }); // with kio based on android it watches the directory itself, so better relay on that #ifdef Q_OS_ANDROID connect(this->watcher, &QFileSystemWatcher::directoryChanged, [&](const QString &path) { qDebug()<< "FOLDER PATH CHANGED" << path; this->reset(); }); #else connect(this->fm, &FM::pathContentChanged, [&](const QUrl &path) { qDebug()<< "FOLDER PATH CHANGED" << path; if(path != this->path) return; this->sortList(); }); #endif connect(this->fm, &FM::newItem, [&] (const FMH::MODEL &item, const QUrl &url) { if(this->path == url) { emit this->preItemAppended(); this->list << item; emit this->postItemAppended(); } }); const auto value = UTIL::loadSettings("SaveDirProps", "SETTINGS", this->saveDirProps).toBool(); this->setSaveDirProps(value); connect(this, &FMList::pathChanged, this, &FMList::reset); } void FMList::watchPath(const QString& path, const bool& clear) { #ifdef Q_OS_ANDROID if(!this->watcher->directories().isEmpty() && clear) this->watcher->removePaths(this->watcher->directories()); if(path.isEmpty() || !FMH::fileExists(path) || !QUrl(path).isLocalFile()) return; this->watcher->addPath(QString(path).replace("file://", "")); qDebug()<< "WATCHING PATHS" << this->watcher->directories(); #else Q_UNUSED(path) Q_UNUSED(clear) #endif } void FMList::assignList(const FMH::MODEL_LIST& list) { emit this->preListChanged(); this->list =list; this->sortList(); this->count = static_cast(this->list.size()); emit this->countChanged(); this->setStatus({STATUS_CODE::READY, this->list.isEmpty() ? "Nothing here!" : "", this->list.isEmpty() ? "This place seems to be empty" : "",this->list.isEmpty() ? "folder-add" : "", this->list.isEmpty(), true}); emit this->postListChanged(); } +void FMList::appendToList(const FMH::MODEL_LIST& list) +{ + for(const auto &item : list) + { + emit this->preItemAppended(); + this->list << item; + + this->count = static_cast(this->list.size()); + emit this->countChanged(); + + emit this->postItemAppended(); + } +} + +void FMList::clear() +{ + emit this->preListChanged(); + this->list.clear(); + emit this->postListChanged(); +} + void FMList::setList() { qDebug()<< "PATHTYPE FOR URL"<< pathType << this->path.toString() << this->filters << this; switch(this->pathType) { case FMList::PATHTYPE::SEARCH_PATH: this->search(this->path.fileName(), this->searchPath, this->hidden, this->onlyDirs, this->filters); break; //ASYNC case FMList::PATHTYPE::TAGS_PATH: this->assignList(this->fm->getTagContent(this->path.fileName(), QStringList() <filters << FMH::FILTER_LIST[static_cast(this->filterType)])); break; //SYNC case FMList::PATHTYPE::CLOUD_PATH: this->fm->getCloudServerContent(this->path.toString(), this->filters, this->cloudDepth); break; //ASYNC default: { - emit this->preListChanged(); - this->list.clear(); - emit this->postListChanged(); - + this->clear(); const bool exists = this->path.isLocalFile() ? FMH::fileExists(this->path) : true; if(!exists) this->setStatus({STATUS_CODE::ERROR, "Error", "This URL cannot be listed", "documentinfo", this->list.isEmpty(), exists}); else{ this->fm->getPathContent(this->path, this->hidden, this->onlyDirs, QStringList() <filters << FMH::FILTER_LIST[static_cast(this->filterType)]); } break;//ASYNC } } } void FMList::reset() { if(this->saveDirProps) { auto conf = FMH::dirConf(this->path.toString()+"/.directory"); this->sort = static_cast(conf[FMH::MODEL_NAME[FMH::MODEL_KEY::SORTBY]].toInt()); this->hidden = conf[FMH::MODEL_NAME[FMH::MODEL_KEY::HIDDEN]].toBool(); this->foldersFirst = conf[FMH::MODEL_NAME[FMH::MODEL_KEY::FOLDERSFIRST]].toBool(); }else { this->hidden = UTIL::loadSettings("HiddenFilesShown", "SETTINGS", this->hidden).toBool(); this->foldersFirst = UTIL::loadSettings("FoldersFirst", "SETTINGS", this->foldersFirst).toBool(); this->sort = static_cast(UTIL::loadSettings("SortBy", "SETTINGS", this->sort).toInt()); } emit this->sortByChanged(); emit this->hiddenChanged(); emit this->foldersFirstChanged(); this->setList(); } FMH::MODEL_LIST FMList::items() const { return this->list; } FMList::SORTBY FMList::getSortBy() const { return this->sort; } void FMList::setSortBy(const FMList::SORTBY &key) { if(this->sort == key) return; emit this->preListChanged(); this->sort = key; this->sortList(); if(this->pathType == FMList::PATHTYPE::PLACES_PATH && this->trackChanges && this->saveDirProps) FMH::setDirConf(this->path.toString()+"/.directory", "MAUIFM", "SortBy", this->sort); else UTIL::saveSettings("SortBy", this->sort, "SETTINGS"); emit this->sortByChanged(); emit this->postListChanged(); } void FMList::sortList() { const FMH::MODEL_KEY key = static_cast(this->sort); auto index = 0; if(this->foldersFirst) { qSort(this->list.begin(), this->list.end(), [](const FMH::MODEL& e1, const FMH::MODEL& e2) -> bool { Q_UNUSED(e2) const auto key = FMH::MODEL_KEY::MIME; if(e1[key] == "inode/directory") return true; return false; }); for(const auto &item : this->list) if(item[FMH::MODEL_KEY::MIME] == "inode/directory") index++; else break; std::sort(this->list.begin(),this->list.begin() + index, [&key](const FMH::MODEL& e1, const FMH::MODEL& e2) -> bool { switch(key) { case FMH::MODEL_KEY::SIZE: { if(e1[key].toDouble() > e2[key].toDouble()) return true; break; } case FMH::MODEL_KEY::MODIFIED: case FMH::MODEL_KEY::DATE: { auto currentTime = QDateTime::currentDateTime(); auto date1 = QDateTime::fromString(e1[key], Qt::TextDate); auto date2 = QDateTime::fromString(e2[key], Qt::TextDate); if(date1.secsTo(currentTime) < date2.secsTo(currentTime)) return true; break; } case FMH::MODEL_KEY::LABEL: { const auto str1 = QString(e1[key]).toLower(); const auto str2 = QString(e2[key]).toLower(); if(str1 < str2) return true; break; } default: if(e1[key] < e2[key]) return true; } return false; }); } std::sort(this->list.begin() + index, this->list.end(), [key](const FMH::MODEL& e1, const FMH::MODEL& e2) -> bool { const auto role = key; switch(role) { case FMH::MODEL_KEY::MIME: if(e1[role] == "inode/directory") return true; break; case FMH::MODEL_KEY::SIZE: { if(e1[role].toDouble() > e2[role].toDouble()) return true; break; } case FMH::MODEL_KEY::MODIFIED: case FMH::MODEL_KEY::DATE: { auto currentTime = QDateTime::currentDateTime(); auto date1 = QDateTime::fromString(e1[role], Qt::TextDate); auto date2 = QDateTime::fromString(e2[role], Qt::TextDate); if(date1.secsTo(currentTime) < date2.secsTo(currentTime)) return true; break; } case FMH::MODEL_KEY::LABEL: { const auto str1 = QString(e1[role]).toLower(); const auto str2 = QString(e2[role]).toLower(); if(str1 < str2) return true; break; } default: if(e1[role] < e2[role]) return true; } return false; }); } QString FMList::getPathName() const { return this->pathName; } QUrl FMList::getPath() const { return this->path; } void FMList::setPath(const QUrl &path) { if(this->path == path) return; this->searchPath = this->path; this->path = path; NavHistory.appendPath(this->path); this->setStatus({STATUS_CODE::LOADING, "Loading content", "Almost ready!", "view-refresh", true, false}); const auto __scheme = this->path.scheme(); this->pathName = this->path.fileName(); if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::SEARCH_PATH]) { this->pathType = FMList::PATHTYPE::SEARCH_PATH; this->watchPath(QString()); }else if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::CLOUD_PATH]) { this->pathType = FMList::PATHTYPE::CLOUD_PATH; this->watchPath(QString()); }else if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::APPS_PATH]) { this->pathType = FMList::PATHTYPE::APPS_PATH; this->watchPath(QString()); }else if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::TAGS_PATH]) { this->pathType = FMList::PATHTYPE::TAGS_PATH; this->watchPath(QString()); }else if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::TRASH_PATH]) { this->pathType = FMList::PATHTYPE::TRASH_PATH; this->pathName = "Trash"; this->watchPath(QString()); }else if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::PLACES_PATH]) { this->watchPath(this->path.toString()); this->pathType = FMList::PATHTYPE::PLACES_PATH; this->pathName = FMH::getDirInfoModel(this->path)[FMH::MODEL_KEY::LABEL]; }else if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::MTP_PATH]) { this->pathType = FMList::PATHTYPE::MTP_PATH; }else if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::FISH_PATH] ) { this->pathType = FMList::PATHTYPE::FISH_PATH; }else if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::REMOTE_PATH] ) { this->pathType = FMList::PATHTYPE::REMOTE_PATH; }else if(__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::DRIVES_PATH] ) { this->pathType = FMList::PATHTYPE::DRIVES_PATH; }else { this->pathType = FMList::PATHTYPE::OTHER_PATH; } emit this->pathNameChanged(); emit this->pathTypeChanged(); emit this->pathChanged(); } FMList::PATHTYPE FMList::getPathType() const { return this->pathType; } QStringList FMList::getFilters() const { return this->filters; } void FMList::setFilters(const QStringList &filters) { if(this->filters == filters) return; this->filters = filters; emit this->filtersChanged(); this->reset(); } FMList::FILTER FMList::getFilterType() const { return this->filterType; } void FMList::setFilterType(const FMList::FILTER &type) { if(this->filterType == type) return; this->filterType = type; emit this->filterTypeChanged(); this->reset(); } bool FMList::getHidden() const { return this->hidden; } void FMList::setHidden(const bool &state) { if(this->hidden == state) return; this->hidden = state; if(this->pathType == FMList::PATHTYPE::PLACES_PATH && this->trackChanges && this->saveDirProps) FMH::setDirConf(this->path.toString()+"/.directory", "Settings", "HiddenFilesShown", this->hidden); else UTIL::saveSettings("HiddenFilesShown", this->hidden, "SETTINGS"); emit this->hiddenChanged(); this->reset(); } bool FMList::getOnlyDirs() const { return this->onlyDirs; } void FMList::setOnlyDirs(const bool &state) { if(this->onlyDirs == state) return; this->onlyDirs = state; emit this->onlyDirsChanged(); this->reset(); } QVariantMap FMList::get(const int &index) const { if(index >= this->list.size() || index < 0) - return QVariantMap(); - - const auto model = this->list.at(index); + return QVariantMap(); - return FMH::toMap(model); + return FMH::toMap(this->list.at(this->mappedIndex(index))); } void FMList::refresh() { emit this->pathChanged(); } void FMList::createDir(const QString& name) { if(this->pathType == FMList::PATHTYPE::CLOUD_PATH) { #ifdef COMPONENT_SYNCING this->fm->createCloudDir(QString(this->path.toString()).replace(FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::CLOUD_PATH]+"/"+this->fm->sync->getUser(), ""), name); #endif }else { FMStatic::createDir(this->path, name); } } void FMList::copyInto(const QStringList& urls) { this->fm->copy(QUrl::fromStringList(urls), this->path); } void FMList::cutInto(const QStringList& urls) { this->fm->cut(QUrl::fromStringList(urls), this->path); // else if(this->pathType == FMList::PATHTYPE::CLOUD_PATH) // { // this->fm->createCloudDir(QString(this->path).replace(FMH::PATHTYPE_NAME[FMList::PATHTYPE::CLOUD_PATH]+"/"+this->fm->sync->getUser(), ""), name); // } } void FMList::setDirIcon(const int &index, const QString &iconName) { if(index >= this->list.size() || index < 0) return; - const auto path = QUrl(this->list.at(index)[FMH::MODEL_KEY::PATH]); + const auto index_ = this->mappedIndex(index); + + const auto path = QUrl(this->list.at(index_)[FMH::MODEL_KEY::PATH]); if(!FMStatic::isDir(path)) return; FMH::setDirConf(path.toString()+"/.directory", "Desktop Entry", "Icon", iconName); - this->list[index][FMH::MODEL_KEY::ICON] = iconName; - emit this->updateModel(index, QVector {FMH::MODEL_KEY::ICON}); + this->list[index_][FMH::MODEL_KEY::ICON] = iconName; + emit this->updateModel(index_, QVector {FMH::MODEL_KEY::ICON}); } const QUrl FMList::getParentPath() { switch(this->pathType) { case FMList::PATHTYPE::PLACES_PATH: return FMStatic::parentDir(this->path).toString(); default: return this->getPreviousPath(); } } QList FMList::getPosteriorPathHistory() { return QList (); // todo : implement signal } QList FMList::getPreviousPathHistory() { return QList (); // todo : implement signal } const QUrl FMList::getPosteriorPath() { const auto url = NavHistory.getPosteriorPath(); if(url.isEmpty()) return this->path; return url; } const QUrl FMList::getPreviousPath() { const auto url = NavHistory.getPreviousPath(); if(url.isEmpty()) return this->path; return url; } bool FMList::getTrackChanges() const { return this->trackChanges; } void FMList::setTrackChanges(const bool& value) { if(this->trackChanges == value) return; this->trackChanges = value; emit this->trackChangesChanged(); } bool FMList::getFoldersFirst() const { return this->foldersFirst; } void FMList::setFoldersFirst(const bool &value) { if(this->foldersFirst == value) return; emit this->preListChanged(); this->foldersFirst = value; if(this->pathType == FMList::PATHTYPE::PLACES_PATH && this->trackChanges && this->saveDirProps) FMH::setDirConf(this->path.toString()+"/.directory", "MAUIFM", "FoldersFirst", this->foldersFirst); else UTIL::saveSettings("FoldersFirst", this->foldersFirst, "SETTINGS"); emit this->foldersFirstChanged(); this->sortList(); emit this->postListChanged(); } void FMList::setSaveDirProps(const bool& value) { if(this->saveDirProps == value) return; this->saveDirProps = value; UTIL::saveSettings("SaveDirProps", this->saveDirProps, "SETTINGS"); emit this->saveDirPropsChanged(); } bool FMList::getSaveDirProps() const { return this->saveDirProps; } void FMList::search(const QString& query, const QUrl &path, const bool &hidden, const bool &onlyDirs, const QStringList &filters) { qDebug()<< "SEARCHING FOR" << query << path; if(!path.isLocalFile()) { qWarning() << "URL recived is not a local file. So search will only filter the content" << path; this->filterContent(query, path, hidden, onlyDirs, filters); return; } QFutureWatcher *watcher = new QFutureWatcher; connect(watcher, &QFutureWatcher::finished, [=]() { if(this->pathType != FMList::PATHTYPE::SEARCH_PATH) return; const auto res = watcher->future().result(); if(res.path != this->searchPath.toString()) return; this->assignList(res.content); emit this->searchResultReady(); watcher->deleteLater(); }); QFuture t1 = QtConcurrent::run([=]() -> FMH::PATH_CONTENT { FMH::PATH_CONTENT res; res.path = path.toString(); res.content = FMStatic::search(query, path, hidden, onlyDirs, filters); return res; }); watcher->setFuture(t1); } void FMList::filterContent(const QString &query, const QUrl &path, const bool &hidden, const bool &onlyDirs, const QStringList &filters) { if(this->list.isEmpty()) { qDebug() << "Can not filter content. List is empty"; return; } QFutureWatcher *watcher = new QFutureWatcher; connect(watcher, &QFutureWatcher::finished, [=]() { if(this->pathType != FMList::PATHTYPE::SEARCH_PATH) return; const auto res = watcher->future().result(); if(res.path != this->searchPath.toString()) return; this->assignList(res.content); emit this->searchResultReady(); watcher->deleteLater(); }); QFuture t1 = QtConcurrent::run([=]() -> FMH::PATH_CONTENT { FMH::MODEL_LIST m_content; FMH::PATH_CONTENT res; for(const auto &item : this->list) { if(item[FMH::MODEL_KEY::LABEL].contains(query, Qt::CaseInsensitive) || item[FMH::MODEL_KEY::SUFFIX].contains(query, Qt::CaseInsensitive) || item[FMH::MODEL_KEY::MIME].contains(query, Qt::CaseInsensitive)) { if(onlyDirs && item[FMH::MODEL_KEY::IS_DIR] == "true") { m_content << item; continue; } m_content << item; } } res.path = path.toString(); res.content = m_content; return res; }); watcher->setFuture(t1); } int FMList::getCloudDepth() const { return this->cloudDepth; } void FMList::setCloudDepth(const int& value) { if(this->cloudDepth == value) return; this->cloudDepth = value; emit this->cloudDepthChanged(); this->reset(); } uint FMList::getCount() const { return this->count; } PathStatus FMList::getStatus() const { return this->m_status; } void FMList::setStatus(const PathStatus &status) { this->m_status = status; emit this->statusChanged(); } bool FMList::itemIsFav(const QUrl &path) { return FMStatic::isFav(path); } bool FMList::favItem(const QUrl &path) { return FMStatic::toggleFav(path); } + +void FMList::deleteFile(const int& index) +{ + if(index > this->list.size() || index < 0) + return; + + FMStatic::removeFile(this->list[this->mappedIndex(index)][FMH::MODEL_KEY::PATH]); + this->remove(index); +} + +void FMList::moveFileToTrash(const int& index) +{ + if(index > this->list.size() || index < 0) + return; + + FMStatic::moveToTrash(this->list[this->mappedIndex(index)][FMH::MODEL_KEY::PATH]); + this->remove(index); +} + +void FMList::remove(const int& index) +{ + if(index > this->list.size() || index < 0) + return; + + const auto index_ = this->mappedIndex(index); + + emit this->preItemRemoved(index_); + const auto item = this->list.takeAt(index_); + + this->count = static_cast(this->list.size()); + emit this->countChanged(); + + emit this->postItemRemoved(); +} + + diff --git a/src/fm/fmlist.h b/src/fm/fmlist.h index c91dcb7..890d1da 100644 --- a/src/fm/fmlist.h +++ b/src/fm/fmlist.h @@ -1,302 +1,308 @@ /* * * Copyright (C) 2018 Camilo Higuita * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef FMLIST_H #define FMLIST_H #include #include "fmh.h" #include "mauilist.h" enum STATUS_CODE : uint_fast8_t { LOADING, ERROR, READY }; class PathStatus { Q_GADGET Q_PROPERTY(STATUS_CODE code MEMBER m_code) Q_PROPERTY(QString title MEMBER m_title) Q_PROPERTY(QString message MEMBER m_message) Q_PROPERTY(QString icon MEMBER m_icon) Q_PROPERTY(bool empty MEMBER m_empty) Q_PROPERTY(bool exists MEMBER m_exists) public: STATUS_CODE m_code; QString m_title; QString m_message; QString m_icon; bool m_empty = false; bool m_exists = false; }; Q_DECLARE_METATYPE(PathStatus) static inline struct { void appendPath(const QUrl &path) { this->prev_history.append(path); } QUrl getPosteriorPath() { if(this->post_history.isEmpty()) return QUrl(); return this->post_history.takeLast(); } QUrl getPreviousPath() { if(this->prev_history.isEmpty()) return QUrl(); if(this->prev_history.length() < 2) return this->prev_history.at(0); this->post_history.append(this->prev_history.takeLast()); return this->prev_history.takeLast(); } private: QVector prev_history; QVector post_history; } NavHistory; class FM; class QFileSystemWatcher; class FMList : public MauiList { Q_OBJECT //writable Q_PROPERTY(QUrl path READ getPath WRITE setPath NOTIFY pathChanged) Q_PROPERTY(bool hidden READ getHidden WRITE setHidden NOTIFY hiddenChanged) Q_PROPERTY(bool onlyDirs READ getOnlyDirs WRITE setOnlyDirs NOTIFY onlyDirsChanged) Q_PROPERTY(bool foldersFirst READ getFoldersFirst WRITE setFoldersFirst NOTIFY foldersFirstChanged) Q_PROPERTY(int cloudDepth READ getCloudDepth WRITE setCloudDepth NOTIFY cloudDepthChanged) Q_PROPERTY(QStringList filters READ getFilters WRITE setFilters NOTIFY filtersChanged) Q_PROPERTY(FMList::FILTER filterType READ getFilterType WRITE setFilterType NOTIFY filterTypeChanged) Q_PROPERTY(FMList::SORTBY sortBy READ getSortBy WRITE setSortBy NOTIFY sortByChanged) Q_PROPERTY(bool trackChanges READ getTrackChanges WRITE setTrackChanges NOTIFY trackChangesChanged) Q_PROPERTY(bool saveDirProps READ getSaveDirProps WRITE setSaveDirProps NOTIFY saveDirPropsChanged) //readonly Q_PROPERTY(uint count READ getCount NOTIFY countChanged) Q_PROPERTY(QString pathName READ getPathName NOTIFY pathNameChanged) Q_PROPERTY(FMList::PATHTYPE pathType READ getPathType NOTIFY pathTypeChanged) Q_PROPERTY(PathStatus status READ getStatus NOTIFY statusChanged) Q_PROPERTY(QList previousPathHistory READ getPreviousPathHistory) //interface for NavHistory Q_PROPERTY(QList posteriorPathHistory READ getPreviousPathHistory) //interface for NavHistory Q_PROPERTY(QUrl previousPath READ getPreviousPath) //interface for NavHistory Q_PROPERTY(QUrl posteriorPath READ getPosteriorPath) //interface for NavHistory Q_PROPERTY(QUrl parentPath READ getParentPath) public: enum SORTBY : uint_fast8_t { SIZE = FMH::MODEL_KEY::SIZE, MODIFIED = FMH::MODEL_KEY::MODIFIED, DATE = FMH::MODEL_KEY::DATE, LABEL = FMH::MODEL_KEY::LABEL, MIME = FMH::MODEL_KEY::MIME, ADDDATE = FMH::MODEL_KEY::MIME, TITLE = FMH::MODEL_KEY::TITLE, PLACE = FMH::MODEL_KEY::PLACE, FORMAT = FMH::MODEL_KEY::FORMAT }; Q_ENUM(SORTBY) enum FILTER : uint_fast8_t { AUDIO = FMH::FILTER_TYPE::AUDIO, VIDEO= FMH::FILTER_TYPE::VIDEO, TEXT = FMH::FILTER_TYPE::TEXT, IMAGE = FMH::FILTER_TYPE::IMAGE, DOCUMENT = FMH::FILTER_TYPE::DOCUMENT, NONE = FMH::FILTER_TYPE::NONE }; Q_ENUM(FILTER) enum PATHTYPE : uint_fast8_t { PLACES_PATH = FMH::PATHTYPE_KEY::PLACES_PATH, FISH_PATH = FMH::PATHTYPE_KEY::FISH_PATH, MTP_PATH = FMH::PATHTYPE_KEY::MTP_PATH, REMOTE_PATH = FMH::PATHTYPE_KEY::REMOTE_PATH, DRIVES_PATH = FMH::PATHTYPE_KEY::DRIVES_PATH, REMOVABLE_PATH = FMH::PATHTYPE_KEY::REMOVABLE_PATH, TAGS_PATH = FMH::PATHTYPE_KEY::TAGS_PATH, APPS_PATH = FMH::PATHTYPE_KEY::APPS_PATH, TRASH_PATH = FMH::PATHTYPE_KEY::TRASH_PATH, SEARCH_PATH = FMH::PATHTYPE_KEY::SEARCH_PATH, CLOUD_PATH = FMH::PATHTYPE_KEY::CLOUD_PATH, QUICK_PATH = FMH::PATHTYPE_KEY::QUICK_PATH, OTHER_PATH = FMH::PATHTYPE_KEY::OTHER_PATH }; Q_ENUM(PATHTYPE) enum VIEW_TYPE : uint_fast8_t { ICON_VIEW, LIST_VIEW, MILLERS_VIEW }; Q_ENUM(VIEW_TYPE) Q_ENUM(STATUS_CODE) FMList(QObject *parent = nullptr); FMH::MODEL_LIST items() const final override; FMList::SORTBY getSortBy() const; void setSortBy(const FMList::SORTBY &key); QUrl getPath() const; void setPath(const QUrl &path); QString getPathName() const; FMList::PATHTYPE getPathType() const; QStringList getFilters() const; void setFilters(const QStringList &filters); FMList::FILTER getFilterType() const; void setFilterType(const FMList::FILTER &type); bool getHidden() const; void setHidden(const bool &state); bool getOnlyDirs() const; void setOnlyDirs(const bool &state); const QUrl getParentPath(); static QList getPreviousPathHistory(); static QList getPosteriorPathHistory(); const QUrl getPreviousPath(); const QUrl getPosteriorPath(); bool getTrackChanges() const; void setTrackChanges(const bool &value); bool getFoldersFirst() const; void setFoldersFirst(const bool &value); bool getSaveDirProps() const; void setSaveDirProps(const bool &value); int getCloudDepth() const; void setCloudDepth(const int &value); uint getCount() const; void setStatus(const PathStatus &status); PathStatus getStatus() const; private: FM *fm; QFileSystemWatcher *watcher; + void clear(); void reset(); void setList(); - void assignList(const FMH::MODEL_LIST &list); + void assignList(const FMH::MODEL_LIST &list); + void appendToList(const FMH::MODEL_LIST &list); void sortList(); void watchPath(const QString &path, const bool &clear = true); void search(const QString &query, const QUrl &path, const bool &hidden = false, const bool &onlyDirs = false, const QStringList &filters = QStringList()); void filterContent(const QString &query, const QUrl &path, const bool &hidden = false, const bool &onlyDirs = false, const QStringList &filters = QStringList()); FMH::MODEL_LIST list = {{}}; QUrl path; QString pathName = QString(); QStringList filters = {}; bool onlyDirs = false; bool hidden = false; bool trackChanges = true; bool foldersFirst = false; bool saveDirProps = false; int cloudDepth = 1; uint count = 0; QUrl searchPath; PathStatus m_status; FMList::SORTBY sort = FMList::SORTBY::MODIFIED; FMList::FILTER filterType = FMList::FILTER::NONE; FMList::PATHTYPE pathType = FMList::PATHTYPE::PLACES_PATH; QList prevHistory = {}; QList postHistory = {}; public slots: QVariantMap get(const int &index) const; void refresh(); void createDir(const QString &name); void copyInto(const QStringList &urls); void cutInto(const QStringList &urls); void setDirIcon(const int &index, const QString &iconName); bool itemIsFav(const QUrl &path); bool favItem(const QUrl &path); + + void remove(const int &index); + void moveFileToTrash(const int &index); + void deleteFile(const int &index); signals: void pathChanged(); void pathNameChanged(); void pathTypeChanged(); void filtersChanged(); void filterTypeChanged(); void hiddenChanged(); void onlyDirsChanged(); void sortByChanged(); void trackChangesChanged(); void foldersFirstChanged(); void saveDirPropsChanged(); void statusChanged(); void cloudDepthChanged(); void countChanged(); void warning(QString message); void progress(int percent); void searchResultReady(); }; #endif // FMLIST_H diff --git a/src/utils/fmstatic.cpp b/src/utils/fmstatic.cpp index 9c2a827..c71a06b 100644 --- a/src/utils/fmstatic.cpp +++ b/src/utils/fmstatic.cpp @@ -1,450 +1,450 @@ #include "fmstatic.h" #include #include "utils.h" #if defined(Q_OS_ANDROID) #include "mauiandroid.h" #elif defined Q_OS_LINUX #include "mauikde.h" #include #include #include #include #include #include #include #include #include #include #endif #ifdef COMPONENT_TAGGING #include "tagging.h" #endif FMStatic::FMStatic(QObject *parent) : QObject(parent) {} FMH::MODEL_LIST FMStatic::packItems(const QStringList &items, const QString &type) { FMH::MODEL_LIST data; for(const auto &path : items) { if(QUrl(path).isLocalFile() && !FMH::fileExists(path)) continue; auto model = FMH::getFileInfoModel(path); model.insert(FMH::MODEL_KEY::TYPE, type); data << model; } return data; } FMH::MODEL_LIST FMStatic::getDefaultPaths() { return FMStatic::packItems(FMH::defaultPaths, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::PLACES_PATH]); } FMH::MODEL_LIST FMStatic::search(const QString& query, const QUrl &path, const bool &hidden, const bool &onlyDirs, const QStringList &filters) { FMH::MODEL_LIST content; if(!path.isLocalFile()) { qWarning() << "URL recived is not a local file. FM::search" << path; return content; } if (FMStatic::isDir(path)) { QDir::Filters dirFilter; dirFilter = (onlyDirs ? QDir::AllDirs | QDir::NoDotDot | QDir::NoDot : QDir::Files | QDir::AllDirs | QDir::NoDotDot | QDir::NoDot); if(hidden) dirFilter = dirFilter | QDir::Hidden | QDir::System; QDirIterator it (path.toLocalFile(), filters, dirFilter, QDirIterator::Subdirectories); while (it.hasNext()) { auto url = it.next(); if(it.fileName().contains(query, Qt::CaseInsensitive)) { content << FMH::getFileInfoModel(QUrl::fromLocalFile(url)); } } }else qWarning() << "Search path does not exists" << path; qDebug()<< content; return content; } FMH::MODEL_LIST FMStatic::getDevices() { FMH::MODEL_LIST drives; #if defined(Q_OS_ANDROID) drives << packItems(MAUIAndroid::sdDirs(), FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::DRIVES_PATH]); return drives; #endif return drives; } QVariantMap FMStatic::getDirInfo(const QUrl &path, const QString &type) { return FMH::getDirInfo(path, type); } QVariantMap FMStatic::getFileInfo(const QUrl &path) { return FMH::getFileInfo(path); } bool FMStatic::isDefaultPath(const QString &path) { return FMH::defaultPaths.contains(path); } QUrl FMStatic::parentDir(const QUrl &path) { return FMH::parentDir(path); } bool FMStatic::isDir(const QUrl &path) { if(!path.isLocalFile()) { qWarning() << "URL recived is not a local file. FM::isDir" << path; return false; } QFileInfo file(path.toLocalFile()); return file.isDir(); } bool FMStatic::isApp(const QString& path) { return /*QFileInfo(path).isExecutable() ||*/ path.endsWith(".desktop"); } bool FMStatic::isCloud(const QUrl &path) { return path.scheme() == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::CLOUD_PATH]; } bool FMStatic::fileExists(const QUrl &path) { return FMH::fileExists(path); } QString FMStatic::fileDir(const QUrl& path) // the directory path of the file { return FMH::fileDir(path); } void FMStatic::saveSettings(const QString &key, const QVariant &value, const QString &group) { UTIL::saveSettings(key, value, group); } QVariant FMStatic::loadSettings(const QString &key, const QString &group, const QVariant &defaultValue) { return UTIL::loadSettings(key, group, defaultValue); } QString FMStatic::formatSize(const int &size) { QLocale locale; return locale.formattedDataSize(size); } QString FMStatic::formatDate(const QString &dateStr, const QString &format, const QString &initFormat) { QDateTime date; if( initFormat.isEmpty() ) date = QDateTime::fromString(dateStr, Qt::TextDate); else date = QDateTime::fromString(dateStr, initFormat); return date.toString(format); } QString FMStatic::formatTime(const qint64 &value) { QString tStr; if (value) { QTime time((value/3600)%60, (value/60)%60, value%60, (value*1000)%1000); QString format = "mm:ss"; if (value > 3600) format = "hh:mm:ss"; tStr = time.toString(format); } return tStr.isEmpty() ? "00:00" : tStr; } QString FMStatic::homePath() { return FMH::HomePath; } bool FMStatic::copy(const QUrl &url, const QUrl &destinationDir, const bool &overWriteDirectory) { #if defined Q_OS_ANDROID || defined Q_OS_WIN32 QFileInfo fileInfo(url.toLocalFile()); if(fileInfo.isFile()) QFile::copy(url.toLocalFile(), destinationDir.toLocalFile()); QDir originDirectory(url.toLocalFile()); if (!originDirectory.exists()) return false; QDir destinationDirectory(destinationDir.toLocalFile()); if(destinationDirectory.exists() && !overWriteDirectory) return false; else if(destinationDirectory.exists() && overWriteDirectory) destinationDirectory.removeRecursively(); originDirectory.mkpath(destinationDir.toLocalFile()); foreach(QString directoryName, originDirectory.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { QString destinationPath = destinationDir.toLocalFile() + "/" + directoryName; originDirectory.mkpath(destinationPath); copy(url.toLocalFile() + "/" + directoryName, destinationPath, overWriteDirectory); } foreach (QString fileName, originDirectory.entryList(QDir::Files)) { QFile::copy(url.toLocalFile() + "/" + fileName, destinationDir.toLocalFile() + "/" + fileName); } /*! Possible race-condition mitigation? */ QDir finalDestination(destinationDir.toLocalFile()); finalDestination.refresh(); if(finalDestination.exists()) return true; return false; #else auto job = KIO::copy(url, destinationDir); job->start(); return true; #endif } bool FMStatic::cut(const QUrl &url, const QUrl &where) { return FMStatic::cut(url, where, QString()); } bool FMStatic::cut(const QUrl &url, const QUrl &where, const QString &name) { QUrl _where; if(name.isEmpty()) _where = QUrl(where.toString()+"/"+FMH::getFileInfoModel(url)[FMH::MODEL_KEY::LABEL]); else _where = QUrl(where.toString()+"/"+name); #if defined Q_OS_ANDROID || defined Q_OS_WIN32 QFile file(url.toLocalFile()); file.rename(_where.toLocalFile()); #else auto job = KIO::move(url, _where, KIO::HideProgressInfo); job->start(); #endif #ifdef COMPONENT_TAGGING Tagging::getInstance()->updateUrl(url.toString(), _where.toString()); #endif return true; } bool FMStatic::removeFile(const QUrl &path) { - if(!path.isLocalFile()) - qWarning() << "URL recived is not a local file, FM::removeFile" << path; + if(!path.isLocalFile() || !FMH::fileExists(path)) + qWarning() << "URL recived is not a local file or does not exists, FM::removeFile" << path; qDebug()<< "TRYING TO REMOVE FILE: " << path; #ifdef COMPONENT_TAGGING Tagging::getInstance()->removeUrl(path.toString()); #endif #if defined Q_OS_ANDROID || defined Q_OS_WIN32 if(QFileInfo(path.toLocalFile()).isDir()) return FMStatic::removeDir(path); else return QFile(path.toLocalFile()).remove(); #else auto job = KIO::del(path); job->start(); return true; #endif } void FMStatic::moveToTrash(const QUrl &path) { - if(!path.isLocalFile()) - qWarning() << "URL recived is not a local file, FM::moveToTrash" << path; + if(!path.isLocalFile() || !FMH::fileExists(path)) + qWarning() << "URL recived is not a local file or does not exists, FM::moveToTrash" << path; #if defined Q_OS_LINUX && !defined Q_OS_ANDROID auto job = KIO::trash(path); job->start(); #endif } void FMStatic::emptyTrash() { #if defined Q_OS_LINUX && !defined Q_OS_ANDROID auto job = KIO::emptyTrash(); job->start(); #endif } bool FMStatic::removeDir(const QUrl &path) { bool result = true; QDir dir(path.toLocalFile()); qDebug()<< "TRYING TO REMOVE DIR" << path << path.toLocalFile(); if (dir.exists()) { Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) { if (info.isDir()) { result = removeDir(QUrl::fromLocalFile(info.absoluteFilePath())); } else { result = QFile::remove(info.absoluteFilePath()); } if (!result) { return result; } } result = dir.rmdir(path.toLocalFile()); } return result; } bool FMStatic::rename(const QUrl &url, const QString &name) { return FMStatic::cut(url, QUrl(url.toString().left(url.toString().lastIndexOf("/"))), name); } bool FMStatic::createDir(const QUrl &path, const QString &name) { #if defined Q_OS_ANDROID || defined Q_OS_WIN32 QFileInfo dd(path.toLocalFile()); return QDir(path.toLocalFile()).mkdir(name); #else auto job = KIO::mkdir(name.isEmpty() ? path : QUrl(path.toString() + "/" + name)); job->start(); return true; #endif } bool FMStatic::createFile(const QUrl &path, const QString &name) { QFile file(path.toLocalFile() + "/" + name); if(file.open(QIODevice::ReadWrite)) { file.close(); return true; } return false; } bool FMStatic::createSymlink(const QUrl &path, const QUrl &where) { #if defined Q_OS_ANDROID || defined Q_OS_WIN32 return QFile::link(path.toLocalFile(), where.toLocalFile() + "/" + QFileInfo(path.toLocalFile()).fileName()); #else const auto job = KIO::link({path}, where); job->start(); return true; #endif } bool FMStatic::openUrl(const QUrl &url) { #ifdef Q_OS_ANDROID MAUIAndroid::openUrl(url.toString()); return true; #elif defined Q_OS_LINUX // return QDesktopServices::openUrl(QUrl::fromUserInput(url)); return KRun::runUrl(url, FMH::getFileInfoModel(url)[FMH::MODEL_KEY::MIME], nullptr, false, KRun::RunFlag::DeleteTemporaryFiles); #elif defined Q_OS_WIN32 return QDesktopServices::openUrl(url); #endif } void FMStatic::openLocation(const QStringList &urls) { for(const auto &url : urls) QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(url).dir().absolutePath())); } const QVariantMap FMStatic::dirConf(const QUrl &path) { return FMH::dirConf(path); } void FMStatic::setDirConf(const QUrl &path, const QString &group, const QString &key, const QVariant &value) { FMH::setDirConf(path, group, key, value); } bool FMStatic::checkFileType(const int& type, const QString& mimeTypeName) { return FMH::SUPPORTED_MIMETYPES[static_cast(type)].contains(mimeTypeName); } bool FMStatic::toggleFav(const QUrl& url) { if(FMStatic::isFav(url)) return FMStatic::unFav(url); return FMStatic::fav(url); } bool FMStatic::fav(const QUrl& url) { #ifdef COMPONENT_TAGGING return Tagging::getInstance()->tagUrl(url.toString(), "fav", "#e91e63"); #endif } bool FMStatic::unFav(const QUrl& url) { #ifdef COMPONENT_TAGGING return Tagging::getInstance()->removeUrlTag(url.toString(), "fav"); #endif } bool FMStatic::isFav(const QUrl& url, const bool &strict) { #ifdef COMPONENT_TAGGING return Tagging::getInstance()->urlTagExists(url.toString(), "fav", strict); #endif } diff --git a/src/utils/model_template/mauilist.h b/src/utils/model_template/mauilist.h index 09c936e..2d52b1b 100644 --- a/src/utils/model_template/mauilist.h +++ b/src/utils/model_template/mauilist.h @@ -1,79 +1,79 @@ /* * * Copyright (C) 2019 camilo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef MAUILIST_H #define MAUILIST_H #include "fmh.h" #ifndef STATIC_MAUIKIT #include "mauikit_export.h" #endif #include /** * @todo write docs */ #include class MauiModel; #ifdef STATIC_MAUIKIT class MauiList : public QObject, public QQmlParserStatus #else class MAUIKIT_EXPORT MauiList : public QObject, public QQmlParserStatus #endif { Q_INTERFACES(QQmlParserStatus) Q_OBJECT Q_PROPERTY(int count READ getCount NOTIFY countChanged) public: /** * Default constructor */ explicit MauiList(QObject *parent = nullptr); virtual FMH::MODEL_LIST items() const = 0; virtual void classBegin() override {} virtual void componentComplete() override {} int getCount() const {return items().size(); } const MauiModel *m_model; //becarefull this is owned by qml engine, this is only supossed to be a viewer public slots: - int mappedIndex(const int &index) const; + int mappedIndex(const int &index) const; protected: bool exists(const FMH::MODEL_KEY &key, const QString &value) const; int indexOf(const FMH::MODEL_KEY &key, const QString &value) const; signals: void preItemAppended(); void postItemAppended(); void preItemAppendedAt(int index); void preItemRemoved(int index); void postItemRemoved(); void updateModel(int index, QVector roles); void preListChanged(); void postListChanged(); void countChanged(); }; #endif // MAUILIST_H diff --git a/src/utils/model_template/mauimodel.cpp b/src/utils/model_template/mauimodel.cpp index 9d20f21..9442803 100644 --- a/src/utils/model_template/mauimodel.cpp +++ b/src/utils/model_template/mauimodel.cpp @@ -1,262 +1,262 @@ /* * * Copyright (C) 2019 camilo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mauimodel.h" #include "mauilist.h" MauiModel::MauiModel(QObject *parent) : QSortFilterProxyModel(parent), m_model(new PrivateAbstractListModel(this)) { this->setSourceModel(this->m_model); this->setDynamicSortFilter(true); } void MauiModel::setFilterString(const QString& string) { this->setFilterCaseSensitivity(Qt::CaseInsensitive); this->setFilterFixedString(string); // this->setFilterRegExp(QRegExp(string, Qt::CaseInsensitive)); } void MauiModel::setSortOrder(const int &sortOrder) { this->sort(0, static_cast(sortOrder)); } QVariantMap MauiModel::get(const int& index) { QVariantMap res; if(index >= this->rowCount() || index < 0) return res; for(const auto &role : this->roleNames()) res.insert(role, this->index(index, 0).data(FMH::MODEL_NAME_KEY[role]).toString()); return res; } QVariantList MauiModel::getAll() { QVariantList res; for(auto i = 0; i < this->rowCount(); i++) res << this->get(i); return res; } void MauiModel::setFilter(const QString& filter) { if(this->m_filter == filter) return; this->m_filter = filter; emit this->filterChanged(this->m_filter); this->setFilterFixedString(this->m_filter); } const QString MauiModel::getFilter() const { return this->m_filter; } void MauiModel::setSortOrder(const Qt::SortOrder& sortOrder) { if(this->m_sortOrder == sortOrder) return; this->m_sortOrder = sortOrder; emit this->sortOrderChanged(this->m_sortOrder); this->sort(0, this->m_sortOrder); } Qt::SortOrder MauiModel::getSortOrder() const { return this->m_sortOrder; } void MauiModel::setSort(const QString& sort) { if(this->m_sort == sort) return; this->m_sort = sort; emit this->sortChanged(this->m_sort); this->setSortRole([sort, roles = this->roleNames()]() -> int { for(const auto key : roles.keys()) { if(roles[key] == sort) { qDebug()<< "FOUND ROLE KEY "<< key << roles[key] << sort; return key; } } return -1; }()); this->sort(0, this->m_sortOrder); } QString MauiModel::getSort() const { return this->m_sort; } bool MauiModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { if(this->filterRole() != Qt::DisplayRole) { QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); const auto data = this->sourceModel()->data(index, this->filterRole()).toString(); return data.contains(this->filterRegExp()); } for(const auto role : this->sourceModel()->roleNames()) { QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); const auto data = this->sourceModel()->data(index, FMH::MODEL_NAME_KEY[role]).toString(); if(data.contains(this->filterRegExp())) return true; else continue; } return false; } MauiList *MauiModel::getList() const { return this->m_model->getList(); } MauiList * MauiModel::PrivateAbstractListModel::getList() const { return this->list; } void MauiModel::PrivateAbstractListModel::setList(MauiList* value) { beginResetModel(); if(this->list) this->list->disconnect(this); this->list = value; if(this->list) { connect(this->list, &MauiList::preItemAppendedAt, this, [=](int index) { beginInsertRows(QModelIndex(), index, index); }); connect(this->list, &MauiList::preItemAppended, this, [=]() { const int index = this->list->items().size(); beginInsertRows(QModelIndex(), index, index); }); connect(this->list, &MauiList::postItemAppended, this, [=]() { emit this->list->countChanged(); endInsertRows(); }); connect(this->list, &MauiList::preItemRemoved, this, [=](int index) { - beginRemoveRows(QModelIndex(), index, index); + beginRemoveRows(QModelIndex(), index, index); }); connect(this->list, &MauiList::postItemRemoved, this, [=]() { emit this->list->countChanged(); endRemoveRows(); }); connect(this->list, &MauiList::updateModel, this, [=](int index, QVector roles) { emit this->dataChanged(this->index(index), this->index(index), roles); }); connect(this->list, &MauiList::preListChanged, this, [=]() { beginResetModel(); }); connect(this->list, &MauiList::postListChanged, this, [=]() { emit this->list->countChanged(); endResetModel(); }); } endResetModel(); } void MauiModel::setList(MauiList *value) { this->m_model->setList(value); this->getList()->m_model = this; } -MauiModel::PrivateAbstractListModel::PrivateAbstractListModel(QObject *parent) -: QAbstractListModel(parent), list(nullptr) {} +MauiModel::PrivateAbstractListModel::PrivateAbstractListModel(MauiModel *model) +: QAbstractListModel(model), list(nullptr), m_model(model) {} int MauiModel::PrivateAbstractListModel::rowCount(const QModelIndex &parent) const { if (parent.isValid() || !list) return 0; return list->items().size(); } QVariant MauiModel::PrivateAbstractListModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || !list) return QVariant(); return list->items().at(index.row())[static_cast(role)]; } bool MauiModel::PrivateAbstractListModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_UNUSED(index); Q_UNUSED(value); Q_UNUSED(role); return false; } Qt::ItemFlags MauiModel::PrivateAbstractListModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsEditable; // FIXME: Implement me! } QHash MauiModel::PrivateAbstractListModel::roleNames() const { QHash names; for(const auto &key : FMH::MODEL_NAME.keys()) names[key] = QString(FMH::MODEL_NAME[key]).toUtf8(); return names; } diff --git a/src/utils/model_template/mauimodel.h b/src/utils/model_template/mauimodel.h index bdd0c9a..53fb499 100644 --- a/src/utils/model_template/mauimodel.h +++ b/src/utils/model_template/mauimodel.h @@ -1,108 +1,108 @@ /* * * Copyright (C) 2019 camilo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef MAUIMODEL_H #define MAUIMODEL_H #include #include #include #include class MauiList; #ifndef STATIC_MAUIKIT #include "mauikit_export.h" #endif #ifdef STATIC_MAUIKIT class MauiModel : public QSortFilterProxyModel #else class MAUIKIT_EXPORT MauiModel : public QSortFilterProxyModel #endif { Q_OBJECT Q_PROPERTY(MauiList *list READ getList WRITE setList) Q_PROPERTY(QString filter READ getFilter WRITE setFilter NOTIFY filterChanged) Q_PROPERTY(Qt::SortOrder sortOrder READ getSortOrder WRITE setSortOrder NOTIFY sortOrderChanged) Q_PROPERTY(QString sort READ getSort WRITE setSort NOTIFY sortChanged) public: MauiModel(QObject *parent = nullptr); MauiList* getList() const; void setList(MauiList *value); protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; private: class PrivateAbstractListModel; PrivateAbstractListModel *m_model; QString m_filter; Qt::SortOrder m_sortOrder; QString m_sort; public slots: void setFilterString(const QString &string); //deprecrated void setSortOrder(const int &sortOrder); //deprecrated QVariantMap get(const int &index); QVariantList getAll(); void setFilter(const QString &filter); const QString getFilter() const; void setSortOrder(const Qt::SortOrder &sortOrder); Qt::SortOrder getSortOrder() const; void setSort(const QString &sort); QString getSort() const; signals: void listChanged(); void filterChanged(QString filter); void sortOrderChanged(Qt::SortOrder sortOrder); void sortChanged(QString sort); }; class MauiModel::PrivateAbstractListModel : public QAbstractListModel { Q_OBJECT public: - PrivateAbstractListModel(QObject *parent = nullptr); + PrivateAbstractListModel(MauiModel *model); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; // Editable: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; virtual QHash roleNames() const override; MauiList* getList() const; void setList(MauiList *value); private: MauiList *list; - + MauiModel *m_model; }; #endif // MAUIMODEL_H