diff --git a/io.github.saeugetier.photobooth.json b/io.github.saeugetier.photobooth.json index cdc3d596..7743b753 100644 --- a/io.github.saeugetier.photobooth.json +++ b/io.github.saeugetier.photobooth.json @@ -9,6 +9,7 @@ "--share=network", "--device=all", "--filesystem=home", + "--filesystem=host:removable", "--socket=fallback-x11", "--socket=wayland" ], @@ -159,4 +160,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/qml.qrc b/qml.qrc index ac5a62f4..868c5dde 100644 --- a/qml.qrc +++ b/qml.qrc @@ -94,5 +94,6 @@ shaders/previewshader.frag.qsb models/coco.names images/backgrounds/Stripes.png + qml/content/CustomFolderDialog.qml diff --git a/qml/Application.qml b/qml/Application.qml index 828221cd..64902714 100644 --- a/qml/Application.qml +++ b/qml/Application.qml @@ -167,7 +167,7 @@ ApplicationWindow { { id: applicationSettings category: "Application" - property url foldername: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + property url foldername: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + "/photobooth" property bool printEnable: true property string password: "0815" property string language: "en" diff --git a/qml/SettingsMenu.qml b/qml/SettingsMenu.qml index 1f3469f7..2e0c0b16 100644 --- a/qml/SettingsMenu.qml +++ b/qml/SettingsMenu.qml @@ -1,6 +1,10 @@ import QtQuick import QtMultimedia import QtQuick.Controls +import QtQuick.Dialogs +import Qt.labs.platform +import QtQml +import "content" SettingsMenuForm { id: form @@ -74,6 +78,50 @@ SettingsMenuForm { console.log("index: " + Number(index).toString()) } + function delay(delayTime, cb) { + timer = new Timer(); + timer.interval = delayTime; + timer.repeat = false; + timer.triggered.connect(cb); + timer.start(); + } + + CustomFolderDialog { + id: pictureFolderDialog + title: qsTr("Select Pictures Folder") + anchors.centerIn: parent + width: parent.width - 100 + height: parent.height - 100 + + Timer + { + id: folderCheckTimer + interval: 1000 + running: false + repeat: false + onTriggered: function() { + filesystem.checkImageFolders() + console.log("Checked folder: " + applicationSettings.foldername) + } + } + + onAccepted: function() + { + console.log("Selected folder: " + pictureFolderDialog.currentFolder) + applicationSettings.foldername = pictureFolderDialog.currentFolder + applicationSettings.sync() + + folderCheckTimer.start() + } + } + + buttonSelectPhotoDirectory.onClicked: + { + pictureFolderDialog.currentFolder = applicationSettings.foldername + console.log("selecting pictures folder: " + pictureFolderDialog.currentFolder) + pictureFolderDialog.open() + } + buttonClose.onClicked: { exitSettings() diff --git a/qml/SettingsMenuForm.ui.qml b/qml/SettingsMenuForm.ui.qml index 7d610300..5f1fa9e3 100644 --- a/qml/SettingsMenuForm.ui.qml +++ b/qml/SettingsMenuForm.ui.qml @@ -28,6 +28,7 @@ Item { property alias versionText: labelVersionText.text property alias comboBoxCameraOrientation: comboBoxCameraOrientation property alias comboBoxNeuralNetworkRuntime: comboBoxNeuralNetworkRuntime + property alias buttonSelectPhotoDirectory: buttonSelectPhotoDirectory ColumnLayout { anchors.fill: parent @@ -92,6 +93,25 @@ Item { anchors.topMargin: 20 spacing: 20 + RowLayout { + spacing: 10 + Label { + text: qsTr("Photo Directory: ") + } + Label { + id: labelPhotoDirectory + Layout.fillWidth: true + text: applicationSettings.foldername + } + Item { + Layout.fillWidth: true + } + Button { + id: buttonSelectPhotoDirectory + text: qsTr("Browse") + } + } + Button { id: buttonCopyPhotos text: qsTr("Copy photos to removable disk") diff --git a/qml/content/CustomFolderDialog.qml b/qml/content/CustomFolderDialog.qml new file mode 100644 index 00000000..3c6fa1de --- /dev/null +++ b/qml/content/CustomFolderDialog.qml @@ -0,0 +1,224 @@ +// CustomFolderDialog.qml +import QtQuick 6.5 +import QtQuick.Controls 6.5 +import QtQuick.Layouts 6.5 +import Qt.labs.folderlistmodel 2.2 +import QtQuick.VirtualKeyboard 6.5 +import QtCore + +Dialog { + id: root + modal: true + width: 800 + height: 600 + standardButtons: Dialog.NoButton + + property url currentFolder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + + signal canceled() + + property bool createMode: false + + Rectangle { + anchors.fill: parent + color: Material.background + border.color: Material.foreground + radius: 8 + + ColumnLayout { + anchors.fill: parent + spacing: 10 + + Text { + text: qsTr("Select Folder") + font.pixelSize: 20 + Layout.alignment: Qt.AlignHCenter + } + + RowLayout { + id: breadcrumbBar + Layout.fillWidth: true + spacing: 4 + + // Root Button "/" + Button { + text: "/" + font.pixelSize: 14 + onClicked: { + root.currentFolder = "file:///" + folderModel.folder = root.currentFolder + } + } + + Repeater { + model:{ + var path = root.currentFolder.toString().replace("file://", "") + var segments = path.split("/").filter(s => s.length > 0) + return segments + } + + delegate: RowLayout { + spacing: 2 + + Button { + text: modelData === "/" ? "/" : modelData + font.pixelSize: 14 + onClicked: { + let segments = root.currentFolder.toString().replace("file://", "").split("/").filter(s => s.length > 0) + let newPath = "/" + segments.slice(0, index + 1).join("/") + root.currentFolder = "file://" + newPath + folderModel.folder = root.currentFolder + + } + } + + Text { + text: index < breadcrumbBarRepeater.count - 1 ? " / " : "" + font.pixelSize: 14 + color: Material.foreground + } + } + + id: breadcrumbBarRepeater + } + + Item { + Layout.fillWidth: true + } + + Button { + text: createMode ? qsTr("Cancel") : qsTr("New Folder") + onClicked: { + createMode = !createMode + if (!createMode) + newFolderNameField.text = "" + } + } + } + + RowLayout { + ToolSeparator { + Layout.fillWidth: true + orientation: Qt.Horizontal + } + } + + // Inline New Folder Creation + RowLayout { + visible: createMode + Layout.fillWidth: true + spacing: 10 + + TextField { + id: newFolderNameField + placeholderText: "New folder name" + Layout.fillWidth: true + inputMethodHints: Qt.ImhNoPredictiveText + focus: createMode + onActiveFocusChanged: { + if (activeFocus && createMode) { + Qt.inputMethod.show() + } else { + Qt.inputMethod.hide() + } + } + } + + Button { + text: "Create" + Button { + text: "Create" + onClicked: { + const name = newFolderNameField.text.trim() + if (name.length > 0) { + var basePath = currentFolder.toString().replace("file://", "") + var newPath = basePath + "/" + name + var success = filesystem.createFolder(newPath) + console.log("Folder created:", success, newPath) + + if (success) { + createMode = false + newFolderNameField.text = "" + Qt.inputMethod.hide() + } + } + } + } + } + } + + ListView { + id: folderList + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AlwaysOn + } + + model: FolderListModel { + id: folderModel + folder: currentFolder + showDirs: true + showFiles: false + sortField: FolderListModel.Name + } + + delegate: Rectangle { + width: folderList.width + height: 48 + color: model.fileURL === currentFolder ? "#ddeeff" : "transparent" + + Text { + text: fileName + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 12 + font.pixelSize: 16 + color: Material.foreground + } + + MouseArea { + anchors.fill: parent + onClicked: { + root.currentFolder = model.fileURL + folderModel.folder = model.fileURL + } + } + } + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 20 + + Button { + text: "Select" + onClicked: { + root.accepted() + root.close() + } + } + + Button { + text: "Cancel" + onClicked: { + root.close() + root.canceled() + } + } + } + } + + // Virtual Keyboard - only visible if TextField is active & createMode is on + InputPanel { + id: inputPanel + y: parent.height - height + width: parent.width > 600 ? 600 : parent.width + visible: Qt.inputMethod.visible && createMode + anchors.horizontalCenter: parent.horizontalCenter + z: 100 + } + } +} diff --git a/src/filesystem.cpp b/src/filesystem.cpp index a7c87e12..6c713bae 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -70,12 +70,42 @@ QUrl FileSystem::findFile(QString filename, QList searchPaths, bool search QString FileSystem::getImagePath() { QSettings settings("saeugetier", "qtbooth"); + settings.sync(); if(settings.contains("Application/foldername")) return settings.value("Application/foldername").value(); else return "file://" + QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } +bool FileSystem::createFolder(const QString &path) +{ + if(path.length() == 0) + { + qDebug() << "Filesystem Error: path is empty!"; + return false; + } + + QDir dir(path); + if(!dir.exists()) + { + if(dir.mkpath(".")) + { + qDebug() << "Created folder: " << path; + return true; + } + else + { + qDebug() << "Filesystem Error: Could not create folder: " << path; + return false; + } + } + else + { + qDebug() << "Folder already exists: " << path; + return true; + } +} + void FileSystem::checkImageFolders() { QString imagePath = getImagePath(); diff --git a/src/filesystem.h b/src/filesystem.h index 1f0ee426..a892f1f3 100644 --- a/src/filesystem.h +++ b/src/filesystem.h @@ -12,6 +12,7 @@ class FileSystem : public QObject explicit FileSystem(QObject *parent = nullptr); Q_INVOKABLE QUrl findFile(QString filename, QList searchPaths, bool searchInResource = true); Q_INVOKABLE QString getImagePath(); + Q_INVOKABLE bool createFolder(const QString &path); Q_INVOKABLE void checkImageFolders(); Q_INVOKABLE bool removableDriveMounted(); Q_INVOKABLE void unmountRemoveableDrive();