From 8fc09b33240fa0b55bd0db22103db65861bc2d6b Mon Sep 17 00:00:00 2001 From: Pavel Pisa Date: Fri, 28 Jun 2019 15:36:23 +0200 Subject: Include QHtml5File source file to allow ELF file load under emscripten. Signed-off-by: Pavel Pisa --- qtmips_gui/qhtml5file_html5.cpp | 200 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 qtmips_gui/qhtml5file_html5.cpp (limited to 'qtmips_gui/qhtml5file_html5.cpp') diff --git a/qtmips_gui/qhtml5file_html5.cpp b/qtmips_gui/qhtml5file_html5.cpp new file mode 100644 index 0000000..60f26d7 --- /dev/null +++ b/qtmips_gui/qhtml5file_html5.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qhtml5file.h" + +#include + +#include +#include + +// +// This file implements file load via HTML file input element and file save via browser download. +// + +// Global user file data ready callback and C helper function. JavaScript will +// call this function when the file data is ready; the helper then forwards +// the call to the current handler function. This means there can be only one +// file open in proress at a given time. +std::function g_qtFileDataReadyCallback; +extern "C" EMSCRIPTEN_KEEPALIVE void qt_callFileDataReady(char *content, size_t contentSize, const char *fileName) +{ + if (g_qtFileDataReadyCallback == nullptr) + return; + + g_qtFileDataReadyCallback(content, contentSize, fileName); + g_qtFileDataReadyCallback = nullptr; +} + +namespace { + void loadFile(const char *accept, std::function fileDataReady) + { + if (::g_qtFileDataReadyCallback) + puts("Warning: Concurrent loadFile() calls are not supported. Cancelling earlier call"); + + // Call qt_callFileDataReady to make sure the emscripten linker does not + // optimize it away, which may happen if the function is called from JavaScript + // only. Set g_qtFileDataReadyCallback to null to make it a a no-op. + ::g_qtFileDataReadyCallback = nullptr; + ::qt_callFileDataReady(nullptr, 0, nullptr); + + ::g_qtFileDataReadyCallback = fileDataReady; + EM_ASM_({ + const accept = UTF8ToString($0); + + // Crate file file input which whil display the native file dialog + var fileElement = document.createElement("input"); + document.body.appendChild(fileElement); + fileElement.type = "file"; + fileElement.style = "display:none"; + fileElement.accept = accept; + 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; + var contentArray = new Uint8Array(reader.result); + const contentSize = reader.result.byteLength; + + // Copy the file file content to the C++ heap. + // Note: this could be simplified by passing the content as an + // "array" type to ccall and then let it copy to C++ memory. + // However, this built-in solution does not handle files larger + // than ~15M (Chrome). Instead, allocate memory manually and + // pass a pointer to the C++ side (which will free() it when done). + + // TODO: consider slice()ing the file to read it picewise and + // then assembling it in a QByteArray on the C++ side. + + 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("qt_callFileDataReady", 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(); + + }, accept); + } + + void saveFile(const char *contentPointer, size_t contentLength, const char *fileNameHint) + { + EM_ASM_({ + // Make the file contents and file name hint accessible to Javascript: convert + // the char * to a JavaScript string and create a subarray view into the C heap. + const contentPointer = $0; + const contentLength = $1; + const fileNameHint = UTF8ToString($2); + const fileContent = Module.HEAPU8.subarray(contentPointer, contentPointer + contentLength); + + // Create a hidden download link and click it programatically + const fileblob = new Blob([fileContent], { type : "application/octet-stream" } ); + var 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); + + }, contentPointer, contentLength, fileNameHint); + } +} + +/*! + \brief Read local file via file dialog. + + Call this function to make the browser display an open-file dialog. This function + returns immediately, and \a fileDataReady is called when the user has selected a file + and the file contents has been read. + + \a The accept argument specifies which file types to accept, and must follow the + html standard formatting, for example ".png, .jpg, .jpeg". + + This function is implemented on Qt for WebAssembly only. A nonfunctional cross- + platform stub is provided so that code that uses it can compile on all platforms. +*/ +void QHtml5File::load(const QString &accept, std::function fileDataReady) +{ + loadFile(accept.toUtf8().constData(), [=](char *content, size_t size, const char *fileName) { + + // Copy file data into QByteArray and free buffer that was allocated + // on the JavaScript side. We could have used QByteArray::fromRawData() + // to avoid the copy here, but that would make memory management awkward. + QByteArray qtFileContent(content, size); + free(content); + + // Call user-supplied data ready callback + fileDataReady(qtFileContent, QString::fromUtf8(fileName)); + }); +} + +/*! + \brief Write local file via browser download + + Call this function to make the browser start a file download. The file + will contains the given \a content, with a suggested \a fileNameHint. + + This function is implemented on Qt for WebAssembly only. A nonfunctional cross- + platform stub is provided so that code that uses it can compile on all platforms. +*/ +void QHtml5File::save(const QByteArray &content, const QString &fileNameHint) +{ + // Convert to C types and save + saveFile(content.constData(), content.size(), fileNameHint.toUtf8().constData()); +} -- cgit v1.2.3