Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)

option(SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS "Build unit tests" ON)

find_package(Qt6 6.6 COMPONENTS Quick QuickControls2 REQUIRED)
find_package(Qt6 6.6 COMPONENTS Quick QuickControls2 Widgets REQUIRED)
set(QT_LIBS Qt6::Quick Qt6::QuickControls2 Qt6::Widgets)

if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS)
set(GTEST_DIR thirdparty/googletest)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ When the project loads, click on the green flag button to run it.
## Roadmap

- [x] Loading from URL
- [ ] Loading from a local file (path to the file can be used as a URL until this is implemented)
- [x] Loading from a local file
- [x] Green flag button
- [x] Stop button
- [ ] Turbo mode
Expand Down
2 changes: 2 additions & 0 deletions build/module.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ set(QML_IMPORT_PATH "${QML_IMPORT_PATH};${CMAKE_CURRENT_LIST_DIR}"
FORCE
)

target_link_libraries(${MODULE} PRIVATE ${QT_LIBS})

list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
list(REMOVE_DUPLICATES QML_IMPORT_PATH)
set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE STRING "" FORCE)
Expand Down
3 changes: 1 addition & 2 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ set_target_properties(${APP_TARGET} PROPERTIES
target_compile_definitions(${APP_TARGET}
PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
target_compile_definitions(${APP_TARGET} PRIVATE BUILD_VERSION="${CMAKE_PROJECT_VERSION}")
target_link_libraries(${APP_TARGET}
PRIVATE Qt6::Quick Qt6::QuickControls2)
target_link_libraries(${APP_TARGET} PRIVATE ${QT_LIBS})
target_include_directories(${APP_TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..)
target_include_directories(${APP_TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../global)

Expand Down
4 changes: 2 additions & 2 deletions src/app/app.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QGuiApplication>
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
#include <QIcon>
Expand All @@ -23,7 +23,7 @@ int App::run(int argc, char **argv)
qputenv("QSG_RENDER_LOOP", "basic");

// Set up application object
QGuiApplication app(argc, argv);
QApplication app(argc, argv);
QCoreApplication::setOrganizationDomain("scratchcpp.github.io");
QCoreApplication::setOrganizationName("ScratchCPP");
QCoreApplication::setApplicationName("ScratchCPP");
Expand Down
52 changes: 51 additions & 1 deletion src/app/appmenubar.cpp
Original file line number Diff line number Diff line change
@@ -1,20 +1,70 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QTemporaryFile>

#include "appmenubar.h"
#include "uicomponents/menubarmodel.h"
#include "uicomponents/menumodel.h"
#include "uicomponents/menuitemmodel.h"
#include "uicomponents/filedialog.h"

using namespace scratchcpp;
using namespace scratchcpp::uicomponents;

AppMenuBar::AppMenuBar(QObject *parent) :
QObject(parent),
m_model(new MenuBarModel(this))
m_model(new MenuBarModel(this)),
m_openFileDialog(new FileDialog(this))
{
m_openFileDialog->setShowAllFiles(false);
m_openFileDialog->setNameFilters({ tr("Scratch 3 projects (%1)").arg("*.sb3") });

// File menu
m_fileMenu = new MenuModel(m_model);
m_fileMenu->setTitle(tr("File"));
m_model->addMenu(m_fileMenu);

// File -> Open
m_openFileItem = new MenuItemModel(m_fileMenu);
m_openFileItem->setText(tr("Open..."));
m_fileMenu->addItem(m_openFileItem);

connect(m_openFileItem, &MenuItemModel::clicked, this, &AppMenuBar::openFile);
#ifdef Q_OS_WASM
connect(m_openFileDialog, &FileDialog::fileContentReady, this, &AppMenuBar::loadOpenedFile);
#endif
}

MenuBarModel *AppMenuBar::model() const
{
return m_model;
}

void AppMenuBar::openFile()
{
#ifdef Q_OS_WASM
m_openFileDialog->getOpenFileContent();
#else
QString fileName = m_openFileDialog->getOpenFileName();

if (!fileName.isEmpty())
emit fileOpened(fileName);
#endif
}

#ifdef Q_OS_WASM
void AppMenuBar::loadOpenedFile(const QByteArray &content)
{
if (m_tmpFile)
m_tmpFile->deleteLater();

m_tmpFile = new QTemporaryFile(this);

if (m_tmpFile->open()) {
m_tmpFile->write(content);
m_tmpFile->close();
emit fileOpened(m_tmpFile->fileName());
} else
qWarning("Failed to create temporary file.");
}
#endif
17 changes: 16 additions & 1 deletion src/app/appmenubar.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@

Q_MOC_INCLUDE("uicomponents/menubarmodel.h")

class QTemporaryFile;

namespace scratchcpp
{

namespace uicomponents
{

class MenuBarModel;
class MenuModel;
class MenuItemModel;
class FileDialog;

}
} // namespace uicomponents

class AppMenuBar : public QObject
{
Expand All @@ -30,9 +35,19 @@ class AppMenuBar : public QObject

signals:
void modelChanged();
void fileOpened(const QString &fileName);

private:
void openFile();
#ifdef Q_OS_WASM
void loadOpenedFile(const QByteArray &content);
#endif

uicomponents::MenuBarModel *m_model = nullptr;
uicomponents::MenuModel *m_fileMenu = nullptr;
uicomponents::MenuItemModel *m_openFileItem = nullptr;
uicomponents::FileDialog *m_openFileDialog = nullptr;
QTemporaryFile *m_tmpFile = nullptr;
};

} // namespace scratchcpp
11 changes: 10 additions & 1 deletion src/app/main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ScratchCPP.Render
ApplicationWindow {
id: root
minimumWidth: layout.implicitWidth + layout.anchors.margins * 2
minimumHeight: layout.implicitHeight + layout.anchors.margins * 2
minimumHeight: menuBar.height + layout.implicitHeight + layout.anchors.margins * 2
visible: true
title: "ScratchCPP"
color: Material.background
Expand All @@ -20,6 +20,15 @@ ApplicationWindow {
menuBar: CustomMenuBar {
width: root.width
model: AppMenuBar.model

Connections {
target: AppMenuBar

function onFileOpened(fileName) {
urlField.text = fileName;
player.fileName = fileName;
}
}
}

ColumnLayout {
Expand Down
2 changes: 2 additions & 0 deletions src/uicomponents/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ set(MODULE_SRC
menumodel.h
menuitemmodel.cpp
menuitemmodel.h
filedialog.cpp
filedialog.h
)

include(${PROJECT_SOURCE_DIR}/build/module.cmake)
Expand Down
122 changes: 122 additions & 0 deletions src/uicomponents/filedialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QFileInfo>
#include <QFileDialog>
#include <QStandardPaths>

#include "filedialog.h"

using namespace scratchcpp::uicomponents;

FileDialog::FileDialog(QObject *parent) :
QObject(parent)
{
}

const QStringList &FileDialog::nameFilters(void) const
{
return m_nameFilters;
}

void FileDialog::setNameFilters(const QStringList &filters)
{
m_nameFilters = filters;
emit nameFiltersChanged();
}

bool FileDialog::showAllFiles(void) const
{
return m_showAllFiles;
}

void FileDialog::setShowAllFiles(bool value)
{
m_showAllFiles = value;
emit showAllFilesChanged();
}

const QString &FileDialog::fileName(void) const
{
return m_fileName;
}

QString FileDialog::shortFileName(void) const
{
QFileInfo fileInfo(m_fileName);
return fileInfo.fileName();
}

const QString &FileDialog::defaultSuffix() const
{
return m_defaultSuffix;
}

void FileDialog::setDefaultSuffix(const QString &newDefaultSuffix)
{
if (m_defaultSuffix == newDefaultSuffix)
return;

m_defaultSuffix = newDefaultSuffix;
emit defaultSuffixChanged();
}

void FileDialog::getOpenFileContent(void)
{
auto fileContentReadyLambda = [this](const QString &fileName, const QByteArray &fileContent) {
if (!fileName.isEmpty()) {
m_fileName = fileName;
emit fileNameChanged();
emit shortFileNameChanged();
emit fileContentReady(fileContent);
}
};

#ifdef Q_OS_WASM
QFileDialog::getOpenFileContent(QString(), fileContentReadyLambda);
#else
QString fileName = QFileDialog::getOpenFileName(nullptr, QString(), QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0], getFilters());

if (fileName != "") {
QFile file(fileName);

if (file.open(QIODevice::ReadOnly))
fileContentReadyLambda(fileName, file.readAll());
}
#endif
}

QString FileDialog::getOpenFileName() const
{
QFileDialog dialog(nullptr, QString(), QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0], getFilters());
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setAcceptMode(QFileDialog::AcceptOpen);
dialog.setDefaultSuffix(m_defaultSuffix);

if (dialog.exec() == QDialog::Accepted)
return dialog.selectedFiles().at(0);
else
return "";
}

QString FileDialog::getSaveFileName() const
{
QFileDialog dialog(nullptr, QString(), QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0], getFilters());
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setDefaultSuffix(m_defaultSuffix);

if (dialog.exec() == QDialog::Accepted)
return dialog.selectedFiles().at(0);
else
return "";
}

QString FileDialog::getFilters() const
{
QString filtersStr = m_nameFilters.join(";;");

if (m_showAllFiles)
filtersStr += ";;" + tr("All files") + " (*)";

return filtersStr;
}
56 changes: 56 additions & 0 deletions src/uicomponents/filedialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <QQmlEngine>

namespace scratchcpp::uicomponents
{

class FileDialog : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
Q_PROPERTY(bool showAllFiles READ showAllFiles WRITE setShowAllFiles NOTIFY showAllFilesChanged)
Q_PROPERTY(QString fileName READ fileName NOTIFY fileNameChanged)
Q_PROPERTY(QString shortFileName READ shortFileName NOTIFY shortFileNameChanged)
Q_PROPERTY(QString defaultSuffix READ defaultSuffix WRITE setDefaultSuffix NOTIFY defaultSuffixChanged)

public:
explicit FileDialog(QObject *parent = nullptr);

const QStringList &nameFilters(void) const;
void setNameFilters(const QStringList &filters);

bool showAllFiles(void) const;
void setShowAllFiles(bool value);

const QString &fileName(void) const;
QString shortFileName(void) const;

const QString &defaultSuffix() const;
void setDefaultSuffix(const QString &newDefaultSuffix);

Q_INVOKABLE void getOpenFileContent(void);
Q_INVOKABLE QString getOpenFileName(void) const;
Q_INVOKABLE QString getSaveFileName() const;

private:
QString getFilters() const;

QStringList m_nameFilters;
bool m_showAllFiles = true;
QString m_fileName;
QString m_defaultSuffix;

signals:
void nameFiltersChanged();
void showAllFilesChanged();
void fileNameChanged();
void shortFileNameChanged();
void defaultSuffixChanged();
void fileContentReady(const QByteArray &content);
};

} // namespace scratchcpp::uicomponents
1 change: 1 addition & 0 deletions src/uicomponents/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ set(MODULE_TEST_SRC
menuitemmodel.cpp
menumodel.cpp
menubarmodel.cpp
filedialog.cpp
)

include(${PROJECT_SOURCE_DIR}/build/module_test.cmake)
Loading