Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for WebAssembly
  4. How to use fileDialog with QML for Web Assembly
Forum Updated to NodeBB v4.3 + New Features

How to use fileDialog with QML for Web Assembly

Scheduled Pinned Locked Moved Unsolved Qt for WebAssembly
qml wa
5 Posts 2 Posters 991 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • G Offline
    G Offline
    Gilboonet
    wrote on 24 Jun 2023, 15:37 last edited by Gilboonet
    #1

    Hello, I'm slowly discovering QML / QT Quick in order to be able to use 3D into a Web Assembly project that I initially made in Qt C++. QML is a very interesting language, but I'm struggling to find a way to use a file selector that will work on WA. For the moment I simply have a menubar with an entry that fires fileDialog.open(), that works fine on Desktop, but nothing happens on WA, it is maybe normal as on the C++ application I needed to use QFileDialog::getOpenFileContent (I mean to have it working on WA) and on QML FileDialog I only found open().

    These are my very first steps with QML, so maybe I'm doing it wrong. I looked for all docs but didn't find any, maybe I need to dig the examples project some more. Does someone have any information that could help me ?

    Here's my code

    import QtQuick
    import QtQuick.Window
    import QtQuick.Controls
    import QtQuick.Dialogs
    
    ApplicationWindow {
        width: 640
        height: 480
        visible: true
        title: qsTr("debut")
        FileDialog {
            id: fileDialog
            nameFilters: ["Fichier 3D WaveFront (*.obj)"]
        }
    
        MenuBar {
            Menu {
                title: qsTr("&Fichier")
                Action { text: qsTr("&Nouveau depuis .OBJ...")
                    onTriggered: {
                        fileDialog.open()
                    }
                }
                Action { text: qsTr("&Ouvrir .DEP...") }
                Action { text: qsTr("&Sauver .DEP") }
                Action { text: qsTr("&Exporter en SVG...") }
                MenuSeparator { }
                Action { text: qsTr("&Quitter")
                    onTriggered: Qt.quit();
                }
            }
            Menu {
                title: qsTr("&Edit")
                Action { text: qsTr("Cu&t") }
                Action { text: qsTr("&Copy") }
                Action { text: qsTr("&Paste") }
            }
            Menu {
                title: qsTr("Aide")
                Action { text: qsTr("&A propos") }
            }
        }
    
    }
    

    here's what is on the console.log
    Screenshot_20230624_175954.png

    1 Reply Last reply
    0
    • G Offline
      G Offline
      Gilboonet
      wrote on 24 Jun 2023, 19:45 last edited by
      #2

      One more thing, the generated main.cpp has a warning, a deprecated operator
      Screenshot_20230624_214250.png

      1 Reply Last reply
      0
      • G Offline
        G Offline
        Gilboonet
        wrote on 25 Jun 2023, 08:54 last edited by Gilboonet
        #3

        Still investigating, I did what's written into fileDialog QML Type documentation, Availability paragraph :

        The Qt Labs Platform module uses Qt Widgets as a fallback on platforms that do not have a native implementation available. Therefore, applications that use types from the Qt Labs Platform module should link to QtWidgets and use QApplication instead of QGuiApplication.
        To link against the QtWidgets library, add the following to your qmake project file:

        QT += widgets
        

        (I did the equivalent for cmake as stated on QApplication documentation :

        find_package(Qt6 REQUIRED COMPONENTS Widgets) 
        target_link_libraries(mytarget PRIVATE Qt6::Widgets)
        

        )

        Create an instance of QApplication in main():

        #include <QApplication>
        #include <QQmlApplicationEngine>
        
        int main(int argc, char *argv[])
        {
            QApplication app(argc, argv);
            QQmlApplicationEngine engine;
            engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
            return app.exec();
        }
        

        but nothing changed and I still have the same warning saying modules are not installed.

        Maybe do I need to add those modules into the cmake file ? Or maybe am I using a too recent QT 6.6.0 (even if I'm using same versions for Desktop and WA kits, but I'm new to that) and need to get back to a previous one ?

        Or maybe what I'm trying to do is not possible and this is not what QML for WA is supposed to be used for ?

        1 Reply Last reply
        0
        • G Offline
          G Offline
          Gilboonet
          wrote on 27 Jun 2023, 06:52 last edited by
          #4

          Well, I didn't find any QML project that enables the user to use local file like it can be done with Qt C++ using QFileDialog::getOpenFileContent() and its saving counterpart, and don't know where to search. I will keep searching as I have no feedback that it is not possible. Anyway at the same time I am learning the basics of QML.

          It's a little odd, my posts here seems to be standalone as it's been some time that I post without receiving some feedback.

          J 1 Reply Last reply 29 Mar 2024, 12:39
          0
          • G Gilboonet
            27 Jun 2023, 06:52

            Well, I didn't find any QML project that enables the user to use local file like it can be done with Qt C++ using QFileDialog::getOpenFileContent() and its saving counterpart, and don't know where to search. I will keep searching as I have no feedback that it is not possible. Anyway at the same time I am learning the basics of QML.

            It's a little odd, my posts here seems to be standalone as it's been some time that I post without receiving some feedback.

            J Offline
            J Offline
            JakubM_work
            wrote on 29 Mar 2024, 12:39 last edited by JakubM_work
            #5

            @Gilboonet it's been a while since you've asked the question, but as I found it then I'll just reply for future generations :)

            Basically you can't directly access any files on your devices from WASM. You need to use Browser API to achieve this, so the only option is to call native browser file picker for open & browser download for saving. You can pick which file to open, but when you save, then it's gonna be downloaded by a browser like any other file from a web (yes, it'll be saved in Downloads folder).

            It seems that Qt also supports file picking on WASM but unfortunately I didn't have a chance to test it yet: https://doc.qt.io/qt-6/qfiledialog.html#getOpenFileContent

            Here's a great example on how to use native file picker in Qt project:

            https://github.com/msorvig/qt-webassembly-examples/tree/master/gui_localfiles

            Here's my code, heavily based on above example, if you're interested:

            std::function<void(const QByteArray &, const QString &)> fileDataReadyCallback;
            extern "C" EMSCRIPTEN_KEEPALIVE void wasmFileDataReadyCallback(char *content, size_t contentSize, const char *fileName)
            {
                if (fileDataReadyCallback == nullptr)
                    return;
            
                QByteArray data(content, contentSize);
                free(content);
            
                fileDataReadyCallback(data, QString::fromUtf8(fileName));
                fileDataReadyCallback = nullptr;
            }
            
            void WasmPlatformIntegration::openFile()
            {
                openFile("*", [](const QByteArray &content, const QString &fileName) {
                    qDebug() << "load file" << fileName << "size" << content.size();
                });
            }
            
            void WasmPlatformIntegration::openFile(const QString &fileNameFilter,
                                                         std::function<void(const QByteArray &, const QString &)> fileDataReady)
            {
                if (::fileDataReadyCallback)
                    puts("Warning: Concurrent loadFile() calls are not supported. Cancelling "
                         "earlier call");
            
                ::fileDataReadyCallback = nullptr;
                ::wasmFileDataReadyCallback(nullptr, 0, nullptr);
            
                ::fileDataReadyCallback = fileDataReady;
                EM_ASM_(
                        {
                            const fileNameFilter = UTF8ToString($0);
            
                            // Crate file file input which whil display the native file dialog
                            let fileElement = document.createElement("input");
                            document.body.appendChild(fileElement);
                            fileElement.type = "file";
                            fileElement.style = "display:none";
                            fileElement.accept = fileNameFilter;
                            fileElement.onchange = function(event)
                            {
                                const files = event.target.files;
            
                                // Read files
                                for (var i = 0; i < files.length; i++) {
                                    const file = files[i];
                                    var reader = new FileReader();
                                    reader.onload = function()
                                    {
                                        const name = file.name;
                                        let contentArray = new Uint8Array(reader.result);
                                        const contentSize = reader.result.byteLength;
            
                                        const heapPointer = _malloc(contentSize);
                                        const heapBytes = new Uint8Array(Module.HEAPU8.buffer, heapPointer, contentSize);
                                        heapBytes.set(contentArray);
            
                                        // Null out the first data copy to enable GC
                                        reader = null;
                                        contentArray = null;
            
                                        // Call the C++ file data ready callback
                                        ccall("wasmFileDataReadyCallback",
                                              null,
                                              [ "number", "number", "string" ],
                                              [ heapPointer, contentSize, name ]);
                                    };
                                    reader.readAsArrayBuffer(file);
                                }
            
                                // Clean up document
                                document.body.removeChild(fileElement);
            
                            }; // onchange callback
            
                            // Trigger file dialog open
                            fileElement.click();
                        },
                        fileNameFilter.toUtf8().constData());
            }
            
            
            void WasmPlatformIntegration::saveFile(const QByteArray &content, const QString &fileNameHint)
            {
                EM_ASM_(
                        {
                            const contentPointer = $0;
                            const contentLength = $1;
                            const fileNameHint = UTF8ToString($2);
                            const fileContent = Module.HEAPU8.subarray(contentPointer, contentPointer + contentLength);
            
                            const arrayBuffer = fileContent.slice();
                            const uint8Array = new Uint8Array(arrayBuffer);
                            uint8Array.fill(255);
                            const fileblob = new Blob([arrayBuffer]);
            
                            // Create a hidden download link and click it programatically
                            let link = document.createElement("a");
                            document.body.appendChild(link);
                            link.download = fileNameHint;
                            link.href = window.URL.createObjectURL(fileblob);
                            link.style = "display:none";
                            link.click();
                            document.body.removeChild(link);
                        },
                        content.constData(),
                        content.size(),
                        fileNameHint.toUtf8().constData());
            }
            
            
            1 Reply Last reply
            0
            • Pl45m4P Pl45m4 referenced this topic on 14 Nov 2024, 16:56

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved