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 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.
|
||||
|
||||
*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:
|
||||
- A display (TFT...)
|
||||
- An Audio output
|
||||
- A SD card or memory
|
||||
- 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:
|
||||
- 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
|
||||
- http://openstoryteller.org/: main documentation and project news
|
||||
- http://github.com/arabine/open-story-teller: source code, tickets, help
|
||||
|
||||
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)
|
||||
2. Invoke cmake with some options, passed as definitions for CMake (-D<option>)
|
||||
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.
|
||||
|
||||
| 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 ..`
|
||||
|
||||
|
||||
# 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 :
|
||||
|
||||

|
||||
Planned nodes:
|
||||
- Random
|
||||
- Loop
|
||||
- Conditional
|
||||
|
||||
# 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
|
||||
---
|
||||
|
||||
|
||||
# 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/ost-editor.qrc
|
||||
src/story_project.cpp
|
||||
src/story_project.h
|
||||
src/media_node_model.h
|
||||
src/media_node_model.cpp
|
||||
src/story_graph_model.cpp
|
||||
|
|
@ -47,6 +48,8 @@ set(PROJECT_SOURCES
|
|||
src/log_dock.cpp
|
||||
src/vm_dock.h
|
||||
src/vm_dock.cpp
|
||||
src/dock_widget_base.h
|
||||
src/dock_widget_base.cpp
|
||||
src/code_editor.h
|
||||
src/code_editor.cpp
|
||||
src/graph.h
|
||||
|
|
@ -55,6 +58,9 @@ set(PROJECT_SOURCES
|
|||
src/event_node_model.cpp
|
||||
src/highlighter.h
|
||||
src/highlighter.cpp
|
||||
src/zip.cpp
|
||||
src/zip.h
|
||||
src/miniz.c
|
||||
src/ost-hmi.ui
|
||||
src/ost-vm.ui
|
||||
src/ost-data.ui
|
||||
|
|
@ -64,6 +70,7 @@ set(PROJECT_SOURCES
|
|||
src/media-node.ui
|
||||
src/event-node.ui
|
||||
src/choose-file.ui
|
||||
src/new-project.ui
|
||||
../software/chip32/chip32_assembler.cpp
|
||||
../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"
|
||||
|
||||
LogDock::LogDock()
|
||||
: QDockWidget(tr("Logs"))
|
||||
: DockWidgetBase(tr("Logs"))
|
||||
{
|
||||
setObjectName("OstHmiDock"); // used to save the state
|
||||
m_logUi.setupUi(this);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
#ifndef LOGDOCK_H
|
||||
#define LOGDOCK_H
|
||||
|
||||
#include <QDockWidget>
|
||||
#include "dock_widget_base.h"
|
||||
#include "ui_ost-log.h"
|
||||
|
||||
class LogDock : public QDockWidget
|
||||
class LogDock : public DockWidgetBase
|
||||
{
|
||||
public:
|
||||
LogDock();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
#include <QHeaderView>
|
||||
#include <QSettings>
|
||||
#include <QtDebug>
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
|
||||
#include <QtNodes/BasicGraphicsScene>
|
||||
#include <QtNodes/ConnectionStyle>
|
||||
|
|
@ -49,6 +51,10 @@ MainWindow::MainWindow()
|
|||
: m_model(m_project)
|
||||
, 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);
|
||||
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();
|
||||
addDockWidget(Qt::DockWidgetArea::BottomDockWidgetArea, m_logDock);
|
||||
m_toolbar->AddDockToMenu(m_logDock->toggleViewAction());
|
||||
|
||||
m_nodeEditorDock = new NodeEditorDock(&m_scene);
|
||||
addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, m_nodeEditorDock);
|
||||
m_toolbar->addAction(m_nodeEditorDock->toggleViewAction());
|
||||
m_toolbar->AddDockToMenu(m_nodeEditorDock->toggleViewAction());
|
||||
|
||||
m_ostHmiDock = new OstHmiDock();
|
||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ostHmiDock);
|
||||
m_toolbar->AddDockToMenu(m_ostHmiDock->toggleViewAction());
|
||||
|
||||
connect(m_ostHmiDock, &OstHmiDock::sigOkButton, [=]() {
|
||||
QCoreApplication::postEvent(this, new VmEvent(VmEvent::evOkButton));
|
||||
});
|
||||
|
||||
m_resourcesDock = new ResourcesDock();
|
||||
m_resourcesDock = new ResourcesDock(m_project);
|
||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_resourcesDock);
|
||||
m_toolbar->AddDockToMenu(m_resourcesDock->toggleViewAction());
|
||||
|
||||
m_scriptEditorDock = new ScriptEditorDock();
|
||||
addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, m_scriptEditorDock);
|
||||
m_toolbar->AddDockToMenu(m_scriptEditorDock->toggleViewAction());
|
||||
|
||||
m_vmDock = new VmDock(m_assembler);
|
||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_vmDock);
|
||||
m_toolbar->AddDockToMenu(m_vmDock->toggleViewAction());
|
||||
|
||||
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());
|
||||
});
|
||||
|
||||
|
|
@ -109,8 +112,11 @@ MainWindow::MainWindow()
|
|||
|
||||
m_ramView = new MemoryViewDock("RamViewDock", "RAM");
|
||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ramView);
|
||||
m_toolbar->AddDockToMenu(m_ramView->toggleViewAction());
|
||||
|
||||
m_romView = new MemoryViewDock("RomViewDock", "ROM");
|
||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_romView);
|
||||
m_toolbar->AddDockToMenu(m_romView->toggleViewAction());
|
||||
|
||||
m_chooseFileDialog = new QDialog(this);
|
||||
m_chooseFileUi.setupUi(m_chooseFileDialog);
|
||||
|
|
@ -160,9 +166,41 @@ MainWindow::MainWindow()
|
|||
|
||||
readSettings();
|
||||
|
||||
connect(m_toolbar, &ToolBar::sigNew, this, [=]() {
|
||||
NewProject();
|
||||
});
|
||||
|
||||
connect(m_toolbar, &ToolBar::sigSave, this, [=]() {
|
||||
SaveProject();
|
||||
});
|
||||
|
||||
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)
|
||||
{
|
||||
m_logDock->Append(type, context, msg);
|
||||
|
|
@ -189,7 +227,7 @@ void MainWindow::DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId)
|
|||
|
||||
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;
|
||||
nodeInternalData["image"] = imageFile.c_str();
|
||||
}
|
||||
|
|
@ -391,6 +429,12 @@ void MainWindow::createStatusBar()
|
|||
statusBar()->showMessage(tr("Ready"));
|
||||
}
|
||||
|
||||
void MainWindow::NewProject()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::open()
|
||||
{
|
||||
QMimeDatabase mimeDatabase;
|
||||
|
|
@ -420,32 +464,25 @@ void MainWindow::open()
|
|||
QGuiApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
void MainWindow::save()
|
||||
void MainWindow::SaveProject()
|
||||
{
|
||||
QMimeDatabase mimeDatabase;
|
||||
QString fileName = QFileDialog::getSaveFileName(this,
|
||||
tr("Choose a file name"), ".",
|
||||
mimeDatabase.mimeTypeForName("application/json").filterString());
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
QFile file(fileName);
|
||||
if (!file.open(QFile::WriteOnly | QFile::Text)) {
|
||||
QMessageBox::warning(this, tr("Dock Widgets"),
|
||||
tr("Cannot write file %1:\n%2.")
|
||||
.arg(QDir::toNativeSeparators(fileName), file.errorString()));
|
||||
return;
|
||||
// Open the dialog if the project file does not exists
|
||||
if (!QFile::exists(m_project.file_path.c_str()))
|
||||
{
|
||||
// Save current project
|
||||
QString fileName = QFileDialog::getSaveFileName(this, tr("Save project file"),
|
||||
QDir::homePath() + "/new_story.ostproj",
|
||||
tr("OpenStory Teller project (*.ostproj)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
m_project.file_path = fileName.toStdString();
|
||||
}
|
||||
|
||||
QTextStream out(&file);
|
||||
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
|
||||
QJsonObject saveData = m_model.save();
|
||||
QJsonDocument doc(saveData);
|
||||
out << doc.toJson();
|
||||
qDebug() << doc.toJson();
|
||||
|
||||
QGuiApplication::restoreOverrideCursor();
|
||||
|
||||
statusBar()->showMessage(tr("Saved '%1'").arg(fileName), 2000);
|
||||
statusBar()->showMessage(tr("Saved '%1'").arg(m_project.file_path.c_str()), 2000);
|
||||
}
|
||||
|
||||
void MainWindow::about()
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ private:
|
|||
// Private functions
|
||||
void createActions();
|
||||
void createStatusBar();
|
||||
void save();
|
||||
void SaveProject();
|
||||
void DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId);
|
||||
void about();
|
||||
void open();
|
||||
|
|
@ -165,6 +165,11 @@ private:
|
|||
|
||||
bool event(QEvent *event);
|
||||
void MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
|
||||
void NewProject();
|
||||
|
||||
void SetupTemporaryProject();
|
||||
void RefreshProjectInformation();
|
||||
void CloseProject();
|
||||
};
|
||||
|
||||
#endif // MAIN_WINDOW_H
|
||||
|
|
|
|||
|
|
@ -53,17 +53,16 @@ QJsonObject MediaNodeModel::save() const
|
|||
|
||||
// Merge two objects
|
||||
QVariantMap map = obj.toVariantMap();
|
||||
map.insert(m_mediaData.toVariantMap());
|
||||
map.insert(m_mediaData);
|
||||
|
||||
return QJsonObject::fromVariantMap(map);
|
||||
}
|
||||
|
||||
void MediaNodeModel::load(const QJsonObject &mediaData)
|
||||
{
|
||||
m_mediaData = mediaData;
|
||||
m_mediaData = mediaData.toVariantMap();
|
||||
|
||||
// Display loaded image
|
||||
|
||||
QString imagePath = m_mediaData["image"].toString();
|
||||
|
||||
if (!imagePath.isEmpty())
|
||||
|
|
@ -93,6 +92,9 @@ void MediaNodeModel::setInternalData(const QVariant &value)
|
|||
if (obj.contains("image")) {
|
||||
setImage(obj.value("image").toString());
|
||||
}
|
||||
|
||||
// Merge new data into local object
|
||||
m_mediaData.insert(obj.toVariantMap());
|
||||
}
|
||||
|
||||
unsigned int MediaNodeModel::nPorts(PortType portType) const
|
||||
|
|
|
|||
|
|
@ -69,6 +69,6 @@ private:
|
|||
Ui::mediaNodeUi m_ui;
|
||||
std::shared_ptr<NodeData> m_nodeData;
|
||||
|
||||
QJsonObject m_mediaData;
|
||||
QVariantMap m_mediaData;
|
||||
void setImage(const QString &fileName);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#include "model/buffer/qmemorybuffer.h"
|
||||
|
||||
MemoryViewDock::MemoryViewDock(const QString &objectName, const QString &title)
|
||||
: QDockWidget(title)
|
||||
: DockWidgetBase(title)
|
||||
{
|
||||
setObjectName(objectName);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
#ifndef MEMORY_VIEWDOCK_H
|
||||
#define MEMORY_VIEWDOCK_H
|
||||
|
||||
#include <QDockWidget>
|
||||
#include "dock_widget_base.h"
|
||||
#include <qhexview.h>
|
||||
|
||||
class MemoryViewDock : public QDockWidget
|
||||
class MemoryViewDock : public DockWidgetBase
|
||||
{
|
||||
public:
|
||||
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>
|
||||
|
||||
NodeEditorDock::NodeEditorDock(BasicGraphicsScene *scene)
|
||||
: QDockWidget(tr("Node editor"))
|
||||
: DockWidgetBase(tr("Node editor"))
|
||||
{
|
||||
setObjectName("NodeEditorDock");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
#ifndef NODEEDITORDOCK_H
|
||||
#define NODEEDITORDOCK_H
|
||||
|
||||
#include <QDockWidget>
|
||||
#include "dock_widget_base.h"
|
||||
#include <QtNodes/GraphicsView>
|
||||
#include <QtNodes/BasicGraphicsScene>
|
||||
using QtNodes::BasicGraphicsScene;
|
||||
using QtNodes::GraphicsView;
|
||||
|
||||
class NodeEditorDock : public QDockWidget
|
||||
|
||||
class NodeEditorDock : public DockWidgetBase
|
||||
{
|
||||
public:
|
||||
NodeEditorDock(BasicGraphicsScene *scene);
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@
|
|||
<file>../assets/play-circle.png</file>
|
||||
<file>../assets/build.png</file>
|
||||
<file>../assets/volume-off.png</file>
|
||||
<file>../assets/file-document-plus-outline.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#include <QPixmap>
|
||||
|
||||
OstHmiDock::OstHmiDock()
|
||||
: QDockWidget(tr("StoryTeller HMI"))
|
||||
: DockWidgetBase(tr("StoryTeller HMI"))
|
||||
{
|
||||
setObjectName("OstHmiDock"); // used to save the state
|
||||
m_uiOstDisplay.setupUi(this);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
#ifndef OSTHMI_DOCK_H
|
||||
#define OSTHMI_DOCK_H
|
||||
|
||||
#include <QDockWidget>
|
||||
#include "dock_widget_base.h"
|
||||
#include "ui_ost-hmi.h"
|
||||
|
||||
class OstHmiDock : public QDockWidget
|
||||
class OstHmiDock : public DockWidgetBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -1,2 +1,47 @@
|
|||
#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
|
||||
#define RESOURCEMODEL_H
|
||||
#ifndef RESOURCE_MODEL_H
|
||||
#define RESOURCE_MODEL_H
|
||||
|
||||
#include <QtGui>
|
||||
#include "story_project.h"
|
||||
|
||||
#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
|
||||
{
|
||||
|
|
@ -39,41 +14,13 @@ public:
|
|||
|
||||
int rowCount(const QModelIndex &) const override { return m_data.count(); }
|
||||
int columnCount(const QModelIndex &) const override { return 3; }
|
||||
QVariant data(const QModelIndex &index, int role) const override {
|
||||
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 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();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
void append(const Resource & res);
|
||||
|
||||
QString GetFileName(int row) {
|
||||
QString n;
|
||||
void Clear();
|
||||
|
||||
if (row < m_data.size())
|
||||
{
|
||||
n = m_data.at(row).file.c_str();
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
QString GetFileName(int row);
|
||||
|
||||
QList<Resource> GetData() const { return m_data; }
|
||||
|
||||
|
|
@ -81,4 +28,4 @@ private:
|
|||
QList<Resource> m_data;
|
||||
};
|
||||
|
||||
#endif // RESOURCEMODEL_H
|
||||
#endif // RESOURCE_MODEL_H
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
|
||||
ResourcesDock::ResourcesDock()
|
||||
: QDockWidget(tr("Resources"))
|
||||
ResourcesDock::ResourcesDock(StoryProject &project)
|
||||
: m_project(project)
|
||||
, DockWidgetBase(tr("Resources"))
|
||||
{
|
||||
setObjectName("ResourcesDock"); // used to save the state
|
||||
|
||||
|
|
@ -16,13 +17,51 @@ ResourcesDock::ResourcesDock()
|
|||
".",
|
||||
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;
|
||||
res.format = "BMP";
|
||||
res.file = fileName.toStdString();
|
||||
|
||||
res.file = p.filename();
|
||||
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
|
||||
#define RESOURCESDOCK_H
|
||||
|
||||
#include <QDockWidget>
|
||||
#include "dock_widget_base.h"
|
||||
#include "ui_ost-resources.h"
|
||||
#include "resource_model.h"
|
||||
#include <filesystem>
|
||||
|
||||
class ResourcesDock : public QDockWidget
|
||||
class ResourcesDock : public DockWidgetBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ResourcesDock();
|
||||
ResourcesDock(StoryProject &project);
|
||||
|
||||
void Initialize();
|
||||
|
||||
QList<Resource> GetResources() const { return m_resourcesModel.GetData(); }
|
||||
|
||||
ResourceModel &getModel() { return m_resourcesModel; }
|
||||
|
||||
void SaveToProject();
|
||||
|
||||
public slots:
|
||||
void slotClear();
|
||||
|
||||
private:
|
||||
// Resources
|
||||
StoryProject &m_project;
|
||||
Ui::ostResources m_uiOstResources;
|
||||
ResourceModel m_resourcesModel;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ mov R0,R2 ; copy R2 into R0 (NO blank space between , and R2)
|
|||
|
||||
|
||||
ScriptEditorDock::ScriptEditorDock()
|
||||
: QDockWidget(tr("Script editor"))
|
||||
: DockWidgetBase(tr("Script editor"))
|
||||
{
|
||||
setObjectName("ScriptEditorDock"); // used to save the state
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#ifndef SCRIPTEDITOR_H
|
||||
#define SCRIPTEDITOR_H
|
||||
|
||||
#include <QDockWidget>
|
||||
#include "dock_widget_base.h"
|
||||
#include "code_editor.h"
|
||||
#include "highlighter.h"
|
||||
|
||||
class ScriptEditorDock : public QDockWidget
|
||||
class ScriptEditorDock : public DockWidgetBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
|
|
|||
|
|
@ -311,8 +311,11 @@ QJsonObject StoryGraphModel::saveNode(NodeId const nodeId) const
|
|||
{
|
||||
QJsonObject nodeJson;
|
||||
|
||||
|
||||
nodeJson["id"] = static_cast<qint64>(nodeId);
|
||||
|
||||
nodeJson["internal-data"] = _models.at(nodeId)->save();
|
||||
|
||||
{
|
||||
QPointF const pos = nodeData(nodeId, NodeRole::Position).value<QPointF>();
|
||||
|
||||
|
|
@ -321,8 +324,8 @@ QJsonObject StoryGraphModel::saveNode(NodeId const nodeId) const
|
|||
posJson["y"] = pos.y();
|
||||
nodeJson["position"] = posJson;
|
||||
|
||||
// nodeJson["inPortCount"] = QString::number(_nodePortCounts[nodeId].in);
|
||||
// nodeJson["outPortCount"] = QString::number(_nodePortCounts[nodeId].out);
|
||||
nodeJson["inPortCount"] = QString::number(nodeData(nodeId, NodeRole::InPortCount).value<int>());
|
||||
nodeJson["outPortCount"] = QString::number(nodeData(nodeId, NodeRole::OutPortCount).value<int>());
|
||||
}
|
||||
|
||||
return nodeJson;
|
||||
|
|
|
|||
|
|
@ -3,22 +3,36 @@
|
|||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <filesystem>
|
||||
#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)
|
||||
{
|
||||
std::ifstream f(file_path);
|
||||
bool success = false;
|
||||
|
||||
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 {
|
||||
|
||||
|
||||
nlohmann::json j = nlohmann::json::parse(f);
|
||||
|
||||
m_nodes.clear();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
struct StoryNode
|
||||
{
|
||||
|
|
@ -43,9 +44,10 @@ struct Resource
|
|||
|
||||
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::string m_type;
|
||||
|
|
@ -57,6 +59,12 @@ struct StoryProject
|
|||
|
||||
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);
|
||||
void CreateTree();
|
||||
void Clear() {
|
||||
|
|
@ -66,16 +74,20 @@ struct StoryProject
|
|||
|
||||
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 GetFileName(const std::string &path);
|
||||
static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace);
|
||||
|
||||
|
||||
public:
|
||||
static void EraseString(std::string &theString, const std::string &toErase);
|
||||
static std::string ToUpper(const std::string &input);
|
||||
private:
|
||||
|
||||
std::filesystem::path m_imagesPath;
|
||||
std::filesystem::path m_soundsPath;
|
||||
};
|
||||
|
||||
#endif // STORY_PROJECT_H
|
||||
|
|
|
|||
|
|
@ -10,65 +10,55 @@ void ToolBar::createActions(QMenuBar *menuBar)
|
|||
{
|
||||
QMenu *fileMenu = menuBar->addMenu(tr("&File"));
|
||||
|
||||
// const QIcon newIcon = QIcon::fromTheme("document-new", QIcon(":/images/new.png"));
|
||||
// QAction *newLetterAct = new QAction(newIcon, tr("&New Letter"), this);
|
||||
// newLetterAct->setShortcuts(QKeySequence::New);
|
||||
// newLetterAct->setStatusTip(tr("Create a new form letter"));
|
||||
// connect(newLetterAct, &QAction::triggered, this, &MainWindow::newLetter);
|
||||
// fileMenu->addAction(newLetterAct);
|
||||
// fileToolBar->addAction(newLetterAct);
|
||||
// ------------ New
|
||||
{
|
||||
const QIcon icon = QIcon::fromTheme("document-save", QIcon(":/assets/file-document-plus-outline.svg"));
|
||||
QAction *act = new QAction(icon, tr("&New project"), this);
|
||||
act->setShortcuts(QKeySequence::Save);
|
||||
act->setStatusTip(tr("Create a new project"));
|
||||
connect(act, &QAction::triggered, this, &ToolBar::sigNew);
|
||||
fileMenu->addAction(act);
|
||||
addAction(act);
|
||||
}
|
||||
|
||||
// ------------ Save
|
||||
{
|
||||
const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(":/assets/floppy.svg"));
|
||||
QAction *saveAct = new QAction(saveIcon, tr("&Save..."), this);
|
||||
saveAct->setShortcuts(QKeySequence::Save);
|
||||
saveAct->setStatusTip(tr("Save the current project"));
|
||||
connect(saveAct, &QAction::triggered, this, &ToolBar::sigSave);
|
||||
fileMenu->addAction(saveAct);
|
||||
addAction(saveAct);
|
||||
const QIcon icon = QIcon::fromTheme("document-save", QIcon(":/assets/floppy.svg"));
|
||||
QAction *act = new QAction(icon, tr("&Save project"), this);
|
||||
act->setShortcuts(QKeySequence::Save);
|
||||
act->setStatusTip(tr("Save the current project"));
|
||||
connect(act, &QAction::triggered, this, &ToolBar::sigSave);
|
||||
fileMenu->addAction(act);
|
||||
addAction(act);
|
||||
}
|
||||
// ------------ Open
|
||||
{
|
||||
const QIcon saveIcon = QIcon::fromTheme("document-open", QIcon(":/assets/folder-open.svg"));
|
||||
QAction *openAct = new QAction(saveIcon, tr("&Open..."), this);
|
||||
openAct->setShortcuts(QKeySequence::Open);
|
||||
openAct->setStatusTip(tr("Open the current project"));
|
||||
connect(openAct, &QAction::triggered, this, &ToolBar::sigOpen);
|
||||
fileMenu->addAction(openAct);
|
||||
addAction(openAct);
|
||||
const QIcon icon = QIcon::fromTheme("document-open", QIcon(":/assets/folder-open.svg"));
|
||||
QAction *act = new QAction(icon, tr("&Open project"), this);
|
||||
act->setShortcuts(QKeySequence::Open);
|
||||
act->setStatusTip(tr("Open an existing project"));
|
||||
connect(act, &QAction::triggered, this, &ToolBar::sigOpen);
|
||||
fileMenu->addAction(act);
|
||||
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();
|
||||
|
||||
QAction *quitAct = fileMenu->addAction(tr("&Quit"), this, &QWidget::close);
|
||||
quitAct->setShortcuts(QKeySequence::Quit);
|
||||
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();
|
||||
|
||||
m_windowsMenu = menuBar->addMenu(tr("&Windows"));
|
||||
|
||||
QMenu *helpMenu = menuBar->addMenu(tr("&Help"));
|
||||
|
||||
QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &ToolBar::sigAbout);
|
||||
aboutAct->setStatusTip(tr("Show the application's About box"));
|
||||
}
|
||||
|
||||
void ToolBar::AddDockToMenu(QAction *action)
|
||||
{
|
||||
m_windowsMenu->addAction(action);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,16 @@ public:
|
|||
ToolBar();
|
||||
void createActions(QMenuBar *menuBar);
|
||||
|
||||
void AddDockToMenu(QAction *action);
|
||||
|
||||
signals:
|
||||
void sigNew();
|
||||
void sigSave();
|
||||
void sigOpen();
|
||||
void sigAbout();
|
||||
|
||||
private:
|
||||
QMenu *m_windowsMenu;
|
||||
};
|
||||
|
||||
#endif // TOOLBAR_H
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include "vm_dock.h"
|
||||
|
||||
VmDock::VmDock(Chip32::Assembler &assembler)
|
||||
: QDockWidget(tr("Virtual Machine"))
|
||||
: DockWidgetBase(tr("Virtual Machine"))
|
||||
{
|
||||
setObjectName("VirtualMachineDock"); // used to save the state
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
#ifndef VMDOCK_H
|
||||
#define VMDOCK_H
|
||||
|
||||
#include <QDockWidget>
|
||||
#include "dock_widget_base.h"
|
||||
#include "ui_ost-vm.h"
|
||||
|
||||
#include "chip32_assembler.h"
|
||||
|
||||
class VmDock : public QDockWidget
|
||||
class VmDock : public DockWidgetBase
|
||||
{
|
||||
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