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();