diff options
-rw-r--r-- | qtmips_gui/newdialog.cpp | 18 | ||||
-rw-r--r-- | qtmips_gui/qhtml5file.h | 59 | ||||
-rw-r--r-- | qtmips_gui/qhtml5file_html5.cpp | 200 | ||||
-rw-r--r-- | qtmips_gui/qtmips_gui.pro | 6 |
4 files changed, 283 insertions, 0 deletions
diff --git a/qtmips_gui/newdialog.cpp b/qtmips_gui/newdialog.cpp index 0e1c5dc..ef9dac3 100644 --- a/qtmips_gui/newdialog.cpp +++ b/qtmips_gui/newdialog.cpp @@ -37,6 +37,11 @@ #include "mainwindow.h" #include "qtmipsexception.h" +#ifdef __EMSCRIPTEN__ +#include <QFileInfo> +#include "qhtml5file.h" +#endif + NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { setWindowTitle("New machine"); @@ -146,6 +151,7 @@ void NewDialog::create_empty() { void NewDialog::browse_elf() { +#ifndef __EMSCRIPTEN__ QFileDialog elf_dialog(this); elf_dialog.setFileMode(QFileDialog::ExistingFile); if (elf_dialog.exec()) { @@ -154,6 +160,18 @@ void NewDialog::browse_elf() { config->set_elf(path); } // Elf shouldn't have any other effect so we skip config_gui here +#else + QHtml5File::load("*", [&](const QByteArray &content, const QString &fileName) { + QFileInfo fi(fileName); + QString elf_name = fi.fileName(); + QFile file(elf_name); + file.open(QIODevice::WriteOnly | QIODevice::Truncate); + file.write(content); + file.close(); + ui->elf_file->setText(elf_name); + config->set_elf(elf_name); + }); +#endif } void NewDialog::elf_change(QString val) { diff --git a/qtmips_gui/qhtml5file.h b/qtmips_gui/qhtml5file.h new file mode 100644 index 0000000..dbe6587 --- /dev/null +++ b/qtmips_gui/qhtml5file.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QHTML5FILE_H +#define QHTML5FILE_H + +#include <QtCore/QByteArray> +#include <QtCore/QString> + +#include <functional> + +QT_BEGIN_NAMESPACE + +namespace QHtml5File { + +void load(const QString &accept, std::function<void(const QByteArray &, const QString &)> fileDataReady); +void save(const QByteArray &contents, const QString &fileNameHint); + +} + +QT_END_NAMESPACE + +#endif 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 <qdebug.h> + +#include <emscripten.h> +#include <emscripten/html5.h> + +// +// 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<void(char *, size_t, const char *)> 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<void(char *, size_t, const char *)> 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 + <input type="file"> 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<void(const QByteArray &, const QString &)> 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()); +} diff --git a/qtmips_gui/qtmips_gui.pro b/qtmips_gui/qtmips_gui.pro index 5bedaa1..2e6287d 100644 --- a/qtmips_gui/qtmips_gui.pro +++ b/qtmips_gui/qtmips_gui.pro @@ -117,6 +117,12 @@ HEADERS += \ hinttabledelegate.h \ coreview/minimux.h +wasm: SOURCES += \ + qhtml5file_html5.cpp + +wasm: HEADERS += \ + qhtml5file.h + FORMS += \ NewDialog.ui \ NewDialogCache.ui \ |