mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
project management load/save/new (WIP)
This commit is contained in:
parent
7df8e38b2c
commit
67ded5aace
39 changed files with 10239 additions and 399 deletions
245
README.md
245
README.md
|
|
@ -1,248 +1,47 @@
|
||||||
# Open Story Teller (OST)
|
# Open Story Teller (OST)
|
||||||
|
|
||||||
Open Story Teller is an Open Source project to provide guidelines and software to build your own story teller box.
|
Open Story Teller is an Open Source project to provide guidelines and software to build your own story teller box (electronics, mechanical).
|
||||||
|
|
||||||
The main goal is to *not* make electronics boards but instead buy or reuse existing ones. This will allow you to easily repair your device with spare parts and avoid
|
The main goal is to *not* make electronics boards but instead buy or reuse existing ones. This will allow you to easily repair your device with spare parts.
|
||||||
|
|
||||||
We propose a set of parts and firmware that is working well togather but your are free to custom everything to fit your needs.
|
We propose a set of parts and firmware that is working well togather but your are free to custom everything to fit your needs.
|
||||||
|
|
||||||
*DO NOT CREATE YOUR OWN BOARD, JUST USE EXISTING ONES!*
|
|
||||||
|
|
||||||
This project can be used as a base platform for any device that is composed by:
|
This project can be used as a base platform for any device that is composed by:
|
||||||
- A display (TFT...)
|
- A display (TFT...)
|
||||||
- An Audio output
|
- An Audio output
|
||||||
- A SD card or memory
|
- A SD card or memory
|
||||||
- Some Buttons / rotary encoder / potentiometer
|
- Some Buttons / rotary encoder / potentiometer
|
||||||
|
|
||||||
# Firmware/software
|
# Links
|
||||||
|
|
||||||
The firmware is highly configurable and highly portable. To achieve that, it is split in multiple parts:
|
- http://openstoryteller.org/: main documentation and project news
|
||||||
- The core source file, which is common to every target
|
- http://github.com/arabine/open-story-teller: source code, tickets, help
|
||||||
- the ports, dedicated to an embedded MCU and board
|
|
||||||
- The tests, to easily test part of source on a standard PC
|
|
||||||
- A desktop/mobile implementation
|
|
||||||
|
|
||||||
The core is written in pure C, targets implementations may add other languages and libraries (QML/C++/python ...).
|
# Generic player software
|
||||||
|
|
||||||
## GCC build system
|
The base software is highly portable and includes a micro virtual machine. This allow potential complex stories with loops, branches, user choices, randomization...
|
||||||
|
|
||||||
The project uses CMake as build system. For the embedded targets, the main CMakeLists.txt includes a generic cross compiler file that should be good for many configurations as soon as your compiler is GCC.
|
This project propose an minimal cross-platform GUI player application that can be used as a base project.
|
||||||
|
|
||||||
## Invocation
|
# StoryTeller Editor
|
||||||
|
|
||||||
1. Create a build directory (mkdir build)
|
We propose a basic editor tool to create your own stories. The generated story script runs on our micro virtual machine and allow generate complex stories.
|
||||||
2. Invoke cmake with some options, passed as definitions for CMake (-D<option>)
|
|
||||||
|
|
||||||
| Option | Role |
|

|
||||||
| -------------------- | --------------------------------------------------------- |
|
|
||||||
| TOOLCHAIN | specify the prefix name of the cross GCC binary |
|
|
||||||
| CMAKE_TOOLCHAIN_FILE | Includes before everything else a compiler toolchain file |
|
|
||||||
| CMAKE_BUILD_TYPE | Debug or Release (default ?) |
|
|
||||||
| OST_BUNDLE | Specify the bundle name to build |
|
|
||||||
| TOOLCHAIN_DIR | Specify a directory for the cross-gcc toolchain location |
|
|
||||||
|
|
||||||
|
Work in progress:
|
||||||
|
- Project management
|
||||||
|
- Story pack generation
|
||||||
|
- Basic nodes (media nodes, start and stop)
|
||||||
|
|
||||||
Example: `cmake -DTOOLCHAIN=riscv64-unknown-elf -DCMAKE_TOOLCHAIN_FILE=cmake/cross-gcc.cmake -DCMAKE_BUILD_TYPE=Debug -DOST_BUNDLE=LONGAN_NANO ..`
|
Planned nodes:
|
||||||
|
- Random
|
||||||
|
- Loop
|
||||||
# Hardware bundles
|
- Conditional
|
||||||
|
|
||||||
Here is a list of currently supported bundles. A bundle is a collection of electronics boards that are supported by official firmware builds.
|
|
||||||
|
|
||||||
Keep in mind that the official bundles proposed are not "cost-optimized". There are many rooms of improvements. The best bundles are marked with three stars "***": parts are easy to buy and the cost is minimal.
|
|
||||||
|
|
||||||
The goal of official bundles is to test the firmware on very different kind of hardware to make sure that the core firmware is highly portable.
|
|
||||||
|
|
||||||
The price indicated is purely informative.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Raspberry Pico (Official DevKit!)
|
|
||||||
|
|
||||||
| Category | Brand | Reference | Aproximate Price |
|
|
||||||
| ------------------ | ------------------------------ | --------------------- | ---------------- |
|
|
||||||
| Main CPU board | Rasperry Pico | MKR Zero | 30€ |
|
|
||||||
| Audio | | | 15€ |
|
|
||||||
| Memory storage | SD card slot on board | | - |
|
|
||||||
| Battery management | Included LiPo charger on board | | - |
|
|
||||||
| Display | NewHaven 2.4" TFT | NHD-2.4-240320CF-BSXV | 22€ |
|
|
||||||
|
|
||||||
### Install build tools
|
|
||||||
|
|
||||||
Install build tools, example for a Debian based operating system:
|
|
||||||
|
|
||||||
- sudo apt install gcc-arm-none-eabi
|
|
||||||
- sudo apt install picolibc-arm-none-eabi
|
|
||||||
|
|
||||||
Download the pico SDK somewhere on your disk:
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/raspberrypi/pico-sdk
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### How to build
|
|
||||||
|
|
||||||
Copy past the following command line, execute at the directory root. Replace the PICO_SDK_PATH value with the real location on your disk where you have installed the Pico SDK.
|
|
||||||
|
|
||||||
First, create a CMake build directory:
|
|
||||||
|
|
||||||
```
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
```
|
|
||||||
Then generate the makefile (we use the Pico toolchain here, so there is no specific toolchain file to setup.)
|
|
||||||
|
|
||||||
```
|
|
||||||
cmake -DCMAKE_BUILD_TYPE=Debug -DOST_BUNDLE=RASPI_PICO -DPICO_SDK_PATH=../pico-sdk -DPICO_BOARD=pico_w ..
|
|
||||||
```
|
|
||||||
|
|
||||||
This assume that the Pico SDK is located on the git project root directory. Change this path according to your real Pico SDK location.
|
|
||||||
|
|
||||||
|
|
||||||
## Sipeed Longan Nano (GD32VF103CBT6)
|
|
||||||
|
|
||||||
| Category | Maker | Name | Rounded Price |
|
|
||||||
| ------------------ | ------------------------------------ | ----------- | ------------- |
|
|
||||||
| Main CPU board | Sipeed | Longan Nano | 4€ |
|
|
||||||
| Audio | | | 15€ |
|
|
||||||
| Memory storage | Included SD card slot in Longan Nano | | - |
|
|
||||||
| Battery management | | | 15€ |
|
|
||||||
|
|
||||||
### How to build
|
|
||||||
|
|
||||||
Tools for a Debian based distro
|
|
||||||
|
|
||||||
- sudo apt install crossbuild-essential-riscv64
|
|
||||||
- sudo apt install picolibc-riscv64-unknown-elf
|
|
||||||
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake -DTOOLCHAIN=riscv64-unknown-elf -DCMAKE_TOOLCHAIN_FILE=cmake/cross-gcc.cmake -DCMAKE_BUILD_TYPE=Debug -DOST_BUNDLE=LONGAN_NANO ..
|
|
||||||
|
|
||||||
Convert tools:
|
|
||||||
|
|
||||||
- riscv64-unknown-elf-objcopy -O binary your-file.elf your-file.hex
|
|
||||||
- riscv64-unknown-elf-objcopy -O ihex your-file.elf your-file.hex
|
|
||||||
|
|
||||||
### Wiring
|
|
||||||
|
|
||||||
TBD
|
|
||||||
|
|
||||||
## Arduino MKR Zero (SAMD21G18A)
|
|
||||||
|
|
||||||
| Category | Maker | Name | Rounded Price |
|
|
||||||
| ------------------ | ------------------------------ | --------------------- | ------------- |
|
|
||||||
| Main CPU board | Arduino | MKR Zero | 30€ |
|
|
||||||
| Audio | | | 15€ |
|
|
||||||
| Memory storage | Included SD card slot on board | | - |
|
|
||||||
| Battery management | Included LiPo charger on board | | - |
|
|
||||||
| Display | NewHaven 2.4" TFT | NHD-2.4-240320CF-BSXV | 22€ |
|
|
||||||
|
|
||||||
### How to build
|
|
||||||
|
|
||||||
Install on Ubuntu :
|
|
||||||
- sudo apt install gcc-arm-none-eabi
|
|
||||||
- sudo apt install picolibc-arm-none-eabi
|
|
||||||
|
|
||||||
cmake -DTOOLCHAIN=arm-none-eabi -DCMAKE_TOOLCHAIN_FILE=cmake/cross-gcc.cmake -DCMAKE_BUILD_TYPE=Debug -DOST_BUNDLE=MKR_ZERO ..
|
|
||||||
|
|
||||||
### Wiring
|
|
||||||
|
|
||||||
TBD
|
|
||||||
|
|
||||||
# Mechanical and enclosure
|
|
||||||
|
|
||||||
Use existing enclosures, build your own using wood or 3D printing... we do not propose (yet) any standard package, sorry :(
|
|
||||||
|
|
||||||
# Future targets
|
|
||||||
|
|
||||||
- ESP32 (low cost and high availability)
|
|
||||||
- RP2040
|
|
||||||
|
|
||||||
|
|
||||||
# How to build a custom Open Story Teller box
|
|
||||||
|
|
||||||
## What is a Story Teller box?
|
|
||||||
|
|
||||||
It is a little electronics device that tells stories, mainly for children. A tiny LCD screen is used only to perform choices and create some variants of the stories.
|
|
||||||
|
|
||||||
## Architecture overview
|
|
||||||
|
|
||||||
Basically, it is a portable sound device that plays mp3 and sometimes displays an image. Here is the diagram :
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
The goal of this project is to help you build your own Open Story Teller box with **existing boards**. I do not plan to build any custom board. This is because :
|
|
||||||
1. You can re-use old boards
|
|
||||||
2. You avoid wastes
|
|
||||||
3. It is relatively cheap (only if you reuse spare parts)
|
|
||||||
4. It is repairable
|
|
||||||
|
|
||||||
## Bill of materials
|
|
||||||
|
|
||||||
- A microcontroller unit board
|
|
||||||
- A rechargable battery board
|
|
||||||
- A LCD screen (320x240)
|
|
||||||
- An audio board
|
|
||||||
- Rotary switches, push buttons
|
|
||||||
- A Speaker
|
|
||||||
- A SD-Card connector
|
|
||||||
- Enclosure (wood, 3D printer-based...)
|
|
||||||
- Finally, a suitable firmware
|
|
||||||
|
|
||||||
Depending of your hardware choice, the provided firmware should be updated. The goal of this projet is to support a **selection of boards**.
|
|
||||||
|
|
||||||
# Prototype, code-name OST-1
|
|
||||||
|
|
||||||
## Presentation
|
|
||||||
|
|
||||||
For this first step, we will deliver a working prototype so that we will have a firmware to work on. This step will give us a lot of knownledge for the next step. For now, there is no price optimization.
|
|
||||||
|
|
||||||
Technology choices are :
|
|
||||||
|
|
||||||
| Part | Price | Shop |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| PCM5102 Audio board | 4 € | Aliexpress |
|
|
||||||
| PAM8302 Mono Amplifier | 9 € | Adafruit |
|
|
||||||
| Longan Nano RISC-V board with SD-Card port | 4 € | Aliexpress |
|
|
||||||
| 3.2" SPI TFT Screen (320x240) with ILI9341 driver | 9 € | Aliexpress |
|
|
||||||
| Adafruit PowerBoost 500 charger | 15 € | Adafruit |
|
|
||||||
| Some Pimoroni buttons are rotary switches | 4 € | Pimoroni |
|
|
||||||
| Speaker | 4 € | Pimoroni |
|
|
||||||
| LiPo battery 500mAh | 9 € | Any |
|
|
||||||
| **TOTAL** | **58 €** |
|
|
||||||
|
|
||||||
/!\ Achtung /!\ We can easily lower some prices, especially if your MCU controller already contains a battery connector. Remember, this first step is to test various parts.
|
|
||||||
|
|
||||||
## What does it look like
|
|
||||||
|
|
||||||
The firmware is still under construction. Everything is tested on breadboard.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
What is working:
|
|
||||||
- The audio path
|
|
||||||
- The SD Card
|
|
||||||
- Roughly: playing a wav file from the SD Card
|
|
||||||
|
|
||||||
## Audio path
|
|
||||||
|
|
||||||
An I2S DAC controller with a jack output :
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
An audio amplifier from Adafruit (2.5W, can drive a speaker between 3 ohms and 8 ohms).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
A speaker :
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Anthony Rabine
|
||||||
|
|
||||||
|
|
|
||||||
234
docs/index.md
234
docs/index.md
|
|
@ -23,3 +23,237 @@ features:
|
||||||
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
# Firmware/software
|
||||||
|
|
||||||
|
The firmware is highly configurable and highly portable. To achieve that, it is split in multiple parts:
|
||||||
|
- The core source file, which is common to every target
|
||||||
|
- the ports, dedicated to an embedded MCU and board
|
||||||
|
- The tests, to easily test part of source on a standard PC
|
||||||
|
- A desktop/mobile implementation
|
||||||
|
|
||||||
|
The core is written in pure C, targets implementations may add other languages and libraries (QML/C++/python ...).
|
||||||
|
|
||||||
|
## GCC build system
|
||||||
|
|
||||||
|
The project uses CMake as build system. For the embedded targets, the main CMakeLists.txt includes a generic cross compiler file that should be good for many configurations as soon as your compiler is GCC.
|
||||||
|
|
||||||
|
## Invocation
|
||||||
|
|
||||||
|
1. Create a build directory (mkdir build)
|
||||||
|
2. Invoke cmake with some options, passed as definitions for CMake (-D<option>)
|
||||||
|
|
||||||
|
| Option | Role |
|
||||||
|
| -------------------- | --------------------------------------------------------- |
|
||||||
|
| TOOLCHAIN | specify the prefix name of the cross GCC binary |
|
||||||
|
| CMAKE_TOOLCHAIN_FILE | Includes before everything else a compiler toolchain file |
|
||||||
|
| CMAKE_BUILD_TYPE | Debug or Release (default ?) |
|
||||||
|
| OST_BUNDLE | Specify the bundle name to build |
|
||||||
|
| TOOLCHAIN_DIR | Specify a directory for the cross-gcc toolchain location |
|
||||||
|
|
||||||
|
|
||||||
|
Example: `cmake -DTOOLCHAIN=riscv64-unknown-elf -DCMAKE_TOOLCHAIN_FILE=cmake/cross-gcc.cmake -DCMAKE_BUILD_TYPE=Debug -DOST_BUNDLE=LONGAN_NANO ..`
|
||||||
|
|
||||||
|
|
||||||
|
# Hardware bundles
|
||||||
|
|
||||||
|
Here is a list of currently supported bundles. A bundle is a collection of electronics boards that are supported by official firmware builds.
|
||||||
|
|
||||||
|
Keep in mind that the official bundles proposed are not "cost-optimized". There are many rooms of improvements. The best bundles are marked with three stars "***": parts are easy to buy and the cost is minimal.
|
||||||
|
|
||||||
|
The goal of official bundles is to test the firmware on very different kind of hardware to make sure that the core firmware is highly portable.
|
||||||
|
|
||||||
|
The price indicated is purely informative.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Raspberry Pico (Official DevKit!)
|
||||||
|
|
||||||
|
| Category | Brand | Reference | Aproximate Price |
|
||||||
|
| ------------------ | ------------------------------ | --------------------- | ---------------- |
|
||||||
|
| Main CPU board | Rasperry Pico | MKR Zero | 30€ |
|
||||||
|
| Audio | | | 15€ |
|
||||||
|
| Memory storage | SD card slot on board | | - |
|
||||||
|
| Battery management | Included LiPo charger on board | | - |
|
||||||
|
| Display | NewHaven 2.4" TFT | NHD-2.4-240320CF-BSXV | 22€ |
|
||||||
|
|
||||||
|
### Install build tools
|
||||||
|
|
||||||
|
Install build tools, example for a Debian based operating system:
|
||||||
|
|
||||||
|
- sudo apt install gcc-arm-none-eabi
|
||||||
|
- sudo apt install picolibc-arm-none-eabi
|
||||||
|
|
||||||
|
Download the pico SDK somewhere on your disk:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/raspberrypi/pico-sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### How to build
|
||||||
|
|
||||||
|
Copy past the following command line, execute at the directory root. Replace the PICO_SDK_PATH value with the real location on your disk where you have installed the Pico SDK.
|
||||||
|
|
||||||
|
First, create a CMake build directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
```
|
||||||
|
Then generate the makefile (we use the Pico toolchain here, so there is no specific toolchain file to setup.)
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Debug -DOST_BUNDLE=RASPI_PICO -DPICO_SDK_PATH=../pico-sdk -DPICO_BOARD=pico_w ..
|
||||||
|
```
|
||||||
|
|
||||||
|
This assume that the Pico SDK is located on the git project root directory. Change this path according to your real Pico SDK location.
|
||||||
|
|
||||||
|
|
||||||
|
## Sipeed Longan Nano (GD32VF103CBT6)
|
||||||
|
|
||||||
|
| Category | Maker | Name | Rounded Price |
|
||||||
|
| ------------------ | ------------------------------------ | ----------- | ------------- |
|
||||||
|
| Main CPU board | Sipeed | Longan Nano | 4€ |
|
||||||
|
| Audio | | | 15€ |
|
||||||
|
| Memory storage | Included SD card slot in Longan Nano | | - |
|
||||||
|
| Battery management | | | 15€ |
|
||||||
|
|
||||||
|
### How to build
|
||||||
|
|
||||||
|
Tools for a Debian based distro
|
||||||
|
|
||||||
|
- sudo apt install crossbuild-essential-riscv64
|
||||||
|
- sudo apt install picolibc-riscv64-unknown-elf
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -DTOOLCHAIN=riscv64-unknown-elf -DCMAKE_TOOLCHAIN_FILE=cmake/cross-gcc.cmake -DCMAKE_BUILD_TYPE=Debug -DOST_BUNDLE=LONGAN_NANO ..
|
||||||
|
|
||||||
|
Convert tools:
|
||||||
|
|
||||||
|
- riscv64-unknown-elf-objcopy -O binary your-file.elf your-file.hex
|
||||||
|
- riscv64-unknown-elf-objcopy -O ihex your-file.elf your-file.hex
|
||||||
|
|
||||||
|
### Wiring
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
## Arduino MKR Zero (SAMD21G18A)
|
||||||
|
|
||||||
|
| Category | Maker | Name | Rounded Price |
|
||||||
|
| ------------------ | ------------------------------ | --------------------- | ------------- |
|
||||||
|
| Main CPU board | Arduino | MKR Zero | 30€ |
|
||||||
|
| Audio | | | 15€ |
|
||||||
|
| Memory storage | Included SD card slot on board | | - |
|
||||||
|
| Battery management | Included LiPo charger on board | | - |
|
||||||
|
| Display | NewHaven 2.4" TFT | NHD-2.4-240320CF-BSXV | 22€ |
|
||||||
|
|
||||||
|
### How to build
|
||||||
|
|
||||||
|
Install on Ubuntu :
|
||||||
|
- sudo apt install gcc-arm-none-eabi
|
||||||
|
- sudo apt install picolibc-arm-none-eabi
|
||||||
|
|
||||||
|
cmake -DTOOLCHAIN=arm-none-eabi -DCMAKE_TOOLCHAIN_FILE=cmake/cross-gcc.cmake -DCMAKE_BUILD_TYPE=Debug -DOST_BUNDLE=MKR_ZERO ..
|
||||||
|
|
||||||
|
### Wiring
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
# Mechanical and enclosure
|
||||||
|
|
||||||
|
Use existing enclosures, build your own using wood or 3D printing... we do not propose (yet) any standard package, sorry :(
|
||||||
|
|
||||||
|
# Future targets
|
||||||
|
|
||||||
|
- ESP32 (low cost and high availability)
|
||||||
|
- RP2040
|
||||||
|
|
||||||
|
|
||||||
|
# How to build a custom Open Story Teller box
|
||||||
|
|
||||||
|
## What is a Story Teller box?
|
||||||
|
|
||||||
|
It is a little electronics device that tells stories, mainly for children. A tiny LCD screen is used only to perform choices and create some variants of the stories.
|
||||||
|
|
||||||
|
## Architecture overview
|
||||||
|
|
||||||
|
Basically, it is a portable sound device that plays mp3 and sometimes displays an image. Here is the diagram :
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
The goal of this project is to help you build your own Open Story Teller box with **existing boards**. I do not plan to build any custom board. This is because :
|
||||||
|
1. You can re-use old boards
|
||||||
|
2. You avoid wastes
|
||||||
|
3. It is relatively cheap (only if you reuse spare parts)
|
||||||
|
4. It is repairable
|
||||||
|
|
||||||
|
## Bill of materials
|
||||||
|
|
||||||
|
- A microcontroller unit board
|
||||||
|
- A rechargable battery board
|
||||||
|
- A LCD screen (320x240)
|
||||||
|
- An audio board
|
||||||
|
- Rotary switches, push buttons
|
||||||
|
- A Speaker
|
||||||
|
- A SD-Card connector
|
||||||
|
- Enclosure (wood, 3D printer-based...)
|
||||||
|
- Finally, a suitable firmware
|
||||||
|
|
||||||
|
Depending of your hardware choice, the provided firmware should be updated. The goal of this projet is to support a **selection of boards**.
|
||||||
|
|
||||||
|
# Prototype, code-name OST-1
|
||||||
|
|
||||||
|
## Presentation
|
||||||
|
|
||||||
|
For this first step, we will deliver a working prototype so that we will have a firmware to work on. This step will give us a lot of knownledge for the next step. For now, there is no price optimization.
|
||||||
|
|
||||||
|
Technology choices are :
|
||||||
|
|
||||||
|
| Part | Price | Shop |
|
||||||
|
| ------------------------------------------------- | -------- | ---------- |
|
||||||
|
| PCM5102 Audio board | 4 € | Aliexpress |
|
||||||
|
| PAM8302 Mono Amplifier | 9 € | Adafruit |
|
||||||
|
| Longan Nano RISC-V board with SD-Card port | 4 € | Aliexpress |
|
||||||
|
| 3.2" SPI TFT Screen (320x240) with ILI9341 driver | 9 € | Aliexpress |
|
||||||
|
| Adafruit PowerBoost 500 charger | 15 € | Adafruit |
|
||||||
|
| Some Pimoroni buttons are rotary switches | 4 € | Pimoroni |
|
||||||
|
| Speaker | 4 € | Pimoroni |
|
||||||
|
| LiPo battery 500mAh | 9 € | Any |
|
||||||
|
| **TOTAL** | **58 €** |
|
||||||
|
|
||||||
|
/!\ Achtung /!\ We can easily lower some prices, especially if your MCU controller already contains a battery connector. Remember, this first step is to test various parts.
|
||||||
|
|
||||||
|
## What does it look like
|
||||||
|
|
||||||
|
The firmware is still under construction. Everything is tested on breadboard.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
What is working:
|
||||||
|
- The audio path
|
||||||
|
- The SD Card
|
||||||
|
- Roughly: playing a wav file from the SD Card
|
||||||
|
|
||||||
|
## Audio path
|
||||||
|
|
||||||
|
An I2S DAC controller with a jack output :
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
An audio amplifier from Adafruit (2.5W, can drive a speaker between 3 ohms and 8 ohms).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A speaker :
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
BIN
images/story_editor_preview.png
Normal file
BIN
images/story_editor_preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
|
|
@ -23,6 +23,7 @@ set(PROJECT_SOURCES
|
||||||
src/toolbar.cpp
|
src/toolbar.cpp
|
||||||
src/ost-editor.qrc
|
src/ost-editor.qrc
|
||||||
src/story_project.cpp
|
src/story_project.cpp
|
||||||
|
src/story_project.h
|
||||||
src/media_node_model.h
|
src/media_node_model.h
|
||||||
src/media_node_model.cpp
|
src/media_node_model.cpp
|
||||||
src/story_graph_model.cpp
|
src/story_graph_model.cpp
|
||||||
|
|
@ -47,6 +48,8 @@ set(PROJECT_SOURCES
|
||||||
src/log_dock.cpp
|
src/log_dock.cpp
|
||||||
src/vm_dock.h
|
src/vm_dock.h
|
||||||
src/vm_dock.cpp
|
src/vm_dock.cpp
|
||||||
|
src/dock_widget_base.h
|
||||||
|
src/dock_widget_base.cpp
|
||||||
src/code_editor.h
|
src/code_editor.h
|
||||||
src/code_editor.cpp
|
src/code_editor.cpp
|
||||||
src/graph.h
|
src/graph.h
|
||||||
|
|
@ -55,6 +58,9 @@ set(PROJECT_SOURCES
|
||||||
src/event_node_model.cpp
|
src/event_node_model.cpp
|
||||||
src/highlighter.h
|
src/highlighter.h
|
||||||
src/highlighter.cpp
|
src/highlighter.cpp
|
||||||
|
src/zip.cpp
|
||||||
|
src/zip.h
|
||||||
|
src/miniz.c
|
||||||
src/ost-hmi.ui
|
src/ost-hmi.ui
|
||||||
src/ost-vm.ui
|
src/ost-vm.ui
|
||||||
src/ost-data.ui
|
src/ost-data.ui
|
||||||
|
|
@ -64,6 +70,7 @@ set(PROJECT_SOURCES
|
||||||
src/media-node.ui
|
src/media-node.ui
|
||||||
src/event-node.ui
|
src/event-node.ui
|
||||||
src/choose-file.ui
|
src/choose-file.ui
|
||||||
|
src/new-project.ui
|
||||||
../software/chip32/chip32_assembler.cpp
|
../software/chip32/chip32_assembler.cpp
|
||||||
../software/chip32/chip32_vm.c
|
../software/chip32/chip32_vm.c
|
||||||
)
|
)
|
||||||
|
|
|
||||||
BIN
story-editor/assets/file-document-plus-outline.png
Normal file
BIN
story-editor/assets/file-document-plus-outline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
39
story-editor/assets/file-document-plus-outline.svg
Normal file
39
story-editor/assets/file-document-plus-outline.svg
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
version="1.1"
|
||||||
|
id="svg4"
|
||||||
|
sodipodi:docname="file-document-plus-outline.svg"
|
||||||
|
inkscape:export-filename="file-document-plus-outline.png"
|
||||||
|
inkscape:export-xdpi="599.172"
|
||||||
|
inkscape:export-ydpi="599.172"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs8" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview6"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="35.06856"
|
||||||
|
inkscape:cx="4.6908114"
|
||||||
|
inkscape:cy="10.707597"
|
||||||
|
inkscape:window-width="2112"
|
||||||
|
inkscape:window-height="1436"
|
||||||
|
inkscape:window-x="1352"
|
||||||
|
inkscape:window-y="380"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg4" />
|
||||||
|
<path
|
||||||
|
d="M23 18H20V15H18V18H15V20H18V23H20V20H23M6 2C4.89 2 4 2.9 4 4V20C4 21.11 4.89 22 6 22H13.81C13.45 21.38 13.2 20.7 13.08 20H6V4H13V9H18V13.08C18.33 13.03 18.67 13 19 13C19.34 13 19.67 13.03 20 13.08V8L14 2M8 12V14H16V12M8 16V18H13V16Z"
|
||||||
|
id="path2" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
25
story-editor/src/dock_widget_base.cpp
Normal file
25
story-editor/src/dock_widget_base.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include "dock_widget_base.h"
|
||||||
|
|
||||||
|
|
||||||
|
EventFilter::EventFilter( QObject* aParent )
|
||||||
|
: QObject( aParent )
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EventFilter::eventFilter( QObject *obj, QEvent *event )
|
||||||
|
{
|
||||||
|
if ( event->type() == QEvent::Close )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return QObject::eventFilter( obj, event );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DockWidgetBase::DockWidgetBase(const QString &title)
|
||||||
|
: QDockWidget(title)
|
||||||
|
{
|
||||||
|
EventFilter* filter = new EventFilter( this );
|
||||||
|
installEventFilter( filter );
|
||||||
|
}
|
||||||
23
story-editor/src/dock_widget_base.h
Normal file
23
story-editor/src/dock_widget_base.h
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#ifndef DOCK_WIDGET_BASE_H
|
||||||
|
#define DOCK_WIDGET_BASE_H
|
||||||
|
|
||||||
|
#include <QDockWidget>
|
||||||
|
#include <QEvent>
|
||||||
|
|
||||||
|
class EventFilter : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
EventFilter( QObject* parent );
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DockWidgetBase : public QDockWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
DockWidgetBase(const QString &title);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DOCK_WIDGET_BASE_H
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#include "log_dock.h"
|
#include "log_dock.h"
|
||||||
|
|
||||||
LogDock::LogDock()
|
LogDock::LogDock()
|
||||||
: QDockWidget(tr("Logs"))
|
: DockWidgetBase(tr("Logs"))
|
||||||
{
|
{
|
||||||
setObjectName("OstHmiDock"); // used to save the state
|
setObjectName("OstHmiDock"); // used to save the state
|
||||||
m_logUi.setupUi(this);
|
m_logUi.setupUi(this);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
#ifndef LOGDOCK_H
|
#ifndef LOGDOCK_H
|
||||||
#define LOGDOCK_H
|
#define LOGDOCK_H
|
||||||
|
|
||||||
#include <QDockWidget>
|
#include "dock_widget_base.h"
|
||||||
#include "ui_ost-log.h"
|
#include "ui_ost-log.h"
|
||||||
|
|
||||||
class LogDock : public QDockWidget
|
class LogDock : public DockWidgetBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LogDock();
|
LogDock();
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QDir>
|
||||||
|
|
||||||
#include <QtNodes/BasicGraphicsScene>
|
#include <QtNodes/BasicGraphicsScene>
|
||||||
#include <QtNodes/ConnectionStyle>
|
#include <QtNodes/ConnectionStyle>
|
||||||
|
|
@ -49,6 +51,10 @@ MainWindow::MainWindow()
|
||||||
: m_model(m_project)
|
: m_model(m_project)
|
||||||
, m_scene(m_model)
|
, m_scene(m_model)
|
||||||
{
|
{
|
||||||
|
SetupTemporaryProject();
|
||||||
|
|
||||||
|
RefreshProjectInformation();
|
||||||
|
|
||||||
Callback<void(QtMsgType , const QMessageLogContext &, const QString &)>::func = std::bind(&MainWindow::MessageOutput, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
Callback<void(QtMsgType , const QMessageLogContext &, const QString &)>::func = std::bind(&MainWindow::MessageOutput, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||||
auto cb = static_cast<message_output_t>(Callback<void(QtMsgType , const QMessageLogContext &, const QString &)>::callback);
|
auto cb = static_cast<message_output_t>(Callback<void(QtMsgType , const QMessageLogContext &, const QString &)>::callback);
|
||||||
|
|
||||||
|
|
@ -63,39 +69,36 @@ MainWindow::MainWindow()
|
||||||
|
|
||||||
m_logDock = new LogDock();
|
m_logDock = new LogDock();
|
||||||
addDockWidget(Qt::DockWidgetArea::BottomDockWidgetArea, m_logDock);
|
addDockWidget(Qt::DockWidgetArea::BottomDockWidgetArea, m_logDock);
|
||||||
|
m_toolbar->AddDockToMenu(m_logDock->toggleViewAction());
|
||||||
|
|
||||||
m_nodeEditorDock = new NodeEditorDock(&m_scene);
|
m_nodeEditorDock = new NodeEditorDock(&m_scene);
|
||||||
addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, m_nodeEditorDock);
|
addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, m_nodeEditorDock);
|
||||||
m_toolbar->addAction(m_nodeEditorDock->toggleViewAction());
|
m_toolbar->AddDockToMenu(m_nodeEditorDock->toggleViewAction());
|
||||||
|
|
||||||
m_ostHmiDock = new OstHmiDock();
|
m_ostHmiDock = new OstHmiDock();
|
||||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ostHmiDock);
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ostHmiDock);
|
||||||
|
m_toolbar->AddDockToMenu(m_ostHmiDock->toggleViewAction());
|
||||||
|
|
||||||
connect(m_ostHmiDock, &OstHmiDock::sigOkButton, [=]() {
|
connect(m_ostHmiDock, &OstHmiDock::sigOkButton, [=]() {
|
||||||
QCoreApplication::postEvent(this, new VmEvent(VmEvent::evOkButton));
|
QCoreApplication::postEvent(this, new VmEvent(VmEvent::evOkButton));
|
||||||
});
|
});
|
||||||
|
|
||||||
m_resourcesDock = new ResourcesDock();
|
m_resourcesDock = new ResourcesDock(m_project);
|
||||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_resourcesDock);
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_resourcesDock);
|
||||||
|
m_toolbar->AddDockToMenu(m_resourcesDock->toggleViewAction());
|
||||||
|
|
||||||
m_scriptEditorDock = new ScriptEditorDock();
|
m_scriptEditorDock = new ScriptEditorDock();
|
||||||
addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, m_scriptEditorDock);
|
addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, m_scriptEditorDock);
|
||||||
|
m_toolbar->AddDockToMenu(m_scriptEditorDock->toggleViewAction());
|
||||||
|
|
||||||
m_vmDock = new VmDock(m_assembler);
|
m_vmDock = new VmDock(m_assembler);
|
||||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_vmDock);
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_vmDock);
|
||||||
|
m_toolbar->AddDockToMenu(m_vmDock->toggleViewAction());
|
||||||
|
|
||||||
connect(m_vmDock, &VmDock::sigCompile, [=]() {
|
connect(m_vmDock, &VmDock::sigCompile, [=]() {
|
||||||
|
|
||||||
m_project.Clear();
|
|
||||||
|
|
||||||
// Add Resources to project
|
|
||||||
QList<Resource> res = m_resourcesDock->GetResources();
|
|
||||||
|
|
||||||
for (auto & r : res)
|
|
||||||
{
|
|
||||||
m_project.m_images.push_back(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
m_resourcesDock->SaveToProject();
|
||||||
m_scriptEditorDock->setScript(m_project.Compile());
|
m_scriptEditorDock->setScript(m_project.Compile());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -109,8 +112,11 @@ MainWindow::MainWindow()
|
||||||
|
|
||||||
m_ramView = new MemoryViewDock("RamViewDock", "RAM");
|
m_ramView = new MemoryViewDock("RamViewDock", "RAM");
|
||||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ramView);
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ramView);
|
||||||
|
m_toolbar->AddDockToMenu(m_ramView->toggleViewAction());
|
||||||
|
|
||||||
m_romView = new MemoryViewDock("RomViewDock", "ROM");
|
m_romView = new MemoryViewDock("RomViewDock", "ROM");
|
||||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_romView);
|
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_romView);
|
||||||
|
m_toolbar->AddDockToMenu(m_romView->toggleViewAction());
|
||||||
|
|
||||||
m_chooseFileDialog = new QDialog(this);
|
m_chooseFileDialog = new QDialog(this);
|
||||||
m_chooseFileUi.setupUi(m_chooseFileDialog);
|
m_chooseFileUi.setupUi(m_chooseFileDialog);
|
||||||
|
|
@ -160,9 +166,41 @@ MainWindow::MainWindow()
|
||||||
|
|
||||||
readSettings();
|
readSettings();
|
||||||
|
|
||||||
|
connect(m_toolbar, &ToolBar::sigNew, this, [=]() {
|
||||||
|
NewProject();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_toolbar, &ToolBar::sigSave, this, [=]() {
|
||||||
|
SaveProject();
|
||||||
|
});
|
||||||
|
|
||||||
qDebug() << "Started StoryTeller Editor";
|
qDebug() << "Started StoryTeller Editor";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::CloseProject()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::SetupTemporaryProject()
|
||||||
|
{
|
||||||
|
QString appDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
|
||||||
|
// Generate a project unique ID name
|
||||||
|
m_project.uuid = QUuid::createUuid().toString().toStdString();
|
||||||
|
m_project.working_dir = QString(appDir + QDir::separator() + m_project.uuid.c_str()).toStdString();
|
||||||
|
m_project.Initialize();
|
||||||
|
|
||||||
|
// m_resourcesDock->Initialize();
|
||||||
|
|
||||||
|
qDebug() << "Working dir is: " << m_project.working_dir.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::RefreshProjectInformation()
|
||||||
|
{
|
||||||
|
setWindowTitle(QString("StoryTeller Editor - ") + m_project.working_dir.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
void MainWindow::MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||||
{
|
{
|
||||||
m_logDock->Append(type, context, msg);
|
m_logDock->Append(type, context, msg);
|
||||||
|
|
@ -189,7 +227,7 @@ void MainWindow::DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId)
|
||||||
|
|
||||||
if (m_tree->image >= 0)
|
if (m_tree->image >= 0)
|
||||||
{
|
{
|
||||||
std::string imageFile = m_project.m_working_dir + "/rf/" + m_project.m_images[m_tree->image].file + ".bmp";
|
std::string imageFile = m_project.working_dir + "/rf/" + m_project.m_images[m_tree->image].file + ".bmp";
|
||||||
std::cout << "Node image: " << imageFile << std::endl;
|
std::cout << "Node image: " << imageFile << std::endl;
|
||||||
nodeInternalData["image"] = imageFile.c_str();
|
nodeInternalData["image"] = imageFile.c_str();
|
||||||
}
|
}
|
||||||
|
|
@ -391,6 +429,12 @@ void MainWindow::createStatusBar()
|
||||||
statusBar()->showMessage(tr("Ready"));
|
statusBar()->showMessage(tr("Ready"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::NewProject()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void MainWindow::open()
|
void MainWindow::open()
|
||||||
{
|
{
|
||||||
QMimeDatabase mimeDatabase;
|
QMimeDatabase mimeDatabase;
|
||||||
|
|
@ -420,32 +464,25 @@ void MainWindow::open()
|
||||||
QGuiApplication::restoreOverrideCursor();
|
QGuiApplication::restoreOverrideCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::save()
|
void MainWindow::SaveProject()
|
||||||
{
|
{
|
||||||
QMimeDatabase mimeDatabase;
|
// Open the dialog if the project file does not exists
|
||||||
QString fileName = QFileDialog::getSaveFileName(this,
|
if (!QFile::exists(m_project.file_path.c_str()))
|
||||||
tr("Choose a file name"), ".",
|
{
|
||||||
mimeDatabase.mimeTypeForName("application/json").filterString());
|
// Save current project
|
||||||
if (fileName.isEmpty())
|
QString fileName = QFileDialog::getSaveFileName(this, tr("Save project file"),
|
||||||
return;
|
QDir::homePath() + "/new_story.ostproj",
|
||||||
QFile file(fileName);
|
tr("OpenStory Teller project (*.ostproj)"));
|
||||||
if (!file.open(QFile::WriteOnly | QFile::Text)) {
|
if (fileName.isEmpty())
|
||||||
QMessageBox::warning(this, tr("Dock Widgets"),
|
return;
|
||||||
tr("Cannot write file %1:\n%2.")
|
m_project.file_path = fileName.toStdString();
|
||||||
.arg(QDir::toNativeSeparators(fileName), file.errorString()));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QTextStream out(&file);
|
|
||||||
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
|
|
||||||
|
|
||||||
QJsonObject saveData = m_model.save();
|
QJsonObject saveData = m_model.save();
|
||||||
QJsonDocument doc(saveData);
|
QJsonDocument doc(saveData);
|
||||||
out << doc.toJson();
|
qDebug() << doc.toJson();
|
||||||
|
|
||||||
QGuiApplication::restoreOverrideCursor();
|
statusBar()->showMessage(tr("Saved '%1'").arg(m_project.file_path.c_str()), 2000);
|
||||||
|
|
||||||
statusBar()->showMessage(tr("Saved '%1'").arg(fileName), 2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::about()
|
void MainWindow::about()
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ private:
|
||||||
// Private functions
|
// Private functions
|
||||||
void createActions();
|
void createActions();
|
||||||
void createStatusBar();
|
void createStatusBar();
|
||||||
void save();
|
void SaveProject();
|
||||||
void DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId);
|
void DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId);
|
||||||
void about();
|
void about();
|
||||||
void open();
|
void open();
|
||||||
|
|
@ -165,6 +165,11 @@ private:
|
||||||
|
|
||||||
bool event(QEvent *event);
|
bool event(QEvent *event);
|
||||||
void MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
|
void MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
|
||||||
|
void NewProject();
|
||||||
|
|
||||||
|
void SetupTemporaryProject();
|
||||||
|
void RefreshProjectInformation();
|
||||||
|
void CloseProject();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MAIN_WINDOW_H
|
#endif // MAIN_WINDOW_H
|
||||||
|
|
|
||||||
|
|
@ -53,17 +53,16 @@ QJsonObject MediaNodeModel::save() const
|
||||||
|
|
||||||
// Merge two objects
|
// Merge two objects
|
||||||
QVariantMap map = obj.toVariantMap();
|
QVariantMap map = obj.toVariantMap();
|
||||||
map.insert(m_mediaData.toVariantMap());
|
map.insert(m_mediaData);
|
||||||
|
|
||||||
return QJsonObject::fromVariantMap(map);
|
return QJsonObject::fromVariantMap(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaNodeModel::load(const QJsonObject &mediaData)
|
void MediaNodeModel::load(const QJsonObject &mediaData)
|
||||||
{
|
{
|
||||||
m_mediaData = mediaData;
|
m_mediaData = mediaData.toVariantMap();
|
||||||
|
|
||||||
// Display loaded image
|
// Display loaded image
|
||||||
|
|
||||||
QString imagePath = m_mediaData["image"].toString();
|
QString imagePath = m_mediaData["image"].toString();
|
||||||
|
|
||||||
if (!imagePath.isEmpty())
|
if (!imagePath.isEmpty())
|
||||||
|
|
@ -93,6 +92,9 @@ void MediaNodeModel::setInternalData(const QVariant &value)
|
||||||
if (obj.contains("image")) {
|
if (obj.contains("image")) {
|
||||||
setImage(obj.value("image").toString());
|
setImage(obj.value("image").toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge new data into local object
|
||||||
|
m_mediaData.insert(obj.toVariantMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int MediaNodeModel::nPorts(PortType portType) const
|
unsigned int MediaNodeModel::nPorts(PortType portType) const
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,6 @@ private:
|
||||||
Ui::mediaNodeUi m_ui;
|
Ui::mediaNodeUi m_ui;
|
||||||
std::shared_ptr<NodeData> m_nodeData;
|
std::shared_ptr<NodeData> m_nodeData;
|
||||||
|
|
||||||
QJsonObject m_mediaData;
|
QVariantMap m_mediaData;
|
||||||
void setImage(const QString &fileName);
|
void setImage(const QString &fileName);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
#include "model/buffer/qmemorybuffer.h"
|
#include "model/buffer/qmemorybuffer.h"
|
||||||
|
|
||||||
MemoryViewDock::MemoryViewDock(const QString &objectName, const QString &title)
|
MemoryViewDock::MemoryViewDock(const QString &objectName, const QString &title)
|
||||||
: QDockWidget(title)
|
: DockWidgetBase(title)
|
||||||
{
|
{
|
||||||
setObjectName(objectName);
|
setObjectName(objectName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
#ifndef MEMORY_VIEWDOCK_H
|
#ifndef MEMORY_VIEWDOCK_H
|
||||||
#define MEMORY_VIEWDOCK_H
|
#define MEMORY_VIEWDOCK_H
|
||||||
|
|
||||||
#include <QDockWidget>
|
#include "dock_widget_base.h"
|
||||||
#include <qhexview.h>
|
#include <qhexview.h>
|
||||||
|
|
||||||
class MemoryViewDock : public QDockWidget
|
class MemoryViewDock : public DockWidgetBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MemoryViewDock(const QString &objectName, const QString &title);
|
MemoryViewDock(const QString &objectName, const QString &title);
|
||||||
|
|
|
||||||
7736
story-editor/src/miniz.c
Normal file
7736
story-editor/src/miniz.c
Normal file
File diff suppressed because it is too large
Load diff
1350
story-editor/src/miniz.h
Normal file
1350
story-editor/src/miniz.h
Normal file
File diff suppressed because it is too large
Load diff
193
story-editor/src/new-project.ui
Normal file
193
story-editor/src/new-project.ui
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>NewProjectDialog</class>
|
||||||
|
<widget class="QDialog" name="NewProjectDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>346</width>
|
||||||
|
<height>263</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>New project parameters</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Directory:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>25</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>25</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Project name:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit_2"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Size of display screen:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>320x240</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Image format:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_2">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>BMP (compressed 4-bit palette)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Sound format</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_3">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>QOA (Quite Ok Audio)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>18</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>NewProjectDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>NewProjectDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
#include <QOpenGLWidget>
|
#include <QOpenGLWidget>
|
||||||
|
|
||||||
NodeEditorDock::NodeEditorDock(BasicGraphicsScene *scene)
|
NodeEditorDock::NodeEditorDock(BasicGraphicsScene *scene)
|
||||||
: QDockWidget(tr("Node editor"))
|
: DockWidgetBase(tr("Node editor"))
|
||||||
{
|
{
|
||||||
setObjectName("NodeEditorDock");
|
setObjectName("NodeEditorDock");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
#ifndef NODEEDITORDOCK_H
|
#ifndef NODEEDITORDOCK_H
|
||||||
#define NODEEDITORDOCK_H
|
#define NODEEDITORDOCK_H
|
||||||
|
|
||||||
#include <QDockWidget>
|
#include "dock_widget_base.h"
|
||||||
#include <QtNodes/GraphicsView>
|
#include <QtNodes/GraphicsView>
|
||||||
#include <QtNodes/BasicGraphicsScene>
|
#include <QtNodes/BasicGraphicsScene>
|
||||||
using QtNodes::BasicGraphicsScene;
|
using QtNodes::BasicGraphicsScene;
|
||||||
using QtNodes::GraphicsView;
|
using QtNodes::GraphicsView;
|
||||||
|
|
||||||
class NodeEditorDock : public QDockWidget
|
|
||||||
|
class NodeEditorDock : public DockWidgetBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NodeEditorDock(BasicGraphicsScene *scene);
|
NodeEditorDock(BasicGraphicsScene *scene);
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,6 @@
|
||||||
<file>../assets/play-circle.png</file>
|
<file>../assets/play-circle.png</file>
|
||||||
<file>../assets/build.png</file>
|
<file>../assets/build.png</file>
|
||||||
<file>../assets/volume-off.png</file>
|
<file>../assets/volume-off.png</file>
|
||||||
|
<file>../assets/file-document-plus-outline.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
|
||||||
OstHmiDock::OstHmiDock()
|
OstHmiDock::OstHmiDock()
|
||||||
: QDockWidget(tr("StoryTeller HMI"))
|
: DockWidgetBase(tr("StoryTeller HMI"))
|
||||||
{
|
{
|
||||||
setObjectName("OstHmiDock"); // used to save the state
|
setObjectName("OstHmiDock"); // used to save the state
|
||||||
m_uiOstDisplay.setupUi(this);
|
m_uiOstDisplay.setupUi(this);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
#ifndef OSTHMI_DOCK_H
|
#ifndef OSTHMI_DOCK_H
|
||||||
#define OSTHMI_DOCK_H
|
#define OSTHMI_DOCK_H
|
||||||
|
|
||||||
#include <QDockWidget>
|
#include "dock_widget_base.h"
|
||||||
#include "ui_ost-hmi.h"
|
#include "ui_ost-hmi.h"
|
||||||
|
|
||||||
class OstHmiDock : public QDockWidget
|
class OstHmiDock : public DockWidgetBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,47 @@
|
||||||
#include "resource_model.h"
|
#include "resource_model.h"
|
||||||
|
|
||||||
|
|
||||||
|
QVariant ResourceModel::data(const QModelIndex &index, int role) const {
|
||||||
|
if (role != Qt::DisplayRole && role != Qt::EditRole) return {};
|
||||||
|
const auto & res = m_data[index.row()];
|
||||||
|
switch (index.column()) {
|
||||||
|
case 0: return res.file.c_str();
|
||||||
|
case 1: return res.format.c_str();
|
||||||
|
case 2: return res.description.c_str();
|
||||||
|
default: return {};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ResourceModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||||
|
if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return {};
|
||||||
|
switch (section) {
|
||||||
|
case 0: return "File";
|
||||||
|
case 1: return "Format";
|
||||||
|
case 2: return "Description";
|
||||||
|
default: return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceModel::append(const Resource &res) {
|
||||||
|
beginInsertRows({}, m_data.count(), m_data.count());
|
||||||
|
m_data.append(res);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceModel::Clear()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_data.clear();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ResourceModel::GetFileName(int row) {
|
||||||
|
QString n;
|
||||||
|
|
||||||
|
if (row < m_data.size())
|
||||||
|
{
|
||||||
|
n = m_data.at(row).file.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,10 @@
|
||||||
#ifndef RESOURCEMODEL_H
|
#ifndef RESOURCE_MODEL_H
|
||||||
#define RESOURCEMODEL_H
|
#define RESOURCE_MODEL_H
|
||||||
|
|
||||||
#include <QtGui>
|
#include <QtGui>
|
||||||
#include "story_project.h"
|
#include "story_project.h"
|
||||||
|
|
||||||
#include <QAbstractTableModel>
|
#include <QAbstractTableModel>
|
||||||
/*
|
|
||||||
class CustomTableModel : public QAbstractTableModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit CustomTableModel(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
// Reimplemented virtual functions
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
const int rows = 3;
|
|
||||||
const int columns = 3;
|
|
||||||
int dataStorage[3][3] = {
|
|
||||||
{1, 2, 3},
|
|
||||||
{4, 5, 6},
|
|
||||||
{7, 8, 9}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ResourceModel : public QAbstractTableModel
|
class ResourceModel : public QAbstractTableModel
|
||||||
{
|
{
|
||||||
|
|
@ -39,41 +14,13 @@ public:
|
||||||
|
|
||||||
int rowCount(const QModelIndex &) const override { return m_data.count(); }
|
int rowCount(const QModelIndex &) const override { return m_data.count(); }
|
||||||
int columnCount(const QModelIndex &) const override { return 3; }
|
int columnCount(const QModelIndex &) const override { return 3; }
|
||||||
QVariant data(const QModelIndex &index, int role) const override {
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
if (role != Qt::DisplayRole && role != Qt::EditRole) return {};
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||||
const auto & res = m_data[index.row()];
|
void append(const Resource & res);
|
||||||
switch (index.column()) {
|
|
||||||
case 0: return res.file.c_str();
|
|
||||||
case 1: return res.format.c_str();
|
|
||||||
case 2: return res.description.c_str();
|
|
||||||
default: return {};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override {
|
|
||||||
if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return {};
|
|
||||||
switch (section) {
|
|
||||||
case 0: return "File";
|
|
||||||
case 1: return "Format";
|
|
||||||
case 2: return "Description";
|
|
||||||
default: return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void append(const Resource & res) {
|
|
||||||
beginInsertRows({}, m_data.count(), m_data.count());
|
|
||||||
m_data.append(res);
|
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GetFileName(int row) {
|
void Clear();
|
||||||
QString n;
|
|
||||||
|
|
||||||
if (row < m_data.size())
|
QString GetFileName(int row);
|
||||||
{
|
|
||||||
n = m_data.at(row).file.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<Resource> GetData() const { return m_data; }
|
QList<Resource> GetData() const { return m_data; }
|
||||||
|
|
||||||
|
|
@ -81,4 +28,4 @@ private:
|
||||||
QList<Resource> m_data;
|
QList<Resource> m_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RESOURCEMODEL_H
|
#endif // RESOURCE_MODEL_H
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
ResourcesDock::ResourcesDock()
|
ResourcesDock::ResourcesDock(StoryProject &project)
|
||||||
: QDockWidget(tr("Resources"))
|
: m_project(project)
|
||||||
|
, DockWidgetBase(tr("Resources"))
|
||||||
{
|
{
|
||||||
setObjectName("ResourcesDock"); // used to save the state
|
setObjectName("ResourcesDock"); // used to save the state
|
||||||
|
|
||||||
|
|
@ -16,13 +17,51 @@ ResourcesDock::ResourcesDock()
|
||||||
".",
|
".",
|
||||||
tr("Images (*.bmp)"));
|
tr("Images (*.bmp)"));
|
||||||
|
|
||||||
|
std::filesystem::path p(fileName.toStdString());
|
||||||
|
std::filesystem::path p2 = m_project.ImagesPath() / p.filename().generic_string();
|
||||||
|
std::filesystem::copy(p, p2);
|
||||||
|
|
||||||
Resource res;
|
Resource res;
|
||||||
res.format = "BMP";
|
res.format = "BMP";
|
||||||
res.file = fileName.toStdString();
|
res.file = p.filename();
|
||||||
|
|
||||||
m_resourcesModel.append(res);
|
m_resourcesModel.append(res);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(m_uiOstResources.addSoundButton, &QPushButton::clicked, [=](bool checked) {
|
||||||
|
|
||||||
|
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
|
||||||
|
".",
|
||||||
|
tr("Sounds (*.wav)"));
|
||||||
|
|
||||||
|
std::filesystem::path p(fileName.toStdString());
|
||||||
|
std::filesystem::path p2 = m_project.SoundsPath() / p.filename().generic_string();
|
||||||
|
std::filesystem::copy(p, p2);
|
||||||
|
|
||||||
|
Resource res;
|
||||||
|
res.format = "WAV";
|
||||||
|
res.file = p.filename();
|
||||||
|
m_resourcesModel.append(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcesDock::Initialize()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcesDock::SaveToProject()
|
||||||
|
{
|
||||||
|
m_project.Clear();
|
||||||
|
for (auto & r : m_resourcesModel.GetData())
|
||||||
|
{
|
||||||
|
m_project.m_images.push_back(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcesDock::slotClear()
|
||||||
|
{
|
||||||
|
m_resourcesModel.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,29 @@
|
||||||
#ifndef RESOURCESDOCK_H
|
#ifndef RESOURCESDOCK_H
|
||||||
#define RESOURCESDOCK_H
|
#define RESOURCESDOCK_H
|
||||||
|
|
||||||
#include <QDockWidget>
|
#include "dock_widget_base.h"
|
||||||
#include "ui_ost-resources.h"
|
#include "ui_ost-resources.h"
|
||||||
#include "resource_model.h"
|
#include "resource_model.h"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
class ResourcesDock : public QDockWidget
|
class ResourcesDock : public DockWidgetBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ResourcesDock();
|
ResourcesDock(StoryProject &project);
|
||||||
|
|
||||||
|
void Initialize();
|
||||||
|
|
||||||
QList<Resource> GetResources() const { return m_resourcesModel.GetData(); }
|
QList<Resource> GetResources() const { return m_resourcesModel.GetData(); }
|
||||||
|
|
||||||
ResourceModel &getModel() { return m_resourcesModel; }
|
ResourceModel &getModel() { return m_resourcesModel; }
|
||||||
|
|
||||||
|
void SaveToProject();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void slotClear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Resources
|
StoryProject &m_project;
|
||||||
Ui::ostResources m_uiOstResources;
|
Ui::ostResources m_uiOstResources;
|
||||||
ResourceModel m_resourcesModel;
|
ResourceModel m_resourcesModel;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ mov R0,R2 ; copy R2 into R0 (NO blank space between , and R2)
|
||||||
|
|
||||||
|
|
||||||
ScriptEditorDock::ScriptEditorDock()
|
ScriptEditorDock::ScriptEditorDock()
|
||||||
: QDockWidget(tr("Script editor"))
|
: DockWidgetBase(tr("Script editor"))
|
||||||
{
|
{
|
||||||
setObjectName("ScriptEditorDock"); // used to save the state
|
setObjectName("ScriptEditorDock"); // used to save the state
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
#ifndef SCRIPTEDITOR_H
|
#ifndef SCRIPTEDITOR_H
|
||||||
#define SCRIPTEDITOR_H
|
#define SCRIPTEDITOR_H
|
||||||
|
|
||||||
#include <QDockWidget>
|
#include "dock_widget_base.h"
|
||||||
#include "code_editor.h"
|
#include "code_editor.h"
|
||||||
#include "highlighter.h"
|
#include "highlighter.h"
|
||||||
|
|
||||||
class ScriptEditorDock : public QDockWidget
|
class ScriptEditorDock : public DockWidgetBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -311,8 +311,11 @@ QJsonObject StoryGraphModel::saveNode(NodeId const nodeId) const
|
||||||
{
|
{
|
||||||
QJsonObject nodeJson;
|
QJsonObject nodeJson;
|
||||||
|
|
||||||
|
|
||||||
nodeJson["id"] = static_cast<qint64>(nodeId);
|
nodeJson["id"] = static_cast<qint64>(nodeId);
|
||||||
|
|
||||||
|
nodeJson["internal-data"] = _models.at(nodeId)->save();
|
||||||
|
|
||||||
{
|
{
|
||||||
QPointF const pos = nodeData(nodeId, NodeRole::Position).value<QPointF>();
|
QPointF const pos = nodeData(nodeId, NodeRole::Position).value<QPointF>();
|
||||||
|
|
||||||
|
|
@ -321,8 +324,8 @@ QJsonObject StoryGraphModel::saveNode(NodeId const nodeId) const
|
||||||
posJson["y"] = pos.y();
|
posJson["y"] = pos.y();
|
||||||
nodeJson["position"] = posJson;
|
nodeJson["position"] = posJson;
|
||||||
|
|
||||||
// nodeJson["inPortCount"] = QString::number(_nodePortCounts[nodeId].in);
|
nodeJson["inPortCount"] = QString::number(nodeData(nodeId, NodeRole::InPortCount).value<int>());
|
||||||
// nodeJson["outPortCount"] = QString::number(_nodePortCounts[nodeId].out);
|
nodeJson["outPortCount"] = QString::number(nodeData(nodeId, NodeRole::OutPortCount).value<int>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeJson;
|
return nodeJson;
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,36 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <filesystem>
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
void StoryProject::Initialize()
|
||||||
|
{
|
||||||
|
// Frist try to create the working directory
|
||||||
|
if (!std::filesystem::is_directory(working_dir))
|
||||||
|
{
|
||||||
|
std::filesystem::create_directories(working_dir);
|
||||||
|
}
|
||||||
|
m_imagesPath = std::filesystem::path(working_dir) / "images";
|
||||||
|
m_soundsPath = std::filesystem::path(working_dir) / "sounds";
|
||||||
|
|
||||||
|
std::filesystem::create_directories(m_imagesPath);
|
||||||
|
std::filesystem::create_directories(m_soundsPath);
|
||||||
|
}
|
||||||
|
|
||||||
bool StoryProject::Load(const std::string &file_path)
|
bool StoryProject::Load(const std::string &file_path)
|
||||||
{
|
{
|
||||||
std::ifstream f(file_path);
|
std::ifstream f(file_path);
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
std::filesystem::path p(file_path);
|
std::filesystem::path p(file_path);
|
||||||
m_working_dir= p.parent_path();
|
working_dir= p.parent_path();
|
||||||
|
|
||||||
std::cout << "Working dir is: " << m_working_dir << std::endl;
|
std::cout << "Working dir is: " << working_dir << std::endl;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
||||||
nlohmann::json j = nlohmann::json::parse(f);
|
nlohmann::json j = nlohmann::json::parse(f);
|
||||||
|
|
||||||
m_nodes.clear();
|
m_nodes.clear();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
struct StoryNode
|
struct StoryNode
|
||||||
{
|
{
|
||||||
|
|
@ -43,9 +44,10 @@ struct Resource
|
||||||
|
|
||||||
struct StoryProject
|
struct StoryProject
|
||||||
{
|
{
|
||||||
|
std::string uuid;
|
||||||
|
std::string working_dir; /// Temporary folder based on the uuid, where the archive is unzipped
|
||||||
|
std::string file_path; /// project file (archive)
|
||||||
|
|
||||||
|
|
||||||
std::string m_working_dir;
|
|
||||||
std::vector<StoryNode> m_nodes;
|
std::vector<StoryNode> m_nodes;
|
||||||
|
|
||||||
std::string m_type;
|
std::string m_type;
|
||||||
|
|
@ -57,6 +59,12 @@ struct StoryProject
|
||||||
|
|
||||||
StoryNode *m_tree;
|
StoryNode *m_tree;
|
||||||
|
|
||||||
|
// Initialize a project
|
||||||
|
// The following parameters must be set before calling this method:
|
||||||
|
// - uuid
|
||||||
|
// - working_directory
|
||||||
|
void Initialize();
|
||||||
|
|
||||||
bool Load(const std::string &file_path);
|
bool Load(const std::string &file_path);
|
||||||
void CreateTree();
|
void CreateTree();
|
||||||
void Clear() {
|
void Clear() {
|
||||||
|
|
@ -66,16 +74,20 @@ struct StoryProject
|
||||||
|
|
||||||
std::string Compile();
|
std::string Compile();
|
||||||
|
|
||||||
|
|
||||||
|
std::filesystem::path ImagesPath() const { return m_imagesPath; }
|
||||||
|
std::filesystem::path SoundsPath() const { return m_soundsPath; }
|
||||||
|
|
||||||
static std::string GetFileExtension(const std::string &FileName);
|
static std::string GetFileExtension(const std::string &FileName);
|
||||||
static std::string GetFileName(const std::string &path);
|
static std::string GetFileName(const std::string &path);
|
||||||
static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace);
|
static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace);
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void EraseString(std::string &theString, const std::string &toErase);
|
static void EraseString(std::string &theString, const std::string &toErase);
|
||||||
static std::string ToUpper(const std::string &input);
|
static std::string ToUpper(const std::string &input);
|
||||||
private:
|
private:
|
||||||
|
std::filesystem::path m_imagesPath;
|
||||||
|
std::filesystem::path m_soundsPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // STORY_PROJECT_H
|
#endif // STORY_PROJECT_H
|
||||||
|
|
|
||||||
|
|
@ -10,65 +10,55 @@ void ToolBar::createActions(QMenuBar *menuBar)
|
||||||
{
|
{
|
||||||
QMenu *fileMenu = menuBar->addMenu(tr("&File"));
|
QMenu *fileMenu = menuBar->addMenu(tr("&File"));
|
||||||
|
|
||||||
// const QIcon newIcon = QIcon::fromTheme("document-new", QIcon(":/images/new.png"));
|
// ------------ New
|
||||||
// QAction *newLetterAct = new QAction(newIcon, tr("&New Letter"), this);
|
{
|
||||||
// newLetterAct->setShortcuts(QKeySequence::New);
|
const QIcon icon = QIcon::fromTheme("document-save", QIcon(":/assets/file-document-plus-outline.svg"));
|
||||||
// newLetterAct->setStatusTip(tr("Create a new form letter"));
|
QAction *act = new QAction(icon, tr("&New project"), this);
|
||||||
// connect(newLetterAct, &QAction::triggered, this, &MainWindow::newLetter);
|
act->setShortcuts(QKeySequence::Save);
|
||||||
// fileMenu->addAction(newLetterAct);
|
act->setStatusTip(tr("Create a new project"));
|
||||||
// fileToolBar->addAction(newLetterAct);
|
connect(act, &QAction::triggered, this, &ToolBar::sigNew);
|
||||||
|
fileMenu->addAction(act);
|
||||||
|
addAction(act);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------ Save
|
// ------------ Save
|
||||||
{
|
{
|
||||||
const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(":/assets/floppy.svg"));
|
const QIcon icon = QIcon::fromTheme("document-save", QIcon(":/assets/floppy.svg"));
|
||||||
QAction *saveAct = new QAction(saveIcon, tr("&Save..."), this);
|
QAction *act = new QAction(icon, tr("&Save project"), this);
|
||||||
saveAct->setShortcuts(QKeySequence::Save);
|
act->setShortcuts(QKeySequence::Save);
|
||||||
saveAct->setStatusTip(tr("Save the current project"));
|
act->setStatusTip(tr("Save the current project"));
|
||||||
connect(saveAct, &QAction::triggered, this, &ToolBar::sigSave);
|
connect(act, &QAction::triggered, this, &ToolBar::sigSave);
|
||||||
fileMenu->addAction(saveAct);
|
fileMenu->addAction(act);
|
||||||
addAction(saveAct);
|
addAction(act);
|
||||||
}
|
}
|
||||||
// ------------ Open
|
// ------------ Open
|
||||||
{
|
{
|
||||||
const QIcon saveIcon = QIcon::fromTheme("document-open", QIcon(":/assets/folder-open.svg"));
|
const QIcon icon = QIcon::fromTheme("document-open", QIcon(":/assets/folder-open.svg"));
|
||||||
QAction *openAct = new QAction(saveIcon, tr("&Open..."), this);
|
QAction *act = new QAction(icon, tr("&Open project"), this);
|
||||||
openAct->setShortcuts(QKeySequence::Open);
|
act->setShortcuts(QKeySequence::Open);
|
||||||
openAct->setStatusTip(tr("Open the current project"));
|
act->setStatusTip(tr("Open an existing project"));
|
||||||
connect(openAct, &QAction::triggered, this, &ToolBar::sigOpen);
|
connect(act, &QAction::triggered, this, &ToolBar::sigOpen);
|
||||||
fileMenu->addAction(openAct);
|
fileMenu->addAction(act);
|
||||||
addAction(openAct);
|
addAction(act);
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
const QIcon printIcon = QIcon::fromTheme("document-print", QIcon(":/images/print.png"));
|
|
||||||
QAction *printAct = new QAction(printIcon, tr("&Print..."), this);
|
|
||||||
printAct->setShortcuts(QKeySequence::Print);
|
|
||||||
printAct->setStatusTip(tr("Print the current form letter"));
|
|
||||||
connect(printAct, &QAction::triggered, this, &MainWindow::print);
|
|
||||||
fileMenu->addAction(printAct);
|
|
||||||
fileToolBar->addAction(printAct);
|
|
||||||
*/
|
|
||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
|
|
||||||
QAction *quitAct = fileMenu->addAction(tr("&Quit"), this, &QWidget::close);
|
QAction *quitAct = fileMenu->addAction(tr("&Quit"), this, &QWidget::close);
|
||||||
quitAct->setShortcuts(QKeySequence::Quit);
|
quitAct->setShortcuts(QKeySequence::Quit);
|
||||||
quitAct->setStatusTip(tr("Quit the application"));
|
quitAct->setStatusTip(tr("Quit the application"));
|
||||||
|
|
||||||
// QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
|
|
||||||
// QToolBar *editToolBar = addToolBar(tr("Edit"));
|
|
||||||
// const QIcon undoIcon = QIcon::fromTheme("edit-undo", QIcon(":/images/undo.png"));
|
|
||||||
// QAction *undoAct = new QAction(undoIcon, tr("&Undo"), this);
|
|
||||||
// undoAct->setShortcuts(QKeySequence::Undo);
|
|
||||||
// undoAct->setStatusTip(tr("Undo the last editing action"));
|
|
||||||
// connect(undoAct, &QAction::triggered, this, &MainWindow::undo);
|
|
||||||
// editMenu->addAction(undoAct);
|
|
||||||
// editToolBar->addAction(undoAct);
|
|
||||||
|
|
||||||
// viewMenu = menuBar()->addMenu(tr("&View"));
|
|
||||||
|
|
||||||
menuBar->addSeparator();
|
menuBar->addSeparator();
|
||||||
|
|
||||||
|
m_windowsMenu = menuBar->addMenu(tr("&Windows"));
|
||||||
|
|
||||||
QMenu *helpMenu = menuBar->addMenu(tr("&Help"));
|
QMenu *helpMenu = menuBar->addMenu(tr("&Help"));
|
||||||
|
|
||||||
QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &ToolBar::sigAbout);
|
QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &ToolBar::sigAbout);
|
||||||
aboutAct->setStatusTip(tr("Show the application's About box"));
|
aboutAct->setStatusTip(tr("Show the application's About box"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ToolBar::AddDockToMenu(QAction *action)
|
||||||
|
{
|
||||||
|
m_windowsMenu->addAction(action);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,16 @@ public:
|
||||||
ToolBar();
|
ToolBar();
|
||||||
void createActions(QMenuBar *menuBar);
|
void createActions(QMenuBar *menuBar);
|
||||||
|
|
||||||
|
void AddDockToMenu(QAction *action);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void sigNew();
|
||||||
void sigSave();
|
void sigSave();
|
||||||
void sigOpen();
|
void sigOpen();
|
||||||
void sigAbout();
|
void sigAbout();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMenu *m_windowsMenu;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TOOLBAR_H
|
#endif // TOOLBAR_H
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#include "vm_dock.h"
|
#include "vm_dock.h"
|
||||||
|
|
||||||
VmDock::VmDock(Chip32::Assembler &assembler)
|
VmDock::VmDock(Chip32::Assembler &assembler)
|
||||||
: QDockWidget(tr("Virtual Machine"))
|
: DockWidgetBase(tr("Virtual Machine"))
|
||||||
{
|
{
|
||||||
setObjectName("VirtualMachineDock"); // used to save the state
|
setObjectName("VirtualMachineDock"); // used to save the state
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
#ifndef VMDOCK_H
|
#ifndef VMDOCK_H
|
||||||
#define VMDOCK_H
|
#define VMDOCK_H
|
||||||
|
|
||||||
#include <QDockWidget>
|
#include "dock_widget_base.h"
|
||||||
#include "ui_ost-vm.h"
|
#include "ui_ost-vm.h"
|
||||||
|
|
||||||
#include "chip32_assembler.h"
|
#include "chip32_assembler.h"
|
||||||
|
|
||||||
class VmDock : public QDockWidget
|
class VmDock : public DockWidgetBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|
|
||||||
273
story-editor/src/zip.cpp
Normal file
273
story-editor/src/zip.cpp
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
* Copyright (c) 2019 Anthony Rabine
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "zip.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <cstring>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
static bool ensure_file_exists_and_is_readable(const char *pFilename)
|
||||||
|
{
|
||||||
|
FILE *p = nullptr;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
fopen_s(& p, pFilename, "rb");
|
||||||
|
#else
|
||||||
|
p = fopen(pFilename, "rb");
|
||||||
|
#endif
|
||||||
|
if (!p) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(p, 0, SEEK_END);
|
||||||
|
long int src_file_size = ftell(p);
|
||||||
|
fseek(p, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (src_file_size)
|
||||||
|
{
|
||||||
|
char buf[1];
|
||||||
|
if (fread(buf, 1, 1, p) != 1)
|
||||||
|
{
|
||||||
|
fclose(p);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(p);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
Zip::Zip()
|
||||||
|
: mIsValid(false)
|
||||||
|
, mNumberOfFiles(0U)
|
||||||
|
{
|
||||||
|
std::memset(&mZipArchive, 0, sizeof(mZipArchive));
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
Zip::~Zip()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
void Zip::CreateInMemory(const std::string &fileName)
|
||||||
|
{
|
||||||
|
mz_bool status = MZ_FALSE;
|
||||||
|
std::memset(&mZipArchive, 0, sizeof(mZipArchive));
|
||||||
|
|
||||||
|
status = mz_zip_writer_init_file(&mZipArchive, fileName.c_str(), 65537);
|
||||||
|
|
||||||
|
if (status)
|
||||||
|
{
|
||||||
|
mIsOpenForWriting = true;
|
||||||
|
mIsValid = true;
|
||||||
|
mIsOpen = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mIsOpenForWriting = false;
|
||||||
|
mIsValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
bool Zip::AddFile(const std::string &fileName, const std::string &archiveName)
|
||||||
|
{
|
||||||
|
mz_bool status = MZ_FALSE;
|
||||||
|
if (ensure_file_exists_and_is_readable(fileName.c_str()))
|
||||||
|
{
|
||||||
|
status = mz_zip_writer_add_file(&mZipArchive, archiveName.c_str(), fileName.c_str(), NULL, 0, MZ_NO_COMPRESSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status == MZ_TRUE;
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
void Zip::AddDirectory(const std::string &dirName)
|
||||||
|
{
|
||||||
|
std::string d = dirName + "/";
|
||||||
|
mz_zip_writer_add_mem(&mZipArchive, d.c_str(), NULL, 0, MZ_NO_COMPRESSION);
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
std::vector<std::string> Zip::Unzip(std::string const &zipFile, const std::string &destination_dir, std::string const &password)
|
||||||
|
{
|
||||||
|
(void)(password);
|
||||||
|
std::vector<std::string> files = {};
|
||||||
|
mz_zip_archive zip_archive;
|
||||||
|
memset(&zip_archive, 0, sizeof(zip_archive));
|
||||||
|
|
||||||
|
auto status = mz_zip_reader_init_file(&zip_archive, zipFile.c_str(), 0);
|
||||||
|
if (!status) return files;
|
||||||
|
int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive);
|
||||||
|
if (fileCount == 0)
|
||||||
|
{
|
||||||
|
mz_zip_reader_end(&zip_archive);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
mz_zip_archive_file_stat file_stat;
|
||||||
|
if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat))
|
||||||
|
{
|
||||||
|
mz_zip_reader_end(&zip_archive);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
// Get root folder
|
||||||
|
// QFileInfo fileInfo(file_stat.m_filename);
|
||||||
|
// QFileInfo filePath(fileInfo.path());
|
||||||
|
// QString baseName = filePath.path();
|
||||||
|
// QString dstDir = QString::fromStdString(destination_dir);
|
||||||
|
// QString separator = "/";
|
||||||
|
std::string base = std::filesystem::path(file_stat.m_filename).parent_path().string();
|
||||||
|
|
||||||
|
// Get and print information about each file in the archive.
|
||||||
|
for (int i = 0; i < fileCount; i++)
|
||||||
|
{
|
||||||
|
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue;
|
||||||
|
if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) continue; // skip directories for now
|
||||||
|
std::string fileName = base + std::filesystem::path::preferred_separator + file_stat.m_filename; // make path relative
|
||||||
|
std::string destFile = destination_dir + std::filesystem::path::preferred_separator + fileName; // make full dest path
|
||||||
|
|
||||||
|
// creates the directory where the file will be decompressed
|
||||||
|
std::filesystem::create_directories(std::filesystem::path(destFile).parent_path());
|
||||||
|
|
||||||
|
// Extract file
|
||||||
|
if (mz_zip_reader_extract_to_file(&zip_archive, i, destFile.c_str(), 0))
|
||||||
|
{
|
||||||
|
files.emplace_back(destFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the archive, freeing any resources it was using
|
||||||
|
mz_zip_reader_end(&zip_archive);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
bool Zip::Open(const std::string &zip, bool isFile)
|
||||||
|
{
|
||||||
|
mz_bool status;
|
||||||
|
mIsValid = false;
|
||||||
|
mIsOpenForWriting = true;
|
||||||
|
|
||||||
|
mNumberOfFiles = 0U;
|
||||||
|
|
||||||
|
std::memset(&mZipArchive, 0, sizeof(mZipArchive));
|
||||||
|
|
||||||
|
if (isFile)
|
||||||
|
{
|
||||||
|
// Physical file on disk
|
||||||
|
status = mz_zip_reader_init_file(&mZipArchive, zip.c_str(), 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Zipped memory
|
||||||
|
status = mz_zip_reader_init_mem(&mZipArchive, zip.c_str(), zip.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status)
|
||||||
|
{
|
||||||
|
mFiles.clear();
|
||||||
|
// Get and print information about each file in the archive.
|
||||||
|
for (std::uint32_t i = 0; i < mz_zip_reader_get_num_files(&mZipArchive); i++)
|
||||||
|
{
|
||||||
|
mz_zip_archive_file_stat file_stat;
|
||||||
|
if (mz_zip_reader_file_stat(&mZipArchive, i, &file_stat))
|
||||||
|
{
|
||||||
|
mNumberOfFiles++;
|
||||||
|
mFiles.push_back(file_stat.m_filename);
|
||||||
|
//printf("Filename: \"%s\", Comment: \"%s\", Uncompressed size: %u, Compressed size: %u\n", file_stat.m_filename, file_stat.m_comment, (std::uint32_t)file_stat.m_uncomp_size, (std::uint32_t)file_stat.m_comp_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mNumberOfFiles == mz_zip_reader_get_num_files(&mZipArchive))
|
||||||
|
{
|
||||||
|
mIsValid = true;
|
||||||
|
mIsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIsValid;
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
void Zip::Close()
|
||||||
|
{
|
||||||
|
if (mIsValid)
|
||||||
|
{
|
||||||
|
if (mIsOpenForWriting)
|
||||||
|
{
|
||||||
|
if (!mz_zip_writer_finalize_archive(&mZipArchive))
|
||||||
|
{
|
||||||
|
mz_zip_writer_end(&mZipArchive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mz_zip_reader_end(&mZipArchive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mIsOpen = false;
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
struct UserData
|
||||||
|
{
|
||||||
|
char *output;
|
||||||
|
int offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
static mz_bool DeflateCallback(const void *pBuf, int len, void *pUser)
|
||||||
|
{
|
||||||
|
UserData *ud = static_cast<UserData*>(pUser);
|
||||||
|
std::memcpy(ud->output + ud->offset, pBuf, len);
|
||||||
|
ud->offset += len;
|
||||||
|
(void) len;
|
||||||
|
(void) pUser;
|
||||||
|
return MZ_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
int Zip::CompressBuffer(const char *input, size_t input_size, char *output)
|
||||||
|
{
|
||||||
|
int finalsize = -1;
|
||||||
|
tdefl_compressor Comp;
|
||||||
|
|
||||||
|
UserData ud;
|
||||||
|
|
||||||
|
ud.offset = 0U;
|
||||||
|
ud.output = output;
|
||||||
|
|
||||||
|
if (tdefl_init(&Comp, DeflateCallback, &ud, 0) == TDEFL_STATUS_OKAY)
|
||||||
|
{
|
||||||
|
if(tdefl_compress_buffer(&Comp, input, input_size, TDEFL_FINISH) == TDEFL_STATUS_DONE)
|
||||||
|
{
|
||||||
|
finalsize = ud.offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalsize;
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
bool Zip::GetFile(const std::string &fileName, std::string &contents)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
if (mIsValid)
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
char *p = reinterpret_cast<char *>(mz_zip_reader_extract_file_to_heap(&mZipArchive, fileName.c_str(), &size, 0));
|
||||||
|
|
||||||
|
if (p != nullptr)
|
||||||
|
{
|
||||||
|
contents.assign(p, size);
|
||||||
|
free(p);
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
/*****************************************************************************/
|
||||||
|
std::vector<std::string> Zip::ListFiles()
|
||||||
|
{
|
||||||
|
return mFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// End of file Zip.cpp
|
||||||
|
//=============================================================================
|
||||||
52
story-editor/src/zip.h
Normal file
52
story-editor/src/zip.h
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
* Copyright (c) 2019 Anthony Rabine
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZIP_H
|
||||||
|
#define ZIP_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
#include "miniz.h"
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
class Zip
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Zip();
|
||||||
|
~Zip();
|
||||||
|
|
||||||
|
bool Open(const std::string &zip, bool isFile);
|
||||||
|
std::uint32_t NumberOfFiles() { return mNumberOfFiles; }
|
||||||
|
bool GetFile(const std::string &fileName, std::string &contents);
|
||||||
|
std::vector<std::string> ListFiles();
|
||||||
|
void Close();
|
||||||
|
bool IsOpen() const { return mIsOpen; }
|
||||||
|
bool isOpenForWriting() const { return mIsOpenForWriting; }
|
||||||
|
|
||||||
|
static int CompressBuffer(const char *input, size_t input_size, char *output);
|
||||||
|
|
||||||
|
void CreateInMemory(const std::string &fileName);
|
||||||
|
bool AddFile(const std::string &fileName, const std::string &archiveName);
|
||||||
|
void AddDirectory(const std::string &dirName);
|
||||||
|
|
||||||
|
static std::vector<std::string> Unzip(const std::string &zipFile, const std::string &destination_dir, const std::string &password);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mz_zip_archive mZipArchive;
|
||||||
|
bool mIsValid;
|
||||||
|
bool mIsOpen{false};
|
||||||
|
bool mIsOpenForWriting{false};
|
||||||
|
std::uint32_t mNumberOfFiles;
|
||||||
|
std::vector<std::string> mFiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ZIP_H
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// End of file Zip.cpp
|
||||||
|
//=============================================================================
|
||||||
Loading…
Reference in a new issue