diff --git a/quickshell/Modals/FileBrowser/FileBrowserContent.qml b/quickshell/Modals/FileBrowser/FileBrowserContent.qml new file mode 100644 index 00000000..e00768f2 --- /dev/null +++ b/quickshell/Modals/FileBrowser/FileBrowserContent.qml @@ -0,0 +1,891 @@ +import Qt.labs.folderlistmodel +import QtCore +import QtQuick +import QtQuick.Controls +import qs.Common +import qs.Widgets + +FocusScope { + id: root + + property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation) + property string docsDir: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) + property string musicDir: StandardPaths.writableLocation(StandardPaths.MusicLocation) + property string videosDir: StandardPaths.writableLocation(StandardPaths.MoviesLocation) + property string picsDir: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + property string downloadDir: StandardPaths.writableLocation(StandardPaths.DownloadLocation) + property string desktopDir: StandardPaths.writableLocation(StandardPaths.DesktopLocation) + property string currentPath: "" + property var fileExtensions: ["*.*"] + property alias filterExtensions: root.fileExtensions + property string browserTitle: "Select File" + property string browserIcon: "folder_open" + property string browserType: "generic" + property bool showHiddenFiles: false + property int selectedIndex: -1 + property bool keyboardNavigationActive: false + property bool backButtonFocused: false + property bool saveMode: false + property string defaultFileName: "" + property int keyboardSelectionIndex: -1 + property bool keyboardSelectionRequested: false + property bool showKeyboardHints: false + property bool showFileInfo: false + property string selectedFilePath: "" + property string selectedFileName: "" + property bool selectedFileIsDir: false + property bool showOverwriteConfirmation: false + property string pendingFilePath: "" + property bool showSidebar: true + property string viewMode: "grid" + property string sortBy: "name" + property bool sortAscending: true + property int iconSizeIndex: 1 + property var iconSizes: [80, 120, 160, 200] + property bool pathEditMode: false + property bool pathInputHasFocus: false + property int actualGridColumns: 5 + property bool _initialized: false + + signal fileSelected(string path) + signal closeRequested + + function initialize() { + loadSettings(); + currentPath = getLastPath(); + _initialized = true; + } + + function reset() { + currentPath = getLastPath(); + selectedIndex = -1; + keyboardNavigationActive = false; + backButtonFocused = false; + } + + function loadSettings() { + const type = browserType || "default"; + const settings = CacheData.fileBrowserSettings[type]; + const isImageBrowser = ["wallpaper", "profile"].includes(browserType); + + if (settings) { + viewMode = settings.viewMode || (isImageBrowser ? "grid" : "list"); + sortBy = settings.sortBy || "name"; + sortAscending = settings.sortAscending !== undefined ? settings.sortAscending : true; + iconSizeIndex = settings.iconSizeIndex !== undefined ? settings.iconSizeIndex : 1; + showSidebar = settings.showSidebar !== undefined ? settings.showSidebar : true; + } else { + viewMode = isImageBrowser ? "grid" : "list"; + } + } + + function saveSettings() { + if (!_initialized) + return; + const type = browserType || "default"; + let settings = CacheData.fileBrowserSettings; + if (!settings[type]) { + settings[type] = {}; + } + settings[type].viewMode = viewMode; + settings[type].sortBy = sortBy; + settings[type].sortAscending = sortAscending; + settings[type].iconSizeIndex = iconSizeIndex; + settings[type].showSidebar = showSidebar; + settings[type].lastPath = currentPath; + CacheData.fileBrowserSettings = settings; + + if (browserType === "wallpaper") { + CacheData.wallpaperLastPath = currentPath; + } else if (browserType === "profile") { + CacheData.profileLastPath = currentPath; + } + + CacheData.saveCache(); + } + + onViewModeChanged: saveSettings() + onSortByChanged: saveSettings() + onSortAscendingChanged: saveSettings() + onIconSizeIndexChanged: saveSettings() + onShowSidebarChanged: saveSettings() + + function isImageFile(fileName) { + if (!fileName) + return false; + const ext = fileName.toLowerCase().split('.').pop(); + return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext); + } + + function getLastPath() { + const type = browserType || "default"; + const settings = CacheData.fileBrowserSettings[type]; + const lastPath = settings?.lastPath || ""; + return (lastPath && lastPath !== "") ? lastPath : homeDir; + } + + function saveLastPath(path) { + const type = browserType || "default"; + let settings = CacheData.fileBrowserSettings; + if (!settings[type]) { + settings[type] = {}; + } + settings[type].lastPath = path; + CacheData.fileBrowserSettings = settings; + CacheData.saveCache(); + + if (browserType === "wallpaper") { + CacheData.wallpaperLastPath = path; + } else if (browserType === "profile") { + CacheData.profileLastPath = path; + } + } + + function setSelectedFileData(path, name, isDir) { + selectedFilePath = path; + selectedFileName = name; + selectedFileIsDir = isDir; + } + + function navigateUp() { + const path = currentPath; + if (path === homeDir) + return; + const lastSlash = path.lastIndexOf('/'); + if (lastSlash <= 0) + return; + + const newPath = path.substring(0, lastSlash); + if (newPath.length < homeDir.length) { + currentPath = homeDir; + saveLastPath(homeDir); + } else { + currentPath = newPath; + saveLastPath(newPath); + } + } + + function navigateTo(path) { + currentPath = path; + saveLastPath(path); + selectedIndex = -1; + backButtonFocused = false; + } + + function keyboardFileSelection(index) { + if (index < 0) + return; + keyboardSelectionTimer.targetIndex = index; + keyboardSelectionTimer.start(); + } + + function executeKeyboardSelection(index) { + keyboardSelectionIndex = index; + keyboardSelectionRequested = true; + } + + function handleSaveFile(filePath) { + var normalizedPath = filePath; + if (!normalizedPath.startsWith("file://")) { + normalizedPath = "file://" + filePath; + } + + var exists = false; + var fileName = filePath.split('/').pop(); + + for (var i = 0; i < folderModel.count; i++) { + if (folderModel.get(i, "fileName") === fileName && !folderModel.get(i, "fileIsDir")) { + exists = true; + break; + } + } + + if (exists) { + pendingFilePath = normalizedPath; + showOverwriteConfirmation = true; + } else { + fileSelected(normalizedPath); + closeRequested(); + } + } + + onCurrentPathChanged: { + selectedFilePath = ""; + selectedFileName = ""; + selectedFileIsDir = false; + saveSettings(); + } + + onSelectedIndexChanged: { + if (selectedIndex >= 0 && folderModel && selectedIndex < folderModel.count) { + selectedFilePath = ""; + selectedFileName = ""; + selectedFileIsDir = false; + } + } + + property var steamPaths: [StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.steam/steam/steamapps/workshop/content/431960", StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.local/share/Steam/steamapps/workshop/content/431960", StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/workshop/content/431960", StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/snap/steam/common/.local/share/Steam/steamapps/workshop/content/431960"] + + property var quickAccessLocations: [ + { + "name": "Home", + "path": homeDir, + "icon": "home" + }, + { + "name": "Documents", + "path": docsDir, + "icon": "description" + }, + { + "name": "Downloads", + "path": downloadDir, + "icon": "download" + }, + { + "name": "Pictures", + "path": picsDir, + "icon": "image" + }, + { + "name": "Music", + "path": musicDir, + "icon": "music_note" + }, + { + "name": "Videos", + "path": videosDir, + "icon": "movie" + }, + { + "name": "Desktop", + "path": desktopDir, + "icon": "computer" + } + ] + + FolderListModel { + id: folderModel + + showDirsFirst: true + showDotAndDotDot: false + showHidden: root.showHiddenFiles + nameFilters: fileExtensions + showFiles: true + showDirs: true + folder: currentPath ? "file://" + currentPath : "file://" + homeDir + sortField: { + switch (sortBy) { + case "name": + return FolderListModel.Name; + case "size": + return FolderListModel.Size; + case "modified": + return FolderListModel.Time; + case "type": + return FolderListModel.Type; + default: + return FolderListModel.Name; + } + } + sortReversed: !sortAscending + } + + QtObject { + id: keyboardController + + property int totalItems: folderModel.count + property int gridColumns: viewMode === "list" ? 1 : Math.max(1, actualGridColumns) + + function handleKey(event) { + if (event.key === Qt.Key_Escape) { + closeRequested(); + event.accepted = true; + return; + } + if (event.key === Qt.Key_F10) { + showKeyboardHints = !showKeyboardHints; + event.accepted = true; + return; + } + if (event.key === Qt.Key_F1 || event.key === Qt.Key_I) { + showFileInfo = !showFileInfo; + event.accepted = true; + return; + } + if ((event.modifiers & Qt.AltModifier && event.key === Qt.Key_Left) || event.key === Qt.Key_Backspace) { + if (currentPath !== homeDir) { + navigateUp(); + event.accepted = true; + } + return; + } + if (!keyboardNavigationActive) { + const isInitKey = event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right || (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) || (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) || (event.key === Qt.Key_L && event.modifiers & Qt.ControlModifier); + + if (isInitKey) { + keyboardNavigationActive = true; + if (currentPath !== homeDir) { + backButtonFocused = true; + selectedIndex = -1; + } else { + backButtonFocused = false; + selectedIndex = 0; + } + event.accepted = true; + } + return; + } + switch (event.key) { + case Qt.Key_Tab: + if (backButtonFocused) { + backButtonFocused = false; + selectedIndex = 0; + } else if (selectedIndex < totalItems - 1) { + selectedIndex++; + } else if (currentPath !== homeDir) { + backButtonFocused = true; + selectedIndex = -1; + } else { + selectedIndex = 0; + } + event.accepted = true; + break; + case Qt.Key_Backtab: + if (backButtonFocused) { + backButtonFocused = false; + selectedIndex = totalItems - 1; + } else if (selectedIndex > 0) { + selectedIndex--; + } else if (currentPath !== homeDir) { + backButtonFocused = true; + selectedIndex = -1; + } else { + selectedIndex = totalItems - 1; + } + event.accepted = true; + break; + case Qt.Key_N: + if (event.modifiers & Qt.ControlModifier) { + if (backButtonFocused) { + backButtonFocused = false; + selectedIndex = 0; + } else if (selectedIndex < totalItems - 1) { + selectedIndex++; + } + event.accepted = true; + } + break; + case Qt.Key_P: + if (event.modifiers & Qt.ControlModifier) { + if (selectedIndex > 0) { + selectedIndex--; + } else if (currentPath !== homeDir) { + backButtonFocused = true; + selectedIndex = -1; + } + event.accepted = true; + } + break; + case Qt.Key_J: + if (event.modifiers & Qt.ControlModifier) { + if (selectedIndex < totalItems - 1) { + selectedIndex++; + } + event.accepted = true; + } + break; + case Qt.Key_K: + if (event.modifiers & Qt.ControlModifier) { + if (selectedIndex > 0) { + selectedIndex--; + } else if (currentPath !== homeDir) { + backButtonFocused = true; + selectedIndex = -1; + } + event.accepted = true; + } + break; + case Qt.Key_H: + if (event.modifiers & Qt.ControlModifier) { + if (!backButtonFocused && selectedIndex > 0) { + selectedIndex--; + } else if (currentPath !== homeDir) { + backButtonFocused = true; + selectedIndex = -1; + } + event.accepted = true; + } + break; + case Qt.Key_L: + if (event.modifiers & Qt.ControlModifier) { + if (backButtonFocused) { + backButtonFocused = false; + selectedIndex = 0; + } else if (selectedIndex < totalItems - 1) { + selectedIndex++; + } + event.accepted = true; + } + break; + case Qt.Key_Left: + if (pathInputHasFocus) + return; + if (backButtonFocused) + return; + if (selectedIndex > 0) { + selectedIndex--; + } else if (currentPath !== homeDir) { + backButtonFocused = true; + selectedIndex = -1; + } + event.accepted = true; + break; + case Qt.Key_Right: + if (pathInputHasFocus) + return; + if (backButtonFocused) { + backButtonFocused = false; + selectedIndex = 0; + } else if (selectedIndex < totalItems - 1) { + selectedIndex++; + } + event.accepted = true; + break; + case Qt.Key_Up: + if (backButtonFocused) { + backButtonFocused = false; + if (gridColumns === 1) { + selectedIndex = 0; + } else { + var col = selectedIndex % gridColumns; + selectedIndex = Math.min(col, totalItems - 1); + } + } else if (selectedIndex >= gridColumns) { + selectedIndex -= gridColumns; + } else if (selectedIndex > 0 && gridColumns === 1) { + selectedIndex--; + } else if (currentPath !== homeDir) { + backButtonFocused = true; + selectedIndex = -1; + } + event.accepted = true; + break; + case Qt.Key_Down: + if (backButtonFocused) { + backButtonFocused = false; + selectedIndex = 0; + } else if (gridColumns === 1) { + if (selectedIndex < totalItems - 1) { + selectedIndex++; + } + } else { + var newIndex = selectedIndex + gridColumns; + if (newIndex < totalItems) { + selectedIndex = newIndex; + } else { + var lastRowStart = Math.floor((totalItems - 1) / gridColumns) * gridColumns; + var col = selectedIndex % gridColumns; + var targetIndex = lastRowStart + col; + if (targetIndex < totalItems && targetIndex > selectedIndex) { + selectedIndex = targetIndex; + } + } + } + event.accepted = true; + break; + case Qt.Key_Return: + case Qt.Key_Enter: + case Qt.Key_Space: + if (backButtonFocused) { + navigateUp(); + } else if (selectedIndex >= 0 && selectedIndex < totalItems) { + root.keyboardFileSelection(selectedIndex); + } + event.accepted = true; + break; + } + } + } + + Timer { + id: keyboardSelectionTimer + + property int targetIndex: -1 + + interval: 1 + onTriggered: { + executeKeyboardSelection(targetIndex); + } + } + + focus: true + + Keys.onPressed: event => { + keyboardController.handleKey(event); + } + + Column { + anchors.fill: parent + spacing: 0 + + Item { + width: parent.width + height: 48 + + Row { + spacing: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: Theme.spacingL + + DankIcon { + name: browserIcon + size: Theme.iconSizeLarge + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: browserTitle + font.pixelSize: Theme.fontSizeXLarge + color: Theme.surfaceText + font.weight: Font.Medium + anchors.verticalCenter: parent.verticalCenter + } + } + + Row { + anchors.right: parent.right + anchors.rightMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankActionButton { + circular: false + iconName: showHiddenFiles ? "visibility_off" : "visibility" + iconSize: Theme.iconSize - 4 + iconColor: showHiddenFiles ? Theme.primary : Theme.surfaceText + onClicked: showHiddenFiles = !showHiddenFiles + } + + DankActionButton { + circular: false + iconName: viewMode === "grid" ? "view_list" : "grid_view" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: viewMode = viewMode === "grid" ? "list" : "grid" + } + + DankActionButton { + circular: false + iconName: iconSizeIndex === 0 ? "photo_size_select_small" : iconSizeIndex === 1 ? "photo_size_select_large" : iconSizeIndex === 2 ? "photo_size_select_actual" : "zoom_in" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + visible: viewMode === "grid" + onClicked: iconSizeIndex = (iconSizeIndex + 1) % iconSizes.length + } + + DankActionButton { + circular: false + iconName: "info" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: root.showKeyboardHints = !root.showKeyboardHints + } + + DankActionButton { + circular: false + iconName: "close" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: root.closeRequested() + } + } + } + + StyledRect { + width: parent.width + height: 1 + color: Theme.outline + } + + Item { + width: parent.width + height: parent.height - 49 + + Row { + anchors.fill: parent + spacing: 0 + + Row { + width: showSidebar ? 201 : 0 + height: parent.height + spacing: 0 + visible: showSidebar + + FileBrowserSidebar { + height: parent.height + quickAccessLocations: root.quickAccessLocations + currentPath: root.currentPath + onLocationSelected: path => navigateTo(path) + } + + StyledRect { + width: 1 + height: parent.height + color: Theme.outline + } + } + + Column { + width: parent.width - (showSidebar ? 201 : 0) + height: parent.height + spacing: 0 + + FileBrowserNavigation { + width: parent.width + currentPath: root.currentPath + homeDir: root.homeDir + backButtonFocused: root.backButtonFocused + keyboardNavigationActive: root.keyboardNavigationActive + showSidebar: root.showSidebar + pathEditMode: root.pathEditMode + onNavigateUp: root.navigateUp() + onNavigateTo: path => root.navigateTo(path) + onPathInputFocusChanged: hasFocus => { + root.pathInputHasFocus = hasFocus; + if (hasFocus) { + root.pathEditMode = true; + } + } + } + + StyledRect { + width: parent.width + height: 1 + color: Theme.outline + } + + Item { + id: gridContainer + width: parent.width + height: parent.height - 41 + clip: true + + property real gridCellWidth: iconSizes[iconSizeIndex] + 24 + property real gridCellHeight: iconSizes[iconSizeIndex] + 56 + property real availableGridWidth: width - Theme.spacingM * 2 + property int gridColumns: Math.max(1, Math.floor(availableGridWidth / gridCellWidth)) + property real gridLeftMargin: Theme.spacingM + Math.max(0, (availableGridWidth - (gridColumns * gridCellWidth)) / 2) + + onGridColumnsChanged: { + root.actualGridColumns = gridColumns; + } + Component.onCompleted: { + root.actualGridColumns = gridColumns; + } + + DankGridView { + id: fileGrid + anchors.fill: parent + anchors.leftMargin: gridContainer.gridLeftMargin + anchors.rightMargin: Theme.spacingM + anchors.topMargin: Theme.spacingS + anchors.bottomMargin: Theme.spacingS + visible: viewMode === "grid" + cellWidth: gridContainer.gridCellWidth + cellHeight: gridContainer.gridCellHeight + cacheBuffer: 260 + model: folderModel + currentIndex: selectedIndex + onCurrentIndexChanged: { + if (keyboardNavigationActive && currentIndex >= 0) + positionViewAtIndex(currentIndex, GridView.Contain); + } + + ScrollBar.vertical: DankScrollbar { + id: gridScrollbar + } + + ScrollBar.horizontal: ScrollBar { + policy: ScrollBar.AlwaysOff + } + + delegate: FileBrowserGridDelegate { + iconSizes: root.iconSizes + iconSizeIndex: root.iconSizeIndex + selectedIndex: root.selectedIndex + keyboardNavigationActive: root.keyboardNavigationActive + onItemClicked: (index, path, name, isDir) => { + selectedIndex = index; + setSelectedFileData(path, name, isDir); + if (isDir) { + navigateTo(path); + } else { + fileSelected(path); + root.closeRequested(); + } + } + onItemSelected: (index, path, name, isDir) => { + setSelectedFileData(path, name, isDir); + } + + Connections { + function onKeyboardSelectionRequestedChanged() { + if (root.keyboardSelectionRequested && root.keyboardSelectionIndex === index) { + root.keyboardSelectionRequested = false; + selectedIndex = index; + setSelectedFileData(filePath, fileName, fileIsDir); + if (fileIsDir) { + navigateTo(filePath); + } else { + fileSelected(filePath); + root.closeRequested(); + } + } + } + + target: root + } + } + } + + DankListView { + id: fileList + anchors.fill: parent + anchors.leftMargin: Theme.spacingM + anchors.rightMargin: Theme.spacingM + anchors.topMargin: Theme.spacingS + anchors.bottomMargin: Theme.spacingS + visible: viewMode === "list" + spacing: 2 + model: folderModel + currentIndex: selectedIndex + onCurrentIndexChanged: { + if (keyboardNavigationActive && currentIndex >= 0) + positionViewAtIndex(currentIndex, ListView.Contain); + } + + ScrollBar.vertical: DankScrollbar { + id: listScrollbar + } + + delegate: FileBrowserListDelegate { + width: fileList.width + selectedIndex: root.selectedIndex + keyboardNavigationActive: root.keyboardNavigationActive + onItemClicked: (index, path, name, isDir) => { + selectedIndex = index; + setSelectedFileData(path, name, isDir); + if (isDir) { + navigateTo(path); + } else { + fileSelected(path); + root.closeRequested(); + } + } + onItemSelected: (index, path, name, isDir) => { + setSelectedFileData(path, name, isDir); + } + + Connections { + function onKeyboardSelectionRequestedChanged() { + if (root.keyboardSelectionRequested && root.keyboardSelectionIndex === index) { + root.keyboardSelectionRequested = false; + selectedIndex = index; + setSelectedFileData(filePath, fileName, fileIsDir); + if (fileIsDir) { + navigateTo(filePath); + } else { + fileSelected(filePath); + root.closeRequested(); + } + } + } + + target: root + } + } + } + } + } + } + + FileBrowserSaveRow { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: Theme.spacingL + saveMode: root.saveMode + defaultFileName: root.defaultFileName + currentPath: root.currentPath + onSaveRequested: filePath => handleSaveFile(filePath) + } + + KeyboardHints { + id: keyboardHints + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: Theme.spacingL + showHints: root.showKeyboardHints + } + + FileInfo { + id: fileInfo + + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Theme.spacingL + width: 300 + showFileInfo: root.showFileInfo + selectedIndex: root.selectedIndex + sourceFolderModel: folderModel + currentPath: root.currentPath + currentFileName: root.selectedFileName + currentFileIsDir: root.selectedFileIsDir + currentFileExtension: { + if (root.selectedFileIsDir || !root.selectedFileName) + return ""; + + var lastDot = root.selectedFileName.lastIndexOf('.'); + return lastDot > 0 ? root.selectedFileName.substring(lastDot + 1).toLowerCase() : ""; + } + } + + FileBrowserSortMenu { + id: sortMenu + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: 120 + anchors.rightMargin: Theme.spacingL + sortBy: root.sortBy + sortAscending: root.sortAscending + onSortBySelected: value => { + root.sortBy = value; + } + onSortOrderSelected: ascending => { + root.sortAscending = ascending; + } + } + } + + FileBrowserOverwriteDialog { + anchors.fill: parent + showDialog: showOverwriteConfirmation + pendingFilePath: root.pendingFilePath + onConfirmed: filePath => { + showOverwriteConfirmation = false; + fileSelected(filePath); + pendingFilePath = ""; + Qt.callLater(() => root.closeRequested()); + } + onCancelled: { + showOverwriteConfirmation = false; + pendingFilePath = ""; + } + } + } +} diff --git a/quickshell/Modals/FileBrowser/FileBrowserModal.qml b/quickshell/Modals/FileBrowser/FileBrowserModal.qml index 8cadb461..ed80eea3 100644 --- a/quickshell/Modals/FileBrowser/FileBrowserModal.qml +++ b/quickshell/Modals/FileBrowser/FileBrowserModal.qml @@ -1,54 +1,19 @@ -import Qt.labs.folderlistmodel -import QtCore import QtQuick -import QtQuick.Controls import Quickshell import qs.Common -import qs.Modals.FileBrowser -import qs.Widgets FloatingWindow { id: fileBrowserModal - property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation) - property string docsDir: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) - property string musicDir: StandardPaths.writableLocation(StandardPaths.MusicLocation) - property string videosDir: StandardPaths.writableLocation(StandardPaths.MoviesLocation) - property string picsDir: StandardPaths.writableLocation(StandardPaths.PicturesLocation) - property string downloadDir: StandardPaths.writableLocation(StandardPaths.DownloadLocation) - property string desktopDir: StandardPaths.writableLocation(StandardPaths.DesktopLocation) - property string currentPath: "" - property var fileExtensions: ["*.*"] - property alias filterExtensions: fileBrowserModal.fileExtensions property string browserTitle: "Select File" property string browserIcon: "folder_open" property string browserType: "generic" + property var fileExtensions: ["*.*"] + property alias filterExtensions: fileBrowserModal.fileExtensions property bool showHiddenFiles: false - property int selectedIndex: -1 - property bool keyboardNavigationActive: false - property bool backButtonFocused: false property bool saveMode: false property string defaultFileName: "" - property int keyboardSelectionIndex: -1 - property bool keyboardSelectionRequested: false - property bool showKeyboardHints: false - property bool showFileInfo: false - property string selectedFilePath: "" - property string selectedFileName: "" - property bool selectedFileIsDir: false - property bool showOverwriteConfirmation: false - property string pendingFilePath: "" property var parentModal: null - property bool showSidebar: true - property string viewMode: "grid" - property string sortBy: "name" - property bool sortAscending: true - property int iconSizeIndex: 1 - property var iconSizes: [80, 120, 160, 200] - property bool pathEditMode: false - property bool pathInputHasFocus: false - property int actualGridColumns: 5 - property bool _initialized: false property bool shouldHaveFocus: visible property bool allowFocusOverride: false property bool shouldBeVisible: visible @@ -65,152 +30,6 @@ FloatingWindow { visible = false; } - function loadSettings() { - const type = browserType || "default"; - const settings = CacheData.fileBrowserSettings[type]; - const isImageBrowser = ["wallpaper", "profile"].includes(browserType); - - if (settings) { - viewMode = settings.viewMode || (isImageBrowser ? "grid" : "list"); - sortBy = settings.sortBy || "name"; - sortAscending = settings.sortAscending !== undefined ? settings.sortAscending : true; - iconSizeIndex = settings.iconSizeIndex !== undefined ? settings.iconSizeIndex : 1; - showSidebar = settings.showSidebar !== undefined ? settings.showSidebar : true; - } else { - viewMode = isImageBrowser ? "grid" : "list"; - } - } - - function saveSettings() { - if (!_initialized) - return; - const type = browserType || "default"; - let settings = CacheData.fileBrowserSettings; - if (!settings[type]) { - settings[type] = {}; - } - settings[type].viewMode = viewMode; - settings[type].sortBy = sortBy; - settings[type].sortAscending = sortAscending; - settings[type].iconSizeIndex = iconSizeIndex; - settings[type].showSidebar = showSidebar; - settings[type].lastPath = currentPath; - CacheData.fileBrowserSettings = settings; - - if (browserType === "wallpaper") { - CacheData.wallpaperLastPath = currentPath; - } else if (browserType === "profile") { - CacheData.profileLastPath = currentPath; - } - - CacheData.saveCache(); - } - - onViewModeChanged: saveSettings() - onSortByChanged: saveSettings() - onSortAscendingChanged: saveSettings() - onIconSizeIndexChanged: saveSettings() - onShowSidebarChanged: saveSettings() - - function isImageFile(fileName) { - if (!fileName) { - return false; - } - const ext = fileName.toLowerCase().split('.').pop(); - return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext); - } - - function getLastPath() { - const type = browserType || "default"; - const settings = CacheData.fileBrowserSettings[type]; - const lastPath = settings?.lastPath || ""; - return (lastPath && lastPath !== "") ? lastPath : homeDir; - } - - function saveLastPath(path) { - const type = browserType || "default"; - let settings = CacheData.fileBrowserSettings; - if (!settings[type]) { - settings[type] = {}; - } - settings[type].lastPath = path; - CacheData.fileBrowserSettings = settings; - CacheData.saveCache(); - - if (browserType === "wallpaper") { - CacheData.wallpaperLastPath = path; - } else if (browserType === "profile") { - CacheData.profileLastPath = path; - } - } - - function setSelectedFileData(path, name, isDir) { - selectedFilePath = path; - selectedFileName = name; - selectedFileIsDir = isDir; - } - - function navigateUp() { - const path = currentPath; - if (path === homeDir) - return; - const lastSlash = path.lastIndexOf('/'); - if (lastSlash > 0) { - const newPath = path.substring(0, lastSlash); - if (newPath.length < homeDir.length) { - currentPath = homeDir; - saveLastPath(homeDir); - } else { - currentPath = newPath; - saveLastPath(newPath); - } - } - } - - function navigateTo(path) { - currentPath = path; - saveLastPath(path); - selectedIndex = -1; - backButtonFocused = false; - } - - function keyboardFileSelection(index) { - if (index >= 0) { - keyboardSelectionTimer.targetIndex = index; - keyboardSelectionTimer.start(); - } - } - - function executeKeyboardSelection(index) { - keyboardSelectionIndex = index; - keyboardSelectionRequested = true; - } - - function handleSaveFile(filePath) { - var normalizedPath = filePath; - if (!normalizedPath.startsWith("file://")) { - normalizedPath = "file://" + filePath; - } - - var exists = false; - var fileName = filePath.split('/').pop(); - - for (var i = 0; i < folderModel.count; i++) { - if (folderModel.get(i, "fileName") === fileName && !folderModel.get(i, "fileIsDir")) { - exists = true; - break; - } - } - - if (exists) { - pendingFilePath = normalizedPath; - showOverwriteConfirmation = true; - } else { - fileSelected(normalizedPath); - fileBrowserModal.close(); - } - } - objectName: "fileBrowserModal" title: "Files - " + browserTitle minimumSize: Qt.size(500, 400) @@ -219,718 +38,39 @@ FloatingWindow { color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) visible: false - Component.onCompleted: { - loadSettings(); - currentPath = getLastPath(); - _initialized = true; - } - - property var steamPaths: [StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.steam/steam/steamapps/workshop/content/431960", StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.local/share/Steam/steamapps/workshop/content/431960", StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/workshop/content/431960", StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/snap/steam/common/.local/share/Steam/steamapps/workshop/content/431960"] - property int currentPathIndex: 0 - onVisibleChanged: { if (visible) { if (parentModal) { parentModal.shouldHaveFocus = false; parentModal.allowFocusOverride = true; } - currentPath = getLastPath(); - selectedIndex = -1; - keyboardNavigationActive = false; - backButtonFocused = false; - Qt.callLater(() => { - if (contentFocusScope) { - contentFocusScope.forceActiveFocus(); - } - }); + content.reset(); + Qt.callLater(() => content.forceActiveFocus()); } else { if (parentModal) { parentModal.allowFocusOverride = false; - parentModal.shouldHaveFocus = Qt.binding(() => { - return parentModal.shouldBeVisible; - }); + parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible); } dialogClosed(); } } - onCurrentPathChanged: { - selectedFilePath = ""; - selectedFileName = ""; - selectedFileIsDir = false; - saveSettings(); - } - onSelectedIndexChanged: { - if (selectedIndex >= 0 && folderModel && selectedIndex < folderModel.count) { - selectedFilePath = ""; - selectedFileName = ""; - selectedFileIsDir = false; - } - } - - FolderListModel { - id: folderModel - - showDirsFirst: true - showDotAndDotDot: false - showHidden: fileBrowserModal.showHiddenFiles - nameFilters: fileExtensions - showFiles: true - showDirs: true - folder: currentPath ? "file://" + currentPath : "file://" + homeDir - sortField: { - switch (sortBy) { - case "name": - return FolderListModel.Name; - case "size": - return FolderListModel.Size; - case "modified": - return FolderListModel.Time; - case "type": - return FolderListModel.Type; - default: - return FolderListModel.Name; - } - } - sortReversed: !sortAscending - } - - property var quickAccessLocations: [ - { - "name": "Home", - "path": homeDir, - "icon": "home" - }, - { - "name": "Documents", - "path": docsDir, - "icon": "description" - }, - { - "name": "Downloads", - "path": downloadDir, - "icon": "download" - }, - { - "name": "Pictures", - "path": picsDir, - "icon": "image" - }, - { - "name": "Music", - "path": musicDir, - "icon": "music_note" - }, - { - "name": "Videos", - "path": videosDir, - "icon": "movie" - }, - { - "name": "Desktop", - "path": desktopDir, - "icon": "computer" - } - ] - - QtObject { - id: keyboardController - - property int totalItems: folderModel.count - property int gridColumns: viewMode === "list" ? 1 : Math.max(1, actualGridColumns) - - function handleKey(event) { - if (event.key === Qt.Key_Escape) { - close(); - event.accepted = true; - return; - } - if (event.key === Qt.Key_F10) { - showKeyboardHints = !showKeyboardHints; - event.accepted = true; - return; - } - if (event.key === Qt.Key_F1 || event.key === Qt.Key_I) { - showFileInfo = !showFileInfo; - event.accepted = true; - return; - } - if ((event.modifiers & Qt.AltModifier && event.key === Qt.Key_Left) || event.key === Qt.Key_Backspace) { - if (currentPath !== homeDir) { - navigateUp(); - event.accepted = true; - } - return; - } - if (!keyboardNavigationActive) { - const isInitKey = event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right || (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) || (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) || (event.key === Qt.Key_L && event.modifiers & Qt.ControlModifier); - - if (isInitKey) { - keyboardNavigationActive = true; - if (currentPath !== homeDir) { - backButtonFocused = true; - selectedIndex = -1; - } else { - backButtonFocused = false; - selectedIndex = 0; - } - event.accepted = true; - } - return; - } - switch (event.key) { - case Qt.Key_Tab: - if (backButtonFocused) { - backButtonFocused = false; - selectedIndex = 0; - } else if (selectedIndex < totalItems - 1) { - selectedIndex++; - } else if (currentPath !== homeDir) { - backButtonFocused = true; - selectedIndex = -1; - } else { - selectedIndex = 0; - } - event.accepted = true; - break; - case Qt.Key_Backtab: - if (backButtonFocused) { - backButtonFocused = false; - selectedIndex = totalItems - 1; - } else if (selectedIndex > 0) { - selectedIndex--; - } else if (currentPath !== homeDir) { - backButtonFocused = true; - selectedIndex = -1; - } else { - selectedIndex = totalItems - 1; - } - event.accepted = true; - break; - case Qt.Key_N: - if (event.modifiers & Qt.ControlModifier) { - if (backButtonFocused) { - backButtonFocused = false; - selectedIndex = 0; - } else if (selectedIndex < totalItems - 1) { - selectedIndex++; - } - event.accepted = true; - } - break; - case Qt.Key_P: - if (event.modifiers & Qt.ControlModifier) { - if (selectedIndex > 0) { - selectedIndex--; - } else if (currentPath !== homeDir) { - backButtonFocused = true; - selectedIndex = -1; - } - event.accepted = true; - } - break; - case Qt.Key_J: - if (event.modifiers & Qt.ControlModifier) { - if (selectedIndex < totalItems - 1) { - selectedIndex++; - } - event.accepted = true; - } - break; - case Qt.Key_K: - if (event.modifiers & Qt.ControlModifier) { - if (selectedIndex > 0) { - selectedIndex--; - } else if (currentPath !== homeDir) { - backButtonFocused = true; - selectedIndex = -1; - } - event.accepted = true; - } - break; - case Qt.Key_H: - if (event.modifiers & Qt.ControlModifier) { - if (!backButtonFocused && selectedIndex > 0) { - selectedIndex--; - } else if (currentPath !== homeDir) { - backButtonFocused = true; - selectedIndex = -1; - } - event.accepted = true; - } - break; - case Qt.Key_L: - if (event.modifiers & Qt.ControlModifier) { - if (backButtonFocused) { - backButtonFocused = false; - selectedIndex = 0; - } else if (selectedIndex < totalItems - 1) { - selectedIndex++; - } - event.accepted = true; - } - break; - case Qt.Key_Left: - if (pathInputHasFocus) - return; - if (backButtonFocused) - return; - if (selectedIndex > 0) { - selectedIndex--; - } else if (currentPath !== homeDir) { - backButtonFocused = true; - selectedIndex = -1; - } - event.accepted = true; - break; - case Qt.Key_Right: - if (pathInputHasFocus) - return; - if (backButtonFocused) { - backButtonFocused = false; - selectedIndex = 0; - } else if (selectedIndex < totalItems - 1) { - selectedIndex++; - } - event.accepted = true; - break; - case Qt.Key_Up: - if (backButtonFocused) { - backButtonFocused = false; - if (gridColumns === 1) { - selectedIndex = 0; - } else { - var col = selectedIndex % gridColumns; - selectedIndex = Math.min(col, totalItems - 1); - } - } else if (selectedIndex >= gridColumns) { - selectedIndex -= gridColumns; - } else if (selectedIndex > 0 && gridColumns === 1) { - selectedIndex--; - } else if (currentPath !== homeDir) { - backButtonFocused = true; - selectedIndex = -1; - } - event.accepted = true; - break; - case Qt.Key_Down: - if (backButtonFocused) { - backButtonFocused = false; - selectedIndex = 0; - } else if (gridColumns === 1) { - if (selectedIndex < totalItems - 1) { - selectedIndex++; - } - } else { - var newIndex = selectedIndex + gridColumns; - if (newIndex < totalItems) { - selectedIndex = newIndex; - } else { - var lastRowStart = Math.floor((totalItems - 1) / gridColumns) * gridColumns; - var col = selectedIndex % gridColumns; - var targetIndex = lastRowStart + col; - if (targetIndex < totalItems && targetIndex > selectedIndex) { - selectedIndex = targetIndex; - } - } - } - event.accepted = true; - break; - case Qt.Key_Return: - case Qt.Key_Enter: - case Qt.Key_Space: - if (backButtonFocused) - navigateUp(); - else if (selectedIndex >= 0 && selectedIndex < totalItems) - fileBrowserModal.keyboardFileSelection(selectedIndex); - event.accepted = true; - break; - } - } - } - - Timer { - id: keyboardSelectionTimer - - property int targetIndex: -1 - - interval: 1 - onTriggered: { - executeKeyboardSelection(targetIndex); - } - } - - FocusScope { - id: contentFocusScope + FileBrowserContent { + id: content anchors.fill: parent focus: true - Keys.onPressed: event => { - keyboardController.handleKey(event); - } + browserTitle: fileBrowserModal.browserTitle + browserIcon: fileBrowserModal.browserIcon + browserType: fileBrowserModal.browserType + fileExtensions: fileBrowserModal.fileExtensions + showHiddenFiles: fileBrowserModal.showHiddenFiles + saveMode: fileBrowserModal.saveMode + defaultFileName: fileBrowserModal.defaultFileName - Column { - anchors.fill: parent - spacing: 0 + Component.onCompleted: initialize() - Item { - width: parent.width - height: 48 - - Row { - spacing: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: Theme.spacingL - - DankIcon { - name: browserIcon - size: Theme.iconSizeLarge - color: Theme.primary - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: browserTitle - font.pixelSize: Theme.fontSizeXLarge - color: Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - Row { - anchors.right: parent.right - anchors.rightMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingS - - DankActionButton { - circular: false - iconName: showHiddenFiles ? "visibility_off" : "visibility" - iconSize: Theme.iconSize - 4 - iconColor: showHiddenFiles ? Theme.primary : Theme.surfaceText - onClicked: showHiddenFiles = !showHiddenFiles - } - - DankActionButton { - circular: false - iconName: viewMode === "grid" ? "view_list" : "grid_view" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText - onClicked: viewMode = viewMode === "grid" ? "list" : "grid" - } - - DankActionButton { - circular: false - iconName: iconSizeIndex === 0 ? "photo_size_select_small" : iconSizeIndex === 1 ? "photo_size_select_large" : iconSizeIndex === 2 ? "photo_size_select_actual" : "zoom_in" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText - visible: viewMode === "grid" - onClicked: iconSizeIndex = (iconSizeIndex + 1) % iconSizes.length - } - - DankActionButton { - circular: false - iconName: "info" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText - onClicked: fileBrowserModal.showKeyboardHints = !fileBrowserModal.showKeyboardHints - } - - DankActionButton { - circular: false - iconName: "close" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText - onClicked: fileBrowserModal.close() - } - } - } - - StyledRect { - width: parent.width - height: 1 - color: Theme.outline - } - - Item { - width: parent.width - height: parent.height - 49 - - Row { - anchors.fill: parent - spacing: 0 - - Row { - width: showSidebar ? 201 : 0 - height: parent.height - spacing: 0 - visible: showSidebar - - FileBrowserSidebar { - height: parent.height - quickAccessLocations: fileBrowserModal.quickAccessLocations - currentPath: fileBrowserModal.currentPath - onLocationSelected: path => navigateTo(path) - } - - StyledRect { - width: 1 - height: parent.height - color: Theme.outline - } - } - - Column { - width: parent.width - (showSidebar ? 201 : 0) - height: parent.height - spacing: 0 - - FileBrowserNavigation { - width: parent.width - currentPath: fileBrowserModal.currentPath - homeDir: fileBrowserModal.homeDir - backButtonFocused: fileBrowserModal.backButtonFocused - keyboardNavigationActive: fileBrowserModal.keyboardNavigationActive - showSidebar: fileBrowserModal.showSidebar - pathEditMode: fileBrowserModal.pathEditMode - onNavigateUp: fileBrowserModal.navigateUp() - onNavigateTo: path => fileBrowserModal.navigateTo(path) - onPathInputFocusChanged: hasFocus => { - fileBrowserModal.pathInputHasFocus = hasFocus; - if (hasFocus) { - fileBrowserModal.pathEditMode = true; - } - } - } - - StyledRect { - width: parent.width - height: 1 - color: Theme.outline - } - - Item { - id: gridContainer - width: parent.width - height: parent.height - 41 - clip: true - - property real gridCellWidth: iconSizes[iconSizeIndex] + 24 - property real gridCellHeight: iconSizes[iconSizeIndex] + 56 - property real availableGridWidth: width - Theme.spacingM * 2 - property int gridColumns: Math.max(1, Math.floor(availableGridWidth / gridCellWidth)) - property real gridLeftMargin: Theme.spacingM + Math.max(0, (availableGridWidth - (gridColumns * gridCellWidth)) / 2) - - onGridColumnsChanged: { - fileBrowserModal.actualGridColumns = gridColumns; - } - Component.onCompleted: { - fileBrowserModal.actualGridColumns = gridColumns; - } - - DankGridView { - id: fileGrid - anchors.fill: parent - anchors.leftMargin: gridContainer.gridLeftMargin - anchors.rightMargin: Theme.spacingM - anchors.topMargin: Theme.spacingS - anchors.bottomMargin: Theme.spacingS - visible: viewMode === "grid" - cellWidth: gridContainer.gridCellWidth - cellHeight: gridContainer.gridCellHeight - cacheBuffer: 260 - model: folderModel - currentIndex: selectedIndex - onCurrentIndexChanged: { - if (keyboardNavigationActive && currentIndex >= 0) - positionViewAtIndex(currentIndex, GridView.Contain); - } - - ScrollBar.vertical: DankScrollbar { - id: gridScrollbar - } - - ScrollBar.horizontal: ScrollBar { - policy: ScrollBar.AlwaysOff - } - - delegate: FileBrowserGridDelegate { - iconSizes: fileBrowserModal.iconSizes - iconSizeIndex: fileBrowserModal.iconSizeIndex - selectedIndex: fileBrowserModal.selectedIndex - keyboardNavigationActive: fileBrowserModal.keyboardNavigationActive - onItemClicked: (index, path, name, isDir) => { - selectedIndex = index; - setSelectedFileData(path, name, isDir); - if (isDir) { - navigateTo(path); - } else { - fileSelected(path); - fileBrowserModal.close(); - } - } - onItemSelected: (index, path, name, isDir) => { - setSelectedFileData(path, name, isDir); - } - - Connections { - function onKeyboardSelectionRequestedChanged() { - if (fileBrowserModal.keyboardSelectionRequested && fileBrowserModal.keyboardSelectionIndex === index) { - fileBrowserModal.keyboardSelectionRequested = false; - selectedIndex = index; - setSelectedFileData(filePath, fileName, fileIsDir); - if (fileIsDir) { - navigateTo(filePath); - } else { - fileSelected(filePath); - fileBrowserModal.close(); - } - } - } - - target: fileBrowserModal - } - } - } - - DankListView { - id: fileList - anchors.fill: parent - anchors.leftMargin: Theme.spacingM - anchors.rightMargin: Theme.spacingM - anchors.topMargin: Theme.spacingS - anchors.bottomMargin: Theme.spacingS - visible: viewMode === "list" - spacing: 2 - model: folderModel - currentIndex: selectedIndex - onCurrentIndexChanged: { - if (keyboardNavigationActive && currentIndex >= 0) - positionViewAtIndex(currentIndex, ListView.Contain); - } - - ScrollBar.vertical: DankScrollbar { - id: listScrollbar - } - - delegate: FileBrowserListDelegate { - width: fileList.width - selectedIndex: fileBrowserModal.selectedIndex - keyboardNavigationActive: fileBrowserModal.keyboardNavigationActive - onItemClicked: (index, path, name, isDir) => { - selectedIndex = index; - setSelectedFileData(path, name, isDir); - if (isDir) { - navigateTo(path); - } else { - fileSelected(path); - fileBrowserModal.close(); - } - } - onItemSelected: (index, path, name, isDir) => { - setSelectedFileData(path, name, isDir); - } - - Connections { - function onKeyboardSelectionRequestedChanged() { - if (fileBrowserModal.keyboardSelectionRequested && fileBrowserModal.keyboardSelectionIndex === index) { - fileBrowserModal.keyboardSelectionRequested = false; - selectedIndex = index; - setSelectedFileData(filePath, fileName, fileIsDir); - if (fileIsDir) { - navigateTo(filePath); - } else { - fileSelected(filePath); - fileBrowserModal.close(); - } - } - } - - target: fileBrowserModal - } - } - } - } - } - } - - FileBrowserSaveRow { - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: Theme.spacingL - saveMode: fileBrowserModal.saveMode - defaultFileName: fileBrowserModal.defaultFileName - currentPath: fileBrowserModal.currentPath - onSaveRequested: filePath => handleSaveFile(filePath) - } - - KeyboardHints { - id: keyboardHints - - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: Theme.spacingL - showHints: fileBrowserModal.showKeyboardHints - } - - FileInfo { - id: fileInfo - - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: Theme.spacingL - width: 300 - showFileInfo: fileBrowserModal.showFileInfo - selectedIndex: fileBrowserModal.selectedIndex - sourceFolderModel: folderModel - currentPath: fileBrowserModal.currentPath - currentFileName: fileBrowserModal.selectedFileName - currentFileIsDir: fileBrowserModal.selectedFileIsDir - currentFileExtension: { - if (fileBrowserModal.selectedFileIsDir || !fileBrowserModal.selectedFileName) - return ""; - - var lastDot = fileBrowserModal.selectedFileName.lastIndexOf('.'); - return lastDot > 0 ? fileBrowserModal.selectedFileName.substring(lastDot + 1).toLowerCase() : ""; - } - } - - FileBrowserSortMenu { - id: sortMenu - anchors.top: parent.top - anchors.right: parent.right - anchors.topMargin: 120 - anchors.rightMargin: Theme.spacingL - sortBy: fileBrowserModal.sortBy - sortAscending: fileBrowserModal.sortAscending - onSortBySelected: value => { - fileBrowserModal.sortBy = value; - } - onSortOrderSelected: ascending => { - fileBrowserModal.sortAscending = ascending; - } - } - } - - FileBrowserOverwriteDialog { - anchors.fill: parent - showDialog: showOverwriteConfirmation - pendingFilePath: fileBrowserModal.pendingFilePath - onConfirmed: filePath => { - showOverwriteConfirmation = false; - fileSelected(filePath); - pendingFilePath = ""; - Qt.callLater(() => fileBrowserModal.close()); - } - onCancelled: { - showOverwriteConfirmation = false; - pendingFilePath = ""; - } - } - } + onFileSelected: path => fileBrowserModal.fileSelected(path) + onCloseRequested: fileBrowserModal.close() } } diff --git a/quickshell/Modals/FileBrowser/FileBrowserSurfaceModal.qml b/quickshell/Modals/FileBrowser/FileBrowserSurfaceModal.qml new file mode 100644 index 00000000..e6e4cd15 --- /dev/null +++ b/quickshell/Modals/FileBrowser/FileBrowserSurfaceModal.qml @@ -0,0 +1,63 @@ +import QtQuick +import Quickshell.Wayland +import qs.Common +import qs.Modals.Common + +DankModal { + id: fileBrowserSurfaceModal + + property string browserTitle: "Select File" + property string browserIcon: "folder_open" + property string browserType: "generic" + property var fileExtensions: ["*.*"] + property alias filterExtensions: fileBrowserSurfaceModal.fileExtensions + property bool showHiddenFiles: false + property bool saveMode: false + property string defaultFileName: "" + property var parentPopout: null + + signal fileSelected(string path) + + layerNamespace: "dms:filebrowser" + modalWidth: 800 + modalHeight: 600 + backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) + closeOnEscapeKey: true + closeOnBackgroundClick: true + allowStacking: true + keepPopoutsOpen: true + + onBackgroundClicked: close() + + onOpened: { + if (parentPopout) { + parentPopout.customKeyboardFocus = WlrKeyboardFocus.None; + } + content.reset(); + Qt.callLater(() => content.forceActiveFocus()); + } + + onDialogClosed: { + if (parentPopout) { + parentPopout.customKeyboardFocus = null; + } + } + + directContent: FileBrowserContent { + id: content + focus: true + + browserTitle: fileBrowserSurfaceModal.browserTitle + browserIcon: fileBrowserSurfaceModal.browserIcon + browserType: fileBrowserSurfaceModal.browserType + fileExtensions: fileBrowserSurfaceModal.fileExtensions + showHiddenFiles: fileBrowserSurfaceModal.showHiddenFiles + saveMode: fileBrowserSurfaceModal.saveMode + defaultFileName: fileBrowserSurfaceModal.defaultFileName + + Component.onCompleted: initialize() + + onFileSelected: path => fileBrowserSurfaceModal.fileSelected(path) + onCloseRequested: fileBrowserSurfaceModal.close() + } +} diff --git a/quickshell/Modules/DankDash/WallpaperTab.qml b/quickshell/Modules/DankDash/WallpaperTab.qml index 4e38bd14..c678c0a4 100644 --- a/quickshell/Modules/DankDash/WallpaperTab.qml +++ b/quickshell/Modules/DankDash/WallpaperTab.qml @@ -3,12 +3,8 @@ import QtCore import QtQuick import QtQuick.Controls import QtQuick.Effects -import QtQuick.Layouts -import Quickshell -import Quickshell.Wayland import qs.Common import qs.Modals.FileBrowser -import qs.Services import qs.Widgets Item { @@ -38,222 +34,222 @@ Item { function getCurrentWallpaper() { if (SessionData.perMonitorWallpaper && targetScreenName) { - return SessionData.getMonitorWallpaper(targetScreenName) + return SessionData.getMonitorWallpaper(targetScreenName); } - return SessionData.wallpaperPath + return SessionData.wallpaperPath; } function setCurrentWallpaper(path) { if (SessionData.perMonitorWallpaper && targetScreenName) { - SessionData.setMonitorWallpaper(targetScreenName, path) + SessionData.setMonitorWallpaper(targetScreenName, path); } else { - SessionData.setWallpaper(path) + SessionData.setWallpaper(path); } } onCurrentPageChanged: { if (currentPage !== lastPage) { - enableAnimation = false - lastPage = currentPage + enableAnimation = false; + lastPage = currentPage; } - updateSelectedFileName() + updateSelectedFileName(); } onGridIndexChanged: { - updateSelectedFileName() + updateSelectedFileName(); } onVisibleChanged: { if (visible && active) { - setInitialSelection() + setInitialSelection(); } } Component.onCompleted: { - loadWallpaperDirectory() + loadWallpaperDirectory(); } onActiveChanged: { if (active && visible) { - setInitialSelection() + setInitialSelection(); } } function handleKeyEvent(event) { - const columns = 4 - const currentCol = gridIndex % columns - const visibleCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage) + const columns = 4; + const currentCol = gridIndex % columns; + const visibleCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage); if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { if (gridIndex >= 0 && gridIndex < visibleCount) { - const absoluteIndex = currentPage * itemsPerPage + gridIndex + const absoluteIndex = currentPage * itemsPerPage + gridIndex; if (absoluteIndex < wallpaperFolderModel.count) { - const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath") + const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath"); if (filePath) { - setCurrentWallpaper(filePath.toString().replace(/^file:\/\//, '')) + setCurrentWallpaper(filePath.toString().replace(/^file:\/\//, '')); } } } - return true + return true; } if (event.key === Qt.Key_Right) { if (gridIndex + 1 < visibleCount) { - gridIndex++ + gridIndex++; } else if (currentPage < totalPages - 1) { - gridIndex = 0 - currentPage++ + gridIndex = 0; + currentPage++; } - return true + return true; } if (event.key === Qt.Key_Left) { if (gridIndex > 0) { - gridIndex-- + gridIndex--; } else if (currentPage > 0) { - currentPage-- - const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage) - gridIndex = prevPageCount - 1 + currentPage--; + const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage); + gridIndex = prevPageCount - 1; } - return true + return true; } if (event.key === Qt.Key_Down) { if (gridIndex + columns < visibleCount) { - gridIndex += columns + gridIndex += columns; } else if (currentPage < totalPages - 1) { - gridIndex = currentCol - currentPage++ + gridIndex = currentCol; + currentPage++; } - return true + return true; } if (event.key === Qt.Key_Up) { if (gridIndex >= columns) { - gridIndex -= columns + gridIndex -= columns; } else if (currentPage > 0) { - currentPage-- - const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage) - const prevPageRows = Math.ceil(prevPageCount / columns) - gridIndex = (prevPageRows - 1) * columns + currentCol - gridIndex = Math.min(gridIndex, prevPageCount - 1) + currentPage--; + const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage); + const prevPageRows = Math.ceil(prevPageCount / columns); + gridIndex = (prevPageRows - 1) * columns + currentCol; + gridIndex = Math.min(gridIndex, prevPageCount - 1); } - return true + return true; } if (event.key === Qt.Key_PageUp && currentPage > 0) { - gridIndex = 0 - currentPage-- - return true + gridIndex = 0; + currentPage--; + return true; } if (event.key === Qt.Key_PageDown && currentPage < totalPages - 1) { - gridIndex = 0 - currentPage++ - return true + gridIndex = 0; + currentPage++; + return true; } if (event.key === Qt.Key_Home && event.modifiers & Qt.ControlModifier) { - gridIndex = 0 - currentPage = 0 - return true + gridIndex = 0; + currentPage = 0; + return true; } if (event.key === Qt.Key_End && event.modifiers & Qt.ControlModifier) { - currentPage = totalPages - 1 - const lastPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage) - gridIndex = Math.max(0, lastPageCount - 1) - return true + currentPage = totalPages - 1; + const lastPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage); + gridIndex = Math.max(0, lastPageCount - 1); + return true; } - return false + return false; } function setInitialSelection() { - const currentWallpaper = getCurrentWallpaper() + const currentWallpaper = getCurrentWallpaper(); if (!currentWallpaper || wallpaperFolderModel.count === 0) { - gridIndex = 0 - updateSelectedFileName() + gridIndex = 0; + updateSelectedFileName(); Qt.callLater(() => { - enableAnimation = true - }) - return + enableAnimation = true; + }); + return; } for (var i = 0; i < wallpaperFolderModel.count; i++) { - const filePath = wallpaperFolderModel.get(i, "filePath") + const filePath = wallpaperFolderModel.get(i, "filePath"); if (filePath && filePath.toString().replace(/^file:\/\//, '') === currentWallpaper) { - const targetPage = Math.floor(i / itemsPerPage) - const targetIndex = i % itemsPerPage - currentPage = targetPage - gridIndex = targetIndex - updateSelectedFileName() + const targetPage = Math.floor(i / itemsPerPage); + const targetIndex = i % itemsPerPage; + currentPage = targetPage; + gridIndex = targetIndex; + updateSelectedFileName(); Qt.callLater(() => { - enableAnimation = true - }) - return + enableAnimation = true; + }); + return; } } - gridIndex = 0 - updateSelectedFileName() + gridIndex = 0; + updateSelectedFileName(); Qt.callLater(() => { - enableAnimation = true - }) + enableAnimation = true; + }); } function loadWallpaperDirectory() { - const currentWallpaper = getCurrentWallpaper() + const currentWallpaper = getCurrentWallpaper(); if (!currentWallpaper || currentWallpaper.startsWith("#")) { if (CacheData.wallpaperLastPath && CacheData.wallpaperLastPath !== "") { - wallpaperDir = CacheData.wallpaperLastPath + wallpaperDir = CacheData.wallpaperLastPath; } else { - wallpaperDir = "" + wallpaperDir = ""; } - return + return; } - wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/')) + wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/')); } function updateSelectedFileName() { if (wallpaperFolderModel.count === 0) { - selectedFileName = "" - return + selectedFileName = ""; + return; } - const absoluteIndex = currentPage * itemsPerPage + gridIndex + const absoluteIndex = currentPage * itemsPerPage + gridIndex; if (absoluteIndex < wallpaperFolderModel.count) { - const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath") + const filePath = wallpaperFolderModel.get(absoluteIndex, "filePath"); if (filePath) { - const pathStr = filePath.toString().replace(/^file:\/\//, '') - selectedFileName = pathStr.substring(pathStr.lastIndexOf('/') + 1) - return + const pathStr = filePath.toString().replace(/^file:\/\//, ''); + selectedFileName = pathStr.substring(pathStr.lastIndexOf('/') + 1); + return; } } - selectedFileName = "" + selectedFileName = ""; } Connections { target: SessionData function onWallpaperPathChanged() { - loadWallpaperDirectory() + loadWallpaperDirectory(); if (visible && active) { - setInitialSelection() + setInitialSelection(); } } function onMonitorWallpapersChanged() { - loadWallpaperDirectory() + loadWallpaperDirectory(); if (visible && active) { - setInitialSelection() + setInitialSelection(); } } } onTargetScreenNameChanged: { - loadWallpaperDirectory() + loadWallpaperDirectory(); if (visible && active) { - setInitialSelection() + setInitialSelection(); } } @@ -262,17 +258,17 @@ Item { function onCountChanged() { if (wallpaperFolderModel.status === FolderListModel.Ready) { if (visible && active) { - setInitialSelection() + setInitialSelection(); } - updateSelectedFileName() + updateSelectedFileName(); } } function onStatusChanged() { if (wallpaperFolderModel.status === FolderListModel.Ready && wallpaperFolderModel.count > 0) { if (visible && active) { - setInitialSelection() + setInitialSelection(); } - updateSelectedFileName() + updateSelectedFileName(); } } } @@ -290,51 +286,27 @@ Item { folder: wallpaperDir ? "file://" + wallpaperDir : "" } - Loader { - id: wallpaperBrowserLoader - active: false - asynchronous: true + FileBrowserSurfaceModal { + id: wallpaperBrowser - onActiveChanged: { - if (active && parentPopout) { - parentPopout.WlrLayershell.keyboardFocus = WlrKeyboardFocus.None - } - } - - sourceComponent: FileBrowserModal { - Component.onCompleted: { - open() - } - browserTitle: I18n.tr("Select Wallpaper Directory", "wallpaper directory file browser title") - browserIcon: "folder_open" - browserType: "wallpaper" - showHiddenFiles: false - fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] - allowStacking: true - - onFileSelected: path => { - const cleanPath = path.replace(/^file:\/\//, '') - setCurrentWallpaper(cleanPath) - - const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/')) - if (dirPath) { - wallpaperDir = dirPath - CacheData.wallpaperLastPath = dirPath - CacheData.saveCache() - } - close() - } - - onDialogClosed: { - if (parentPopout) { - if (CompositorService.isHyprland) { - parentPopout.WlrLayershell.keyboardFocus = WlrKeyboardFocus.OnDemand - } else { - parentPopout.WlrLayershell.keyboardFocus = WlrKeyboardFocus.Exclusive - } - } - Qt.callLater(() => wallpaperBrowserLoader.active = false) + browserTitle: I18n.tr("Select Wallpaper Directory", "wallpaper directory file browser title") + browserIcon: "folder_open" + browserType: "wallpaper" + showHiddenFiles: false + fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] + parentPopout: root.parentPopout + + onFileSelected: path => { + const cleanPath = path.replace(/^file:\/\//, ''); + setCurrentWallpaper(cleanPath); + + const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/')); + if (dirPath) { + wallpaperDir = dirPath; + CacheData.wallpaperLastPath = dirPath; + CacheData.saveCache(); } + close(); } } @@ -376,41 +348,41 @@ Item { } model: { - const startIndex = currentPage * itemsPerPage - const endIndex = Math.min(startIndex + itemsPerPage, wallpaperFolderModel.count) - const items = [] + const startIndex = currentPage * itemsPerPage; + const endIndex = Math.min(startIndex + itemsPerPage, wallpaperFolderModel.count); + const items = []; for (var i = startIndex; i < endIndex; i++) { - const filePath = wallpaperFolderModel.get(i, "filePath") + const filePath = wallpaperFolderModel.get(i, "filePath"); if (filePath) { - items.push(filePath.toString().replace(/^file:\/\//, '')) + items.push(filePath.toString().replace(/^file:\/\//, '')); } } - return items + return items; } onModelChanged: { - const clampedIndex = model.length > 0 ? Math.min(Math.max(0, gridIndex), model.length - 1) : 0 + const clampedIndex = model.length > 0 ? Math.min(Math.max(0, gridIndex), model.length - 1) : 0; if (gridIndex !== clampedIndex) { - gridIndex = clampedIndex + gridIndex = clampedIndex; } } onCountChanged: { if (count > 0) { - const clampedIndex = Math.min(gridIndex, count - 1) - currentIndex = clampedIndex - positionViewAtIndex(clampedIndex, GridView.Contain) + const clampedIndex = Math.min(gridIndex, count - 1); + currentIndex = clampedIndex; + positionViewAtIndex(clampedIndex, GridView.Contain); } - enableAnimation = true + enableAnimation = true; } Connections { target: root function onGridIndexChanged() { if (wallpaperGrid.count > 0) { - wallpaperGrid.currentIndex = gridIndex + wallpaperGrid.currentIndex = gridIndex; if (!enableAnimation) { - wallpaperGrid.positionViewAtIndex(gridIndex, GridView.Contain) + wallpaperGrid.positionViewAtIndex(gridIndex, GridView.Contain); } } } @@ -487,9 +459,9 @@ Item { cursorShape: Qt.PointingHandCursor onClicked: { - gridIndex = index + gridIndex = index; if (modelData) { - setCurrentWallpaper(modelData) + setCurrentWallpaper(modelData); } } } @@ -535,7 +507,7 @@ Item { opacity: enabled ? 1.0 : 0.3 onClicked: { if (currentPage > 0) { - currentPage-- + currentPage--; } } } @@ -557,7 +529,7 @@ Item { opacity: enabled ? 1.0 : 0.3 onClicked: { if (currentPage < totalPages - 1) { - currentPage++ + currentPage++; } } } @@ -570,7 +542,7 @@ Item { iconSize: 20 buttonSize: 32 opacity: 0.7 - onClicked: wallpaperBrowserLoader.active = true + onClicked: wallpaperBrowser.open() } } diff --git a/quickshell/Modules/Settings/PersonalizationTab.qml b/quickshell/Modules/Settings/PersonalizationTab.qml index c1571bb4..b19caa34 100644 --- a/quickshell/Modules/Settings/PersonalizationTab.qml +++ b/quickshell/Modules/Settings/PersonalizationTab.qml @@ -9,7 +9,6 @@ import qs.Widgets Item { id: personalizationTab - property var wallpaperBrowser: wallpaperBrowserLoader.item property var parentModal: null property var cachedFontFamilies: [] property bool fontsEnumerated: false @@ -207,9 +206,7 @@ Item { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - onClicked: { - wallpaperBrowserLoader.active = true; - } + onClicked: mainWallpaperBrowser.open() } } @@ -598,7 +595,7 @@ Item { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { - lightWallpaperBrowserLoader.active = true; + lightWallpaperBrowser.open(); } } } @@ -787,7 +784,7 @@ Item { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { - darkWallpaperBrowserLoader.active = true; + darkWallpaperBrowser.open(); } } } @@ -2140,86 +2137,56 @@ Item { } } - Loader { - id: wallpaperBrowserLoader - active: false - asynchronous: true + FileBrowserModal { + id: mainWallpaperBrowser - sourceComponent: FileBrowserModal { - parentModal: personalizationTab.parentModal - Component.onCompleted: { - open(); - } - browserTitle: I18n.tr("Select Wallpaper", "wallpaper file browser title") - browserIcon: "wallpaper" - browserType: "wallpaper" - showHiddenFiles: true - fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] - onFileSelected: path => { - if (SessionData.perMonitorWallpaper) { - SessionData.setMonitorWallpaper(selectedMonitorName, path); - } else { - SessionData.setWallpaper(path); - } - close(); - } - onDialogClosed: { - Qt.callLater(() => wallpaperBrowserLoader.active = false); + parentModal: personalizationTab.parentModal + browserTitle: I18n.tr("Select Wallpaper", "wallpaper file browser title") + browserIcon: "wallpaper" + browserType: "wallpaper" + showHiddenFiles: true + fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] + onFileSelected: path => { + if (SessionData.perMonitorWallpaper) { + SessionData.setMonitorWallpaper(selectedMonitorName, path); + } else { + SessionData.setWallpaper(path); } + close(); } } - Loader { - id: lightWallpaperBrowserLoader - active: false - asynchronous: true + FileBrowserModal { + id: lightWallpaperBrowser - sourceComponent: FileBrowserModal { - parentModal: personalizationTab.parentModal - Component.onCompleted: { - open(); - } - browserTitle: I18n.tr("Select Wallpaper", "light mode wallpaper file browser title") - browserIcon: "light_mode" - browserType: "wallpaper" - showHiddenFiles: true - fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] - onFileSelected: path => { - SessionData.wallpaperPathLight = path; - SessionData.syncWallpaperForCurrentMode(); - SessionData.saveSettings(); - close(); - } - onDialogClosed: { - Qt.callLater(() => lightWallpaperBrowserLoader.active = false); - } + parentModal: personalizationTab.parentModal + browserTitle: I18n.tr("Select Wallpaper", "light mode wallpaper file browser title") + browserIcon: "light_mode" + browserType: "wallpaper" + showHiddenFiles: true + fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] + onFileSelected: path => { + SessionData.wallpaperPathLight = path; + SessionData.syncWallpaperForCurrentMode(); + SessionData.saveSettings(); + close(); } } - Loader { - id: darkWallpaperBrowserLoader - active: false - asynchronous: true + FileBrowserModal { + id: darkWallpaperBrowser - sourceComponent: FileBrowserModal { - parentModal: personalizationTab.parentModal - Component.onCompleted: { - open(); - } - browserTitle: I18n.tr("Select Wallpaper", "dark mode wallpaper file browser title") - browserIcon: "dark_mode" - browserType: "wallpaper" - showHiddenFiles: true - fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] - onFileSelected: path => { - SessionData.wallpaperPathDark = path; - SessionData.syncWallpaperForCurrentMode(); - SessionData.saveSettings(); - close(); - } - onDialogClosed: { - Qt.callLater(() => darkWallpaperBrowserLoader.active = false); - } + parentModal: personalizationTab.parentModal + browserTitle: I18n.tr("Select Wallpaper", "dark mode wallpaper file browser title") + browserIcon: "dark_mode" + browserType: "wallpaper" + showHiddenFiles: true + fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] + onFileSelected: path => { + SessionData.wallpaperPathDark = path; + SessionData.syncWallpaperForCurrentMode(); + SessionData.saveSettings(); + close(); } } }