diff --git a/src/controls/FileBrowser.qml b/src/controls/FileBrowser.qml index 1bf6441..e73b8ae 100644 --- a/src/controls/FileBrowser.qml +++ b/src/controls/FileBrowser.qml @@ -1,1192 +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.middleContent: Maui.TextField { Layout.fillWidth: true visible: control.currentFMList.count > 0 placeholderText: qsTr("Filter") + " " + control.currentFMList.count + " " + qsTr("files") onAccepted: control.browserView.filter = text onCleared: control.browserView.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) } 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: Maui.Handy.isTouch 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: Maui.Handy.isTouch && 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/utils/fmh.h b/src/utils/fmh.h index 9be7069..096a8b0 100644 --- a/src/utils/fmh.h +++ b/src/utils/fmh.h @@ -1,1112 +1,1113 @@ /* * 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. */ #ifndef FMH_H #define FMH_H #include #include #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 #include #include #include #endif // #ifdef COMPONENT_TAGGING // #include "tagging.h" // #endif namespace FMH { static constexpr bool isAndroid() { #if defined(Q_OS_ANDROID) return true; #elif defined(Q_OS_LINUX) return false; #elif defined(Q_OS_WIN32) return false; #elif defined(Q_OS_WIN64) return false; #elif defined(Q_OS_MACOS) return false; #elif defined(Q_OS_IOS) return false; #elif defined(Q_OS_HAIKU) return false; #endif } static constexpr bool isWindows() { #if defined(Q_OS_ANDROID) return false; #elif defined(Q_OS_LINUX) return false; #elif defined(Q_OS_WIN32) return true; #elif defined(Q_OS_WIN64) return true; #elif defined(Q_OS_MACOS) return false; #elif defined(Q_OS_IOS) return false; #elif defined(Q_OS_HAIKU) return false; #endif } static constexpr bool isLinux() { #if defined(Q_OS_ANDROID) return false; #elif defined(Q_OS_LINUX) return true; #elif defined(Q_OS_WIN32) return false; #elif defined(Q_OS_WIN64) return false; #elif defined(Q_OS_MACOS) return false; #elif defined(Q_OS_IOS) return false; #elif defined(Q_OS_HAIKU) return false; #endif } static constexpr bool isMac() { #if defined(Q_OS_ANDROID) return false; #elif defined(Q_OS_LINUX) return false; #elif defined(Q_OS_WIN32) return false; #elif defined(Q_OS_WIN64) return false; #elif defined(Q_OS_MACOS) return true; #elif defined(Q_OS_IOS) return false; #elif defined(Q_OS_HAIKU) return false; #endif } enum FILTER_TYPE : int { AUDIO, VIDEO, TEXT, IMAGE, DOCUMENT, NONE }; static QStringList AUDIO_MIMETYPES = {"audio/mpeg","audio/mp4","audio/flac","audio/ogg","audio/wav"}; static QStringList VIDEO_MIMETYPES = {"video/mp4", "video/x-matroska","video/webm","video/avi","video/flv","video/mpg", "video/wmv","video/mov","video/ogg","video/mpeg", "video/jpeg"}; static QStringList TEXT_MIMETYPES = {"text/markdown","text/x-chdr", "text/x-c++src", "text/x-c++hdr", "text/css", "text/html", "text/plain", "text/richtext", "text/scriptlet", "text/x-vcard", "text/x-go", "text/x-cmake", "text/x-qml", "application/xml", "application/javascript", "application/json", "application/pgp-keys", "application/x-shellscript", "application/x-cmakecache", "application/x-kicad-project"}; static QStringList IMAGE_MIMETYPES = {"image/webp" , "image/png" , "image/gif" , "image/jpeg" , "image/web" , "image/svg" , "image/svg+xml"}; static QStringList DOCUMENT_MIMETYPES = {"application/pdf","application/rtf","application/doc","application/odf"}; static QMap SUPPORTED_MIMETYPES { {FMH::FILTER_TYPE::AUDIO, AUDIO_MIMETYPES}, {FMH::FILTER_TYPE::VIDEO, VIDEO_MIMETYPES}, {FMH::FILTER_TYPE::TEXT, TEXT_MIMETYPES}, {FMH::FILTER_TYPE::IMAGE, IMAGE_MIMETYPES}, {FMH::FILTER_TYPE::DOCUMENT, DOCUMENT_MIMETYPES} }; static const QStringList getMimeTypeSuffixes(const FMH::FILTER_TYPE &type, QString(*cb)(QString) = nullptr) { QStringList res; QMimeDatabase mimedb; for(const auto &mime : FMH::SUPPORTED_MIMETYPES[type]) { if(cb) for(const QString &_suffix : mimedb.mimeTypeForName(mime).suffixes()) res << cb(_suffix); else res << mimedb.mimeTypeForName(mime).suffixes(); } return res; } static const QHash FILTER_LIST = { {FILTER_TYPE::AUDIO, FMH::getMimeTypeSuffixes(FMH::FILTER_TYPE::AUDIO, [](QString suffix) -> QString {return "*."+suffix;})}, {FILTER_TYPE::VIDEO, FMH::getMimeTypeSuffixes(FMH::FILTER_TYPE::VIDEO, [](QString suffix)-> QString{return "*."+suffix;})}, {FILTER_TYPE::TEXT, FMH::getMimeTypeSuffixes(FMH::FILTER_TYPE::TEXT, [](QString suffix)-> QString{return "*."+suffix;})}, {FILTER_TYPE::DOCUMENT, FMH::getMimeTypeSuffixes(FMH::FILTER_TYPE::DOCUMENT, [](QString suffix)-> QString{return "*."+suffix;})}, {FILTER_TYPE::IMAGE, FMH::getMimeTypeSuffixes(FMH::FILTER_TYPE::IMAGE, [](QString suffix)-> QString{return "*."+suffix;})}, {FILTER_TYPE::NONE, QStringList()} }; enum MODEL_KEY : int { ICON, LABEL, PATH, URL, TYPE, GROUP, OWNER, SUFFIX, NAME, DATE, SIZE, MODIFIED, MIME, TAG, PERMISSIONS, THUMBNAIL, THUMBNAIL_1, THUMBNAIL_2, THUMBNAIL_3, HIDDEN, ICONSIZE, DETAILVIEW, SHOWTHUMBNAIL, SHOWTERMINAL, COUNT, SORTBY, USER, PASSWORD, SERVER, FOLDERSFIRST, VIEWTYPE, ADDDATE, FAV, FAVORITE, COLOR, RATE, FORMAT, PLACE, LOCATION, ALBUM, ARTIST, TRACK, DURATION, ARTWORK, PLAYLIST, LYRICS, WIKI, MOOD, SOURCETYPE, GENRE, NOTE, COMMENT, CONTEXT, SOURCE, TITLE, ID, PARENT_ID, RELEASEDATE, LICENSE, DESCRIPTION, BOOKMARK, ACCOUNT, ACCOUNTTYPE, VERSION, DOMAIN_M, CATEGORY, CONTENT, PIN, IMG, PREVIEW, LINK, STAMP, BOOK, /** ccdav keys **/ N, PHOTO, GENDER, ADR, ADR_2, ADR_3, EMAIL, EMAIL_2, EMAIL_3, LANG, NICKNAME, ORG, PROFILE, TZ, TEL, TEL_2, TEL_3, IM, /** other keys **/ CITY, STATE, COUNTRY, /** keys from opendesktop store **/ PACKAGE_ARCH, PACKAGE_TYPE, GPG_FINGERPRINT, GPG_SIGNATURE, PACKAGE_NAME, PRICE, REPOSITORY, TAGS, WAY, PIC, SMALL_PIC, CHANGED, COMMENTS, CREATED, DETAIL_PAGE, DETAILS, TOTAL_DOWNLOADS, GHNS_EXCLUDED, LANGUAGE, PERSON_ID, SCORE, SUMMARY, TYPE_ID, TYPE_NAME, XDG_TYPE, //file props SYMLINK, IS_SYMLINK, IS_DIR, IS_FILE, IS_REMOTE, EXECUTABLE, READABLE, WRITABLE, LAST_READ, }; static const QHash MODEL_NAME = { {MODEL_KEY::ICON, "icon"}, {MODEL_KEY::LABEL, "label"}, {MODEL_KEY::PATH, "path"}, {MODEL_KEY::URL, "url"}, {MODEL_KEY::TYPE, "type"}, {MODEL_KEY::GROUP, "group"}, {MODEL_KEY::OWNER, "owner"}, {MODEL_KEY::SUFFIX, "suffix"}, {MODEL_KEY::NAME, "name"}, {MODEL_KEY::DATE, "date"}, {MODEL_KEY::MODIFIED, "modified"}, {MODEL_KEY::MIME, "mime"}, {MODEL_KEY::SIZE, "size"}, {MODEL_KEY::TAG, "tag"}, {MODEL_KEY::PERMISSIONS, "permissions"}, {MODEL_KEY::THUMBNAIL, "thumbnail"}, {MODEL_KEY::THUMBNAIL_1, "thumbnail_1"}, {MODEL_KEY::THUMBNAIL_2, "thumbnail_2"}, {MODEL_KEY::THUMBNAIL_3, "thumbnail_3"}, {MODEL_KEY::ICONSIZE, "iconsize"}, {MODEL_KEY::HIDDEN, "hidden"}, {MODEL_KEY::DETAILVIEW, "detailview"}, {MODEL_KEY::SHOWTERMINAL, "showterminal"}, {MODEL_KEY::SHOWTHUMBNAIL, "showthumbnail"}, {MODEL_KEY::COUNT, "count"}, {MODEL_KEY::SORTBY, "sortby"}, {MODEL_KEY::USER, "user"}, {MODEL_KEY::PASSWORD, "password"}, {MODEL_KEY::SERVER, "server"}, {MODEL_KEY::FOLDERSFIRST, "foldersfirst"}, {MODEL_KEY::VIEWTYPE, "viewtype"}, {MODEL_KEY::ADDDATE, "adddate"}, {MODEL_KEY::FAV, "fav"}, {MODEL_KEY::FAVORITE, "favorite"}, {MODEL_KEY::COLOR, "color"}, {MODEL_KEY::RATE, "rate"}, {MODEL_KEY::FORMAT, "format"}, {MODEL_KEY::PLACE, "place"}, {MODEL_KEY::LOCATION, "location"}, {MODEL_KEY::ALBUM, "album"}, {MODEL_KEY::DURATION, "duration"}, {MODEL_KEY::RELEASEDATE, "releasedate"}, {MODEL_KEY::ARTIST, "artist"}, {MODEL_KEY::LYRICS, "lyrics"}, {MODEL_KEY::TRACK, "track"}, {MODEL_KEY::GENRE, "genre"}, {MODEL_KEY::WIKI, "wiki"}, {MODEL_KEY::CONTEXT, "context"}, {MODEL_KEY::SOURCETYPE, "sourcetype"}, {MODEL_KEY::ARTWORK, "artwork"}, {MODEL_KEY::NOTE, "note"}, {MODEL_KEY::MOOD, "mood"}, {MODEL_KEY::COMMENT, "comment"}, {MODEL_KEY::PLAYLIST, "playlist"}, {MODEL_KEY::SOURCE, "source"}, {MODEL_KEY::TITLE, "title"}, {MODEL_KEY::ID, "id"}, {MODEL_KEY::PERSON_ID, "personid"}, {MODEL_KEY::PARENT_ID, "parentid"}, {MODEL_KEY::LICENSE, "license"}, {MODEL_KEY::DESCRIPTION, "description"}, {MODEL_KEY::BOOKMARK, "bookmark"}, {MODEL_KEY::ACCOUNT, "account"}, {MODEL_KEY::ACCOUNTTYPE, "accounttype"}, {MODEL_KEY::VERSION, "version"}, {MODEL_KEY::DOMAIN_M, "domain"}, {MODEL_KEY::CATEGORY, "category"}, {MODEL_KEY::CONTENT, "content"}, {MODEL_KEY::PIN, "pin"}, {MODEL_KEY::IMG, "img"}, {MODEL_KEY::PREVIEW, "preview"}, {MODEL_KEY::LINK, "link"}, {MODEL_KEY::STAMP, "stamp"}, {MODEL_KEY::BOOK, "book"}, /** ccdav keys **/ {MODEL_KEY::N, "n"}, {MODEL_KEY::IM, "im"}, {MODEL_KEY::PHOTO, "photo"}, {MODEL_KEY::GENDER, "gender"}, {MODEL_KEY::ADR, "adr"}, {MODEL_KEY::ADR_2, "adr2"}, {MODEL_KEY::ADR_3, "adr3"}, {MODEL_KEY::EMAIL, "email"}, {MODEL_KEY::EMAIL_2, "email2"}, {MODEL_KEY::EMAIL_3, "email3"}, {MODEL_KEY::LANG, "lang"}, {MODEL_KEY::NICKNAME, "nickname"}, {MODEL_KEY::ORG, "org"}, {MODEL_KEY::PROFILE, "profile"}, {MODEL_KEY::TZ, "tz"}, {MODEL_KEY::TEL, "tel"}, {MODEL_KEY::TEL_2, "tel2"}, {MODEL_KEY::TEL_3, "tel3"}, {MODEL_KEY::CITY, "city"}, {MODEL_KEY::STATE, "state"}, {MODEL_KEY::COUNTRY, "country"}, // opendesktop keys {MODEL_KEY::PACKAGE_ARCH, "packagearch"}, {MODEL_KEY::PACKAGE_TYPE, "packagetype"}, {MODEL_KEY::GPG_FINGERPRINT, "gpgfingerprint"}, {MODEL_KEY::GPG_SIGNATURE, "gpgsignature"}, {MODEL_KEY::PACKAGE_NAME, "packagename"}, {MODEL_KEY::PRICE, "price"}, {MODEL_KEY::REPOSITORY, "repository"}, {MODEL_KEY::TAGS, "tags"}, {MODEL_KEY::WAY, "way"}, {MODEL_KEY::PIC, "pic"}, {MODEL_KEY::SMALL_PIC, "smallpic"}, {MODEL_KEY::CHANGED, "changed"}, {MODEL_KEY::COMMENTS, "comments"}, {MODEL_KEY::CREATED, "created"}, {MODEL_KEY::DETAIL_PAGE, "detailpage"}, {MODEL_KEY::DETAILS, "details"}, {MODEL_KEY::TOTAL_DOWNLOADS, "totaldownloads"}, {MODEL_KEY::GHNS_EXCLUDED, "ghnsexcluded"}, {MODEL_KEY::LANGUAGE, "language"}, {MODEL_KEY::SCORE, "score"}, {MODEL_KEY::SUMMARY, "summary"}, {MODEL_KEY::TYPE_ID, "typeid"}, {MODEL_KEY::TYPE_NAME, "typename"}, {MODEL_KEY::XDG_TYPE, "xdgtype"}, //file props {MODEL_KEY::SYMLINK, "symlink"}, {MODEL_KEY::IS_SYMLINK, "issymlink"}, {MODEL_KEY::LAST_READ, "lastread"}, {MODEL_KEY::READABLE, "readable"}, {MODEL_KEY::WRITABLE, "writeable"}, {MODEL_KEY::IS_DIR, "isdir"}, {MODEL_KEY::IS_FILE, "isfile"}, {MODEL_KEY::IS_REMOTE, "isremote"}, {MODEL_KEY::EXECUTABLE, "executable"} }; static const QHash MODEL_NAME_KEY = { {MODEL_NAME[MODEL_KEY::ICON], MODEL_KEY::ICON}, {MODEL_NAME[MODEL_KEY::LABEL], MODEL_KEY::LABEL}, {MODEL_NAME[MODEL_KEY::PATH], MODEL_KEY::PATH}, {MODEL_NAME[MODEL_KEY::URL], MODEL_KEY::URL}, {MODEL_NAME[MODEL_KEY::TYPE], MODEL_KEY::TYPE}, {MODEL_NAME[MODEL_KEY::GROUP], MODEL_KEY::GROUP}, {MODEL_NAME[MODEL_KEY::OWNER], MODEL_KEY::OWNER}, {MODEL_NAME[MODEL_KEY::SUFFIX], MODEL_KEY::SUFFIX}, {MODEL_NAME[MODEL_KEY::NAME], MODEL_KEY::NAME}, {MODEL_NAME[MODEL_KEY::DATE], MODEL_KEY::DATE}, {MODEL_NAME[MODEL_KEY::MODIFIED], MODEL_KEY::MODIFIED}, {MODEL_NAME[MODEL_KEY::MIME], MODEL_KEY::MIME}, {MODEL_NAME[MODEL_KEY::SIZE], MODEL_KEY::SIZE,}, {MODEL_NAME[MODEL_KEY::TAG], MODEL_KEY::TAG}, {MODEL_NAME[MODEL_KEY::PERMISSIONS], MODEL_KEY::PERMISSIONS}, {MODEL_NAME[MODEL_KEY::THUMBNAIL], MODEL_KEY::THUMBNAIL}, {MODEL_NAME[MODEL_KEY::THUMBNAIL_1], MODEL_KEY::THUMBNAIL_1}, {MODEL_NAME[MODEL_KEY::THUMBNAIL_2], MODEL_KEY::THUMBNAIL_2}, {MODEL_NAME[MODEL_KEY::THUMBNAIL_3], MODEL_KEY::THUMBNAIL_3}, {MODEL_NAME[MODEL_KEY::ICONSIZE], MODEL_KEY::ICONSIZE}, {MODEL_NAME[MODEL_KEY::HIDDEN], MODEL_KEY::HIDDEN}, {MODEL_NAME[MODEL_KEY::DETAILVIEW], MODEL_KEY::DETAILVIEW}, {MODEL_NAME[MODEL_KEY::SHOWTERMINAL], MODEL_KEY::SHOWTERMINAL}, {MODEL_NAME[MODEL_KEY::SHOWTHUMBNAIL], MODEL_KEY::SHOWTHUMBNAIL}, {MODEL_NAME[MODEL_KEY::COUNT], MODEL_KEY::COUNT}, {MODEL_NAME[MODEL_KEY::SORTBY], MODEL_KEY::SORTBY}, {MODEL_NAME[MODEL_KEY::USER], MODEL_KEY::USER}, {MODEL_NAME[MODEL_KEY::PASSWORD], MODEL_KEY::PASSWORD}, {MODEL_NAME[MODEL_KEY::SERVER], MODEL_KEY::SERVER}, {MODEL_NAME[MODEL_KEY::VIEWTYPE], MODEL_KEY::VIEWTYPE}, {MODEL_NAME[MODEL_KEY::ADDDATE], MODEL_KEY::ADDDATE}, {MODEL_NAME[MODEL_KEY::FAV], MODEL_KEY::FAV}, {MODEL_NAME[MODEL_KEY::FAVORITE], MODEL_KEY::FAVORITE}, {MODEL_NAME[MODEL_KEY::COLOR], MODEL_KEY::COLOR}, {MODEL_NAME[MODEL_KEY::RATE], MODEL_KEY::RATE}, {MODEL_NAME[MODEL_KEY::FORMAT], MODEL_KEY::FORMAT}, {MODEL_NAME[MODEL_KEY::PLACE], MODEL_KEY::PLACE}, {MODEL_NAME[MODEL_KEY::LOCATION], MODEL_KEY::LOCATION}, {MODEL_NAME[MODEL_KEY::ALBUM], MODEL_KEY::ALBUM}, {MODEL_NAME[MODEL_KEY::ARTIST], MODEL_KEY::ARTIST}, {MODEL_NAME[MODEL_KEY::DURATION], MODEL_KEY::DURATION}, {MODEL_NAME[MODEL_KEY::TRACK], MODEL_KEY::TRACK}, {MODEL_NAME[MODEL_KEY::GENRE], MODEL_KEY::GENRE}, {MODEL_NAME[MODEL_KEY::LYRICS], MODEL_KEY::LYRICS}, {MODEL_NAME[MODEL_KEY::RELEASEDATE], MODEL_KEY::RELEASEDATE}, {MODEL_NAME[MODEL_KEY::FORMAT], MODEL_KEY::FORMAT}, {MODEL_NAME[MODEL_KEY::WIKI], MODEL_KEY::WIKI}, {MODEL_NAME[MODEL_KEY::SOURCETYPE], MODEL_KEY::SOURCETYPE}, {MODEL_NAME[MODEL_KEY::ARTWORK], MODEL_KEY::ARTWORK}, {MODEL_NAME[MODEL_KEY::NOTE], MODEL_KEY::NOTE}, {MODEL_NAME[MODEL_KEY::MOOD], MODEL_KEY::MOOD}, {MODEL_NAME[MODEL_KEY::COMMENT], MODEL_KEY::COMMENT}, {MODEL_NAME[MODEL_KEY::CONTEXT], MODEL_KEY::CONTEXT}, {MODEL_NAME[MODEL_KEY::SOURCE], MODEL_KEY::SOURCE}, {MODEL_NAME[MODEL_KEY::TITLE], MODEL_KEY::TITLE}, {MODEL_NAME[MODEL_KEY::ID], MODEL_KEY::ID}, {MODEL_NAME[MODEL_KEY::PARENT_ID], MODEL_KEY::PARENT_ID}, {MODEL_NAME[MODEL_KEY::LICENSE], MODEL_KEY::LICENSE}, {MODEL_NAME[MODEL_KEY::DESCRIPTION], MODEL_KEY::DESCRIPTION}, {MODEL_NAME[MODEL_KEY::BOOKMARK], MODEL_KEY::BOOKMARK}, {MODEL_NAME[MODEL_KEY::ACCOUNT], MODEL_KEY::ACCOUNT}, {MODEL_NAME[MODEL_KEY::ACCOUNTTYPE], MODEL_KEY::ACCOUNTTYPE}, {MODEL_NAME[MODEL_KEY::VERSION], MODEL_KEY::VERSION}, {MODEL_NAME[MODEL_KEY::DOMAIN_M], MODEL_KEY::DOMAIN_M}, {MODEL_NAME[MODEL_KEY::CATEGORY], MODEL_KEY::CATEGORY}, {MODEL_NAME[MODEL_KEY::CONTENT], MODEL_KEY::CONTENT}, {MODEL_NAME[MODEL_KEY::PIN], MODEL_KEY::PIN}, {MODEL_NAME[MODEL_KEY::IMG], MODEL_KEY::IMG}, {MODEL_NAME[MODEL_KEY::PREVIEW], MODEL_KEY::PREVIEW}, {MODEL_NAME[MODEL_KEY::LINK], MODEL_KEY::LINK}, {MODEL_NAME[MODEL_KEY::STAMP], MODEL_KEY::STAMP}, {MODEL_NAME[MODEL_KEY::BOOK], MODEL_KEY::BOOK}, /** ccdav keys **/ {MODEL_NAME[MODEL_KEY::N], MODEL_KEY::N}, {MODEL_NAME[MODEL_KEY::IM], MODEL_KEY::IM}, {MODEL_NAME[MODEL_KEY::PHOTO], MODEL_KEY::PHOTO}, {MODEL_NAME[MODEL_KEY::GENDER], MODEL_KEY::GENDER}, {MODEL_NAME[MODEL_KEY::ADR], MODEL_KEY::ADR}, {MODEL_NAME[MODEL_KEY::ADR_2], MODEL_KEY::ADR_2}, {MODEL_NAME[MODEL_KEY::ADR_3], MODEL_KEY::ADR_3}, {MODEL_NAME[MODEL_KEY::EMAIL], MODEL_KEY::EMAIL}, {MODEL_NAME[MODEL_KEY::EMAIL_2], MODEL_KEY::EMAIL_2}, {MODEL_NAME[MODEL_KEY::EMAIL_3], MODEL_KEY::EMAIL_3}, {MODEL_NAME[MODEL_KEY::LANG], MODEL_KEY::LANG}, {MODEL_NAME[MODEL_KEY::NICKNAME], MODEL_KEY::NICKNAME}, {MODEL_NAME[MODEL_KEY::ORG], MODEL_KEY::ORG}, {MODEL_NAME[MODEL_KEY::PROFILE], MODEL_KEY::PROFILE}, {MODEL_NAME[MODEL_KEY::TZ], MODEL_KEY::TZ}, {MODEL_NAME[MODEL_KEY::TEL], MODEL_KEY::TEL}, {MODEL_NAME[MODEL_KEY::TEL_2], MODEL_KEY::TEL_2}, {MODEL_NAME[MODEL_KEY::TEL_3], MODEL_KEY::TEL_3}, {MODEL_NAME[MODEL_KEY::CITY], MODEL_KEY::CITY}, {MODEL_NAME[MODEL_KEY::STATE], MODEL_KEY::STATE}, {MODEL_NAME[MODEL_KEY::COUNTRY], MODEL_KEY::COUNTRY}, //opendesktop store keys {MODEL_NAME[MODEL_KEY::PACKAGE_ARCH], MODEL_KEY::PACKAGE_ARCH}, {MODEL_NAME[MODEL_KEY::PACKAGE_TYPE], MODEL_KEY::PACKAGE_TYPE}, {MODEL_NAME[MODEL_KEY::GPG_FINGERPRINT], MODEL_KEY::GPG_FINGERPRINT}, {MODEL_NAME[MODEL_KEY::GPG_SIGNATURE], MODEL_KEY::GPG_SIGNATURE}, {MODEL_NAME[MODEL_KEY::PACKAGE_NAME], MODEL_KEY::PACKAGE_NAME}, {MODEL_NAME[MODEL_KEY::PRICE], MODEL_KEY::PRICE}, {MODEL_NAME[MODEL_KEY::REPOSITORY], MODEL_KEY::REPOSITORY}, {MODEL_NAME[MODEL_KEY::TAGS], MODEL_KEY::TAGS}, {MODEL_NAME[MODEL_KEY::WAY], MODEL_KEY::WAY}, {MODEL_NAME[MODEL_KEY::PIC], MODEL_KEY::PIC}, {MODEL_NAME[MODEL_KEY::SMALL_PIC], MODEL_KEY::SMALL_PIC}, {MODEL_NAME[MODEL_KEY::CHANGED], MODEL_KEY::CHANGED}, {MODEL_NAME[MODEL_KEY::COMMENTS], MODEL_KEY::COMMENTS}, {MODEL_NAME[MODEL_KEY::CREATED], MODEL_KEY::CREATED}, {MODEL_NAME[MODEL_KEY::DETAIL_PAGE], MODEL_KEY::DETAIL_PAGE}, {MODEL_NAME[MODEL_KEY::DETAILS], MODEL_KEY::DETAILS}, {MODEL_NAME[MODEL_KEY::TOTAL_DOWNLOADS], MODEL_KEY::TOTAL_DOWNLOADS}, {MODEL_NAME[MODEL_KEY::GHNS_EXCLUDED], MODEL_KEY::GHNS_EXCLUDED}, {MODEL_NAME[MODEL_KEY::LANGUAGE], MODEL_KEY::LANGUAGE}, {MODEL_NAME[MODEL_KEY::PERSON_ID], MODEL_KEY::PERSON_ID}, {MODEL_NAME[MODEL_KEY::SCORE], MODEL_KEY::SCORE}, {MODEL_NAME[MODEL_KEY::SUMMARY], MODEL_KEY::SUMMARY}, {MODEL_NAME[MODEL_KEY::TYPE_ID], MODEL_KEY::TYPE_ID}, {MODEL_NAME[MODEL_KEY::TYPE_NAME], MODEL_KEY::TYPE_NAME}, {MODEL_NAME[MODEL_KEY::XDG_TYPE], MODEL_KEY::XDG_TYPE}, //file props {MODEL_NAME[MODEL_KEY::SYMLINK], MODEL_KEY::SYMLINK}, {MODEL_NAME[MODEL_KEY::IS_SYMLINK], MODEL_KEY::IS_SYMLINK}, {MODEL_NAME[MODEL_KEY::LAST_READ], MODEL_KEY::LAST_READ}, {MODEL_NAME[MODEL_KEY::READABLE], MODEL_KEY::READABLE}, {MODEL_NAME[MODEL_KEY::WRITABLE], MODEL_KEY::WRITABLE}, {MODEL_NAME[MODEL_KEY::IS_DIR], MODEL_KEY::IS_DIR}, {MODEL_NAME[MODEL_KEY::IS_FILE], MODEL_KEY::IS_FILE}, {MODEL_NAME[MODEL_KEY::IS_REMOTE], MODEL_KEY::IS_REMOTE}, {MODEL_NAME[MODEL_KEY::EXECUTABLE], MODEL_KEY::EXECUTABLE} }; //for now here to later on use it to allow auto cast qvariant to qstring template class MHash : public QHash { public: using QHash::QHash; MHash(const QHash &other) : QHash(other) {} }; typedef QHash MODEL; typedef QVector MODEL_LIST; static const inline QVector modelRoles(const FMH::MODEL &model) { const auto keys = model.keys(); return std::accumulate(keys.begin(), keys.end(), QVector(), [](QVector &res, const FMH::MODEL_KEY &key) { res.append(key); return res; }); } static const inline QString mapValue(const QVariantMap &map, const FMH::MODEL_KEY &key) { return map[FMH::MODEL_NAME[key]].toString(); } static const inline QVariantMap toMap(const FMH::MODEL& model) { QVariantMap map; for(const auto &key : model.keys()) map.insert(FMH::MODEL_NAME[key], model[key]); return map; } static const inline FMH::MODEL toModel(const QVariantMap& map) { FMH::MODEL model; for(const auto &key : map.keys()) model.insert(FMH::MODEL_NAME_KEY[key], map[key].toString()); return model; } static const inline FMH::MODEL_LIST toModelList(const QVariantList& list) { FMH::MODEL_LIST res; for(const auto &data : list) res << FMH::toModel(data.toMap()); return res; } static const inline QVariantList toMapList(const FMH::MODEL_LIST& list) { QVariantList res; for(const auto &data : list) res << FMH::toMap(data); return res; } static const inline FMH::MODEL filterModel(const FMH::MODEL &model, const QVector &keys) { FMH::MODEL res; for(const auto &key : keys) { if(model.contains(key)) res[key] = model[key]; } return res; } static const inline QStringList modelToList(const FMH::MODEL &model, const FMH::MODEL_KEY &key) { QStringList res; for(const auto &item : model) { if(item.contains(key)) res << item[key]; } return res; } struct PATH_CONTENT { QUrl path; // the url holding all the content FMH::MODEL_LIST content; // the content from the url }; #if defined Q_OS_ANDROID || defined Q_OS_WIN32 enum PATHTYPE_KEY : int { PLACES_PATH, REMOTE_PATH, DRIVES_PATH, REMOVABLE_PATH, TAGS_PATH, UNKNOWN_TYPE, APPS_PATH, TRASH_PATH, SEARCH_PATH, CLOUD_PATH, FISH_PATH, MTP_PATH, QUICK_PATH, OTHER_PATH, }; #else enum PATHTYPE_KEY : int { PLACES_PATH = KFilePlacesModel::GroupType::PlacesType, REMOTE_PATH = KFilePlacesModel::GroupType::RemoteType, DRIVES_PATH = KFilePlacesModel::GroupType::DevicesType, REMOVABLE_PATH = KFilePlacesModel::GroupType::RemovableDevicesType, TAGS_PATH = KFilePlacesModel::GroupType::TagsType, UNKNOWN_TYPE = KFilePlacesModel::GroupType::UnknownType, APPS_PATH = 9, TRASH_PATH = 10, SEARCH_PATH = 11, CLOUD_PATH = 12, FISH_PATH = 13, MTP_PATH = 14, QUICK_PATH = 15, OTHER_PATH = 16, }; #endif static const QHash PATHTYPE_SCHEME = { {PATHTYPE_KEY::PLACES_PATH, "file"}, {PATHTYPE_KEY::DRIVES_PATH, "drives"}, {PATHTYPE_KEY::APPS_PATH, "applications"}, {PATHTYPE_KEY::REMOTE_PATH, "remote"}, {PATHTYPE_KEY::REMOVABLE_PATH, "removable"}, {PATHTYPE_KEY::UNKNOWN_TYPE, "Unkown"}, {PATHTYPE_KEY::TRASH_PATH, "trash"}, {PATHTYPE_KEY::TAGS_PATH, "tags"}, {PATHTYPE_KEY::SEARCH_PATH, "search"}, {PATHTYPE_KEY::CLOUD_PATH, "cloud"}, {PATHTYPE_KEY::FISH_PATH, "fish"}, {PATHTYPE_KEY::MTP_PATH, "mtp"} }; static const QHash PATHTYPE_URI = { {PATHTYPE_KEY::PLACES_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::PLACES_PATH] + "://"}, {PATHTYPE_KEY::DRIVES_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::DRIVES_PATH] + "://"}, {PATHTYPE_KEY::APPS_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::APPS_PATH] + ":///"}, {PATHTYPE_KEY::REMOTE_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::REMOTE_PATH] + "://"}, {PATHTYPE_KEY::REMOVABLE_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::REMOVABLE_PATH] + "://"}, {PATHTYPE_KEY::UNKNOWN_TYPE, PATHTYPE_SCHEME[PATHTYPE_KEY::UNKNOWN_TYPE] + "://"}, {PATHTYPE_KEY::TRASH_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::TRASH_PATH] + "://"}, {PATHTYPE_KEY::TAGS_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::TAGS_PATH] + ":///"}, {PATHTYPE_KEY::SEARCH_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::SEARCH_PATH] + "://"}, {PATHTYPE_KEY::CLOUD_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::CLOUD_PATH] + ":///"}, {PATHTYPE_KEY::FISH_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::FISH_PATH] + "://"}, {PATHTYPE_KEY::MTP_PATH, PATHTYPE_SCHEME[PATHTYPE_KEY::MTP_PATH] + "://"} }; static const QHash PATHTYPE_LABEL = { {PATHTYPE_KEY::PLACES_PATH, ("Places")}, {PATHTYPE_KEY::DRIVES_PATH, ("Drives")}, {PATHTYPE_KEY::APPS_PATH, ("Apps")}, {PATHTYPE_KEY::REMOTE_PATH, ("Remote")}, {PATHTYPE_KEY::REMOVABLE_PATH, ("Removable")}, {PATHTYPE_KEY::UNKNOWN_TYPE, ("Unknown")}, {PATHTYPE_KEY::TRASH_PATH, ("Trash")}, {PATHTYPE_KEY::TAGS_PATH, ("Tags")}, {PATHTYPE_KEY::SEARCH_PATH, ("Search")}, {PATHTYPE_KEY::CLOUD_PATH, ("Cloud")}, {PATHTYPE_KEY::FISH_PATH, ("Remote")}, {PATHTYPE_KEY::MTP_PATH, ("Drives")}, {PATHTYPE_KEY::OTHER_PATH, ("Others")}, {PATHTYPE_KEY::QUICK_PATH, ("Quick")} }; const QString DataPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); + const QString ConfigPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)).toString(); const QString CloudCachePath = FMH::DataPath+"/Cloud/"; const QString DesktopPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).toString(); const QString AppsPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)).toString(); const QString RootPath = "file:///"; #if defined(Q_OS_ANDROID) const QString PicturesPath = QUrl::fromLocalFile(PATHS::PicturesPath).toString(); const QString DownloadsPath = QUrl::fromLocalFile(PATHS::DownloadsPath).toString(); const QString DocumentsPath = QUrl::fromLocalFile(PATHS::DocumentsPath).toString(); const QString HomePath = QUrl::fromLocalFile(PATHS::HomePath).toString(); const QString MusicPath = QUrl::fromLocalFile(PATHS::MusicPath).toString(); const QString VideosPath = QUrl::fromLocalFile(PATHS::VideosPath).toString(); const QStringList defaultPaths = { FMH::HomePath, FMH::DocumentsPath, FMH::PicturesPath, FMH::MusicPath, FMH::VideosPath, FMH::DownloadsPath }; #else const QString PicturesPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)).toString(); const QString DownloadsPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).toString(); const QString DocumentsPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).toString(); const QString HomePath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).toString(); const QString MusicPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::MusicLocation)).toString(); const QString VideosPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)).toString(); const QStringList defaultPaths = { FMH::HomePath, FMH::DesktopPath, FMH::DocumentsPath, FMH::PicturesPath, FMH::MusicPath, FMH::VideosPath, FMH::DownloadsPath, FMH::RootPath }; #endif const QMap folderIcon { {PicturesPath, "folder-pictures"}, {DownloadsPath, "folder-download"}, {DocumentsPath, "folder-documents"}, {HomePath, "user-home"}, {MusicPath, "folder-music"}, {VideosPath, "folder-videos"}, {DesktopPath, "user-desktop"}, {AppsPath, "system-run"}, {RootPath, "folder-root"} }; /** * Checks if a local file exists. * The URL must represent a local file path, by using the scheme file:// **/ static inline bool fileExists(const QUrl &path) { if(!path.isLocalFile()) { qWarning() << "URL recived is not a local file" << path; return false; } return QFileInfo::exists(path.toLocalFile()); } static inline const QString fileDir(const QUrl& path)// the directory path of the file { QString res = path.toString(); if(path.isLocalFile()) { const QFileInfo file(path.toLocalFile()); if(file.isDir()) res = path.toString(); else res = QUrl::fromLocalFile(file.dir().absolutePath()).toString(); }else qWarning()<< "The path is not a local one. FM::fileDir"; return res; } static inline const QUrl parentDir(const QUrl &path) { if(!path.isLocalFile()) { qWarning() << "URL recived is not a local file, FM::parentDir" << path; return path; } QDir dir(path.toLocalFile()); dir.cdUp(); return QUrl::fromLocalFile(dir.absolutePath()); } /** * Return the configuration of a single directory represented * by a QVariantMap. * The passed path must be a local file URL. **/ static inline QVariantMap dirConf(const QUrl &path) { if(!path.isLocalFile()) { qWarning() << "URL recived is not a local file" << path; return QVariantMap(); } if(!FMH::fileExists(path)) return QVariantMap(); QString icon, iconsize, hidden, detailview, showthumbnail, showterminal; uint count = 0, sortby = FMH::MODEL_KEY::MODIFIED, viewType = 0; bool foldersFirst = false; #if defined Q_OS_ANDROID || defined Q_OS_WIN32 QSettings file(path.toLocalFile(), QSettings::Format::NativeFormat); file.beginGroup(QString("Desktop Entry")); icon = file.value("Icon").toString(); file.endGroup(); file.beginGroup(QString("Settings")); hidden = file.value("HiddenFilesShown").toString(); file.endGroup(); file.beginGroup(QString("MAUIFM")); iconsize = file.value("IconSize").toString(); detailview = file.value("DetailView").toString(); showthumbnail = file.value("ShowThumbnail").toString(); showterminal = file.value("ShowTerminal").toString(); count = file.value("Count").toInt(); sortby = file.value("SortBy").toInt(); foldersFirst = file.value("FoldersFirst").toBool(); viewType = file.value("ViewType").toInt(); file.endGroup(); #else KConfig file(path.toLocalFile()); icon = file.entryMap(QString("Desktop Entry"))["Icon"]; hidden = file.entryMap(QString("Settings"))["HiddenFilesShown"]; iconsize = file.entryMap(QString("MAUIFM"))["IconSize"]; detailview = file.entryMap(QString("MAUIFM"))["DetailView"]; showthumbnail = file.entryMap(QString("MAUIFM"))["ShowThumbnail"]; showterminal = file.entryMap(QString("MAUIFM"))["ShowTerminal"]; count = file.entryMap(QString("MAUIFM"))["Count"].toInt(); sortby = file.entryMap(QString("MAUIFM"))["SortBy"].toInt(); foldersFirst = file.entryMap(QString("MAUIFM"))["FoldersFirst"] == "true" ? true : false; viewType = file.entryMap(QString("MAUIFM"))["ViewType"].toInt(); #endif return QVariantMap({ {FMH::MODEL_NAME[FMH::MODEL_KEY::ICON], icon.isEmpty() ? "folder" : icon}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ICONSIZE], iconsize}, {FMH::MODEL_NAME[FMH::MODEL_KEY::COUNT], count}, {FMH::MODEL_NAME[FMH::MODEL_KEY::SHOWTERMINAL], showterminal.isEmpty() ? "false" : showterminal}, {FMH::MODEL_NAME[FMH::MODEL_KEY::SHOWTHUMBNAIL], showthumbnail.isEmpty() ? "false" : showthumbnail}, {FMH::MODEL_NAME[FMH::MODEL_KEY::DETAILVIEW], detailview.isEmpty() ? "false" : detailview}, {FMH::MODEL_NAME[FMH::MODEL_KEY::HIDDEN], hidden.isEmpty() ? false : (hidden == "true" ? true : false)}, {FMH::MODEL_NAME[FMH::MODEL_KEY::SORTBY], sortby}, {FMH::MODEL_NAME[FMH::MODEL_KEY::FOLDERSFIRST], foldersFirst}, {FMH::MODEL_NAME[FMH::MODEL_KEY::VIEWTYPE], viewType} }); } static inline void setDirConf(const QUrl &path, const QString &group, const QString &key, const QVariant &value) { if(!path.isLocalFile()) { qWarning() << "URL recived is not a local file" << path; return; } #if defined Q_OS_ANDROID || defined Q_OS_WIN32 QSettings file(path.toLocalFile(), QSettings::Format::IniFormat); file.beginGroup(group); file.setValue(key, value); file.endGroup(); file.sync(); #else KConfig file(path.toLocalFile(), KConfig::SimpleConfig); auto kgroup = file.group(group); kgroup.writeEntry(key, value); // file.reparseConfiguration(); file.sync(); #endif } /** * Returns the icon name for certain file. * The file path must be represented as a local file URL. * It also looks into the directory config file to get custom set icons **/ static inline QString getIconName(const QUrl &path) { if(!path.isLocalFile()) qWarning() << "URL recived is not a local file. FMH::getIconName" << path; if(path.isLocalFile() && QFileInfo(path.toLocalFile()).isDir()) { if(folderIcon.contains(path.toString())) return folderIcon[path.toString()]; else { const auto icon = FMH::dirConf(QString(path.toString()+"/%1").arg(".directory"))[FMH::MODEL_NAME[FMH::MODEL_KEY::ICON]].toString(); return icon.isEmpty() ? "folder" : icon; } }else { #if defined Q_OS_ANDROID || defined Q_OS_WIN32 QMimeDatabase mime; const auto type = mime.mimeTypeForFile(path.toString()); return type.iconName(); #else KFileItem mime(path); return mime.iconName(); #endif } } static inline QString getMime(const QUrl &path) { if(!path.isLocalFile()) { qWarning() << "URL recived is not a local file, FMH::getMime" << path; return QString(); } const QMimeDatabase mimedb; return mimedb.mimeTypeForFile(path.toLocalFile()).name(); } static inline bool mimeInherits(const QString baseType, const QString &type) { const QMimeDatabase _m; return _m.mimeTypeForName(baseType).inherits(type); } static inline FMH::MODEL getFileInfoModel(const QUrl &path) { FMH::MODEL res; #if defined Q_OS_ANDROID || defined Q_OS_WIN32 const QFileInfo file(path.toLocalFile()); if(!file.exists()) return FMH::MODEL(); const auto mime = FMH::getMime(path); res = FMH::MODEL { {FMH::MODEL_KEY::GROUP, file.group()}, {FMH::MODEL_KEY::OWNER, file.owner()}, {FMH::MODEL_KEY::SUFFIX, file.completeSuffix()}, {FMH::MODEL_KEY::LABEL, /*file.isDir() ? file.baseName() :*/ path == FMH::HomePath ? QStringLiteral("Home") : file.fileName()}, {FMH::MODEL_KEY::NAME, file.baseName()}, {FMH::MODEL_KEY::DATE, file.birthTime().toString(Qt::TextDate)}, {FMH::MODEL_KEY::MODIFIED, file.lastModified().toString(Qt::TextDate)}, {FMH::MODEL_KEY::LAST_READ, file.lastRead().toString(Qt::TextDate)}, {FMH::MODEL_KEY::MIME, mime }, {FMH::MODEL_KEY::SYMLINK, file.symLinkTarget() }, {FMH::MODEL_KEY::SYMLINK, file.symLinkTarget() }, {FMH::MODEL_KEY::IS_SYMLINK, QVariant(file.isSymLink()).toString()}, {FMH::MODEL_KEY::IS_FILE, QVariant(file.isFile()).toString()}, {FMH::MODEL_KEY::HIDDEN, QVariant(file.isHidden()).toString()}, {FMH::MODEL_KEY::IS_DIR, QVariant(file.isDir()).toString()}, {FMH::MODEL_KEY::WRITABLE, QVariant(file.isWritable()).toString()}, {FMH::MODEL_KEY::READABLE, QVariant(file.isReadable()).toString()}, {FMH::MODEL_KEY::EXECUTABLE, QVariant(file.suffix().endsWith(".desktop")).toString()}, {FMH::MODEL_KEY::ICON, FMH::getIconName(path)}, {FMH::MODEL_KEY::SIZE, QString::number(file.size()) /*locale.formattedDataSize(file.size())*/}, {FMH::MODEL_KEY::PATH, path.toString()}, {FMH::MODEL_KEY::THUMBNAIL, path.toString()}, {FMH::MODEL_KEY::COUNT, file.isDir() ? QString::number(QDir(path.toLocalFile()).count() - 2) : "0"} }; #else KFileItem kfile(path, KFileItem::MimeTypeDetermination::NormalMimeTypeDetermination); res = FMH::MODEL { {FMH::MODEL_KEY::LABEL, kfile.name()}, {FMH::MODEL_KEY::NAME, kfile.name().remove(kfile.name().lastIndexOf("."), kfile.name().size())}, {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::COUNT, kfile.isLocalFile() && kfile.isDir() ? QString::number(QDir(kfile.localPath()).count() - 2) : "0"} }; #endif return res; } static inline QVariantMap getFileInfo(const QUrl &path) { return FMH::toMap(FMH::getFileInfoModel(path)); } static inline FMH::MODEL getDirInfoModel(const QUrl &path, const QString &type = QString()) { auto res = getFileInfoModel(path); res[FMH::MODEL_KEY::TYPE] = type; return res; } static inline QVariantMap getDirInfo(const QUrl &path, const QString &type = QString()) { return FMH::toMap(FMH::getDirInfoModel(path)); } } #endif // FMH_H diff --git a/src/utils/handy.cpp b/src/utils/handy.cpp index 2a3e117..0337c7d 100644 --- a/src/utils/handy.cpp +++ b/src/utils/handy.cpp @@ -1,189 +1,221 @@ /* * 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 "handy.h" #include "utils.h" #include #include #include #include #include #include #include #include "fmh.h" +#if defined Q_OS_LINUX && !defined Q_OS_ANDROID +#include +#include +#endif + +static const QUrl CONF_FILE = FMH::ConfigPath + "/kdeglobals"; +static const auto confCheck = [](QString key, QVariant defaultValue) -> QVariant +{ + auto kconf = KSharedConfig::openConfig("kdeglobals"); + const auto group = kconf->group("KDE"); + if( group.hasKey(key)) + return group.readEntry(key, defaultValue); + + return defaultValue; +}; + + Handy::Handy(QObject *parent) : QObject(parent), m_isTouch(Handy::isTouch()) -{} +{ + #if defined Q_OS_LINUX && !defined Q_OS_ANDROID + + auto configWatcher = new QFileSystemWatcher({CONF_FILE.toLocalFile()}, this); + + m_singleClick = confCheck("SingleClick", m_singleClick).toBool(); + emit singleClickChanged(); + + connect(configWatcher, &QFileSystemWatcher::fileChanged, [&](QString) + { + m_singleClick = confCheck("SingleClick", m_singleClick).toBool(); + emit singleClickChanged(); + }); + + #endif +} #ifdef Q_OS_ANDROID static inline struct { QList urls; QString text; bool hasUrls(){ return !urls.isEmpty(); } bool hasText(){ return !text.isEmpty(); } } _clipboard; #endif QVariantMap Handy::appInfo() { auto res = QVariantMap({{FMH::MODEL_NAME[FMH::MODEL_KEY::NAME], qApp->applicationName()}, {FMH::MODEL_NAME[FMH::MODEL_KEY::VERSION], qApp->applicationVersion()}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ORG], qApp->organizationName()}, {FMH::MODEL_NAME[FMH::MODEL_KEY::DOMAIN_M], qApp->organizationDomain()}, {"mauikit_version", MAUIKIT_VERSION_STR}, {"qt_version", QT_VERSION_STR} }); #ifdef Q_OS_ANDROID res.insert(FMH::MODEL_NAME[FMH::MODEL_KEY::ICON], QGuiApplication::windowIcon().name()); #else res.insert(FMH::MODEL_NAME[FMH::MODEL_KEY::ICON], QApplication::windowIcon().name()); #endif return res; } QVariantMap Handy::userInfo() { QString name = qgetenv("USER"); if (name.isEmpty()) name = qgetenv("USERNAME"); return QVariantMap({{FMH::MODEL_NAME[FMH::MODEL_KEY::NAME], name}}); } QString Handy::getClipboardText() { #ifdef Q_OS_ANDROID auto clipbopard = QGuiApplication::clipboard(); #else auto clipbopard = QApplication::clipboard(); #endif auto mime = clipbopard->mimeData(); if(mime->hasText()) return clipbopard->text(); return QString(); } QVariantMap Handy::getClipboard() { QVariantMap res; #ifdef Q_OS_ANDROID if(_clipboard.hasUrls()) res.insert("urls", QUrl::toStringList(_clipboard.urls)); if(_clipboard.hasText()) res.insert("text", _clipboard.text); #else auto clipboard = QApplication::clipboard(); auto mime = clipboard->mimeData(); if(mime->hasUrls()) res.insert("urls", QUrl::toStringList(mime->urls())); if(mime->hasText()) res.insert("text", mime->text()); #endif return res; } bool Handy::copyToClipboard(const QVariantMap &value) { #ifdef Q_OS_ANDROID if(value.contains("urls")) _clipboard.urls = QUrl::fromStringList(value["urls"].toStringList()); if(value.contains("text")) _clipboard.text = value["text"].toString(); return true; #else auto clipboard = QApplication::clipboard(); QMimeData* mimeData = new QMimeData(); if(value.contains("urls")) mimeData->setUrls(QUrl::fromStringList(value["urls"].toStringList())); if(value.contains("text")) mimeData->setText(value["text"].toString()); clipboard->setMimeData(mimeData); return true; #endif return false; } bool Handy::copyTextToClipboard(const QString &text) { #ifdef Q_OS_ANDROID Handy::copyToClipboard({{"text", text}}); #else QApplication::clipboard()->setText(text); #endif return true; } int Handy::version() { return QOperatingSystemVersion::current().majorVersion(); } bool Handy::isAndroid() { return FMH::isAndroid(); } bool Handy::isLinux() { return FMH::isLinux(); } bool Handy::isTouch() { qDebug()<< "CHECKIGN IS IT IS TROYCH"; for(const auto &device : QTouchDevice::devices()) { if(device->type() == QTouchDevice::TouchScreen) return true; qDebug()<< "DEVICE CAPABILITIES" << device->capabilities() << device->name(); } return false; } bool Handy::isWindows() { return FMH::isWindows(); } bool Handy::isMac() { return FMH::isMac(); } diff --git a/src/utils/handy.h b/src/utils/handy.h index 6cd36f3..15729ac 100644 --- a/src/utils/handy.h +++ b/src/utils/handy.h @@ -1,105 +1,111 @@ /* * 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. */ #ifndef HANDY_H #define HANDY_H #include #ifndef STATIC_MAUIKIT #include "mauikit_export.h" #endif #include /*! * \brief The Handy class contains useful static methods to be used as an attached property to the Maui application */ #ifdef STATIC_MAUIKIT class Handy : public QObject #else class MAUIKIT_EXPORT Handy : public QObject #endif { Q_OBJECT Q_PROPERTY(bool isTouch MEMBER m_isTouch CONSTANT FINAL) Q_PROPERTY(bool isAndroid READ isAndroid CONSTANT FINAL) Q_PROPERTY(bool isLinux READ isLinux CONSTANT FINAL) Q_PROPERTY(bool isWindows READ isWindows CONSTANT FINAL) Q_PROPERTY(bool isMac READ isMac CONSTANT FINAL) + + Q_PROPERTY(bool singleClick MEMBER m_singleClick NOTIFY singleClickChanged CONSTANT) public: Handy(QObject *parent = nullptr); private: bool m_isTouch = false; - + bool m_singleClick = true; + public slots: /*! * \brief Returns the major version of the current OS * * This function is static. * \return Major OS version */ static int version(); /*! * \brief Returns a QVariantMap containing basic information about the current app * * The pairs keys for the information returned are: * "name", "version", "org", "domain", "mauikit_version" and "qt_version" * \return QVariantMap with app info */ static QVariantMap appInfo(); /*! * \brief Returns a QVariantMap containing basic information about the current user * * The pairs keys for the information returned are: * "name" * \return QVariantMap with user info */ static QVariantMap userInfo(); /*! * \brief Returns the text contained in the clipboard * \return QString containing clipboard text */ static QString getClipboardText(); static QVariantMap getClipboard(); /*! * \brief Copies text to the clipboard * \param text text to be copied to the clipboard * \return */ static bool copyTextToClipboard(const QString &text); static bool copyToClipboard(const QVariantMap &value); //TODO move to Device.h the defs and implementation of device specifics static bool isTouch(); static bool isAndroid(); static bool isWindows(); static bool isMac(); static bool isLinux(); + +signals: + void singleClickChanged(); }; #endif // HANDY_H