~saiko/mcrestool

35480b2dfb453e29bcbfc35d3f34b3ede1788c21 — 2xsaiko 3 months ago a5903d6
Abstractions?
M logic/src/datasource/dir.rs => logic/src/datasource/dir.rs +6 -4
@@ 26,7 26,7 @@ impl DirDataSource {
        let result = fs::read_dir(self.get_full_path(path)?)?;
        Ok(result
            .filter_map(|e| e.ok())
            .map(|e| e.path())
            .map(|e| e.path().strip_prefix(&self.dir).unwrap().to_path_buf())
            .collect())
    }



@@ 51,18 51,20 @@ impl DirDataSource {
    }

    fn get_full_path(&self, path: impl AsRef<Path>) -> Result<PathBuf, Error> {
        Ok(self.dir.join(normalize_path(&path).ok_or_else(|| Error::InvalidPath(path.as_ref().to_path_buf()))?))
        let buf = self.dir.join(normalize_path(&path).ok_or_else(|| Error::InvalidPath(path.as_ref().to_path_buf()))?.strip_prefix("/").unwrap());
        println!("{}", buf.to_str().unwrap());
        Ok(buf)
    }
}

pub enum Error {
    RootDirNotFound(io::Error),
    InvalidPath(PathBuf),
    IoError(io::Error),
    Io(io::Error),
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self {
        Error::IoError(err)
        Error::Io(err)
    }
}
\ No newline at end of file

M logic/src/datasource/ffi.rs => logic/src/datasource/ffi.rs +9 -7
@@ 1,10 1,12 @@
#![allow(clippy::missing_safety_doc)]

use std::{mem, slice};
use std::ffi::{CStr, CString};
use std::io::{Read, Write};
use std::os::raw::{c_char, c_void};
use std::ptr::{drop_in_place, null, null_mut};

use crate::datasource::{DataSource, Error, OpenOptions};
use crate::datasource::{DataSource, OpenOptions};
use crate::datasource::dir::DirDataSource;
use crate::datasource::resfile::ResFile;
use crate::datasource::zip::ZipDataSource;


@@ 23,7 25,7 @@ pub unsafe extern "C" fn datasource_dir_create(path: *const c_char) -> *mut Data
    if path.is_null() { return null_mut(); }
    let raw = CStr::from_ptr(path);

    let ds = DataSource::Dir(try_ffi!(DirDataSource::new(&*raw.to_string_lossy())));
    let ds = DataSource::Dir(try_ffi!(DirDataSource::new(raw.to_str().unwrap())));

    Box::into_raw(Box::new(ds))
}


@@ 34,7 36,7 @@ pub unsafe extern "C" fn datasource_zip_create(path: *const c_char) -> *mut Data
    if path.is_null() { return null_mut(); }
    let raw = CStr::from_ptr(path);

    let ds = DataSource::Zip(try_ffi!(ZipDataSource::new(&*raw.to_string_lossy())));
    let ds = DataSource::Zip(try_ffi!(ZipDataSource::new(raw.to_str().unwrap())));

    Box::into_raw(Box::new(ds))
}


@@ 53,7 55,7 @@ pub unsafe extern "C" fn datasource_open_file(ds: &mut DataSource, path: *const 
    if path.is_null() { return null_mut(); }
    let raw = CStr::from_ptr(path);

    let file = try_ffi!(ds.open(&*raw.to_string_lossy(), opts));
    let file = try_ffi!(ds.open(raw.to_str().unwrap(), opts));

    Box::into_raw(Box::new(file))
}


@@ 64,8 66,8 @@ pub unsafe extern "C" fn datasource_list_dir(ds: &mut DataSource, path: *const c
    if path.is_null() { return null_mut(); }
    let raw = CStr::from_ptr(path);

    let mut dir_list = try_ffi!(ds.list_dir(&*raw.to_string_lossy())).into_iter()
        .map(|s| CString::new(&*s.file_name().unwrap().to_string_lossy()).expect("Invalid 0-char in file name"))
    let mut dir_list = try_ffi!(ds.list_dir(raw.to_str().unwrap())).into_iter()
        .map(|s| CString::new(&*s.file_name().unwrap().to_str().unwrap()).expect("Invalid 0-char in file name"))
        .collect::<Vec<_>>();

    dir_list.shrink_to_fit();


@@ 105,7 107,7 @@ pub unsafe extern "C" fn datasource_delete_file(ds: &mut DataSource, path: *cons
    if path.is_null() { return false; }
    let raw = CStr::from_ptr(path);

    try_ffi!(ds.delete_file(&*raw.to_string_lossy()), false);
    try_ffi!(ds.delete_file(raw.to_str().unwrap()), false);

    true
}

M logic/src/datasource/mod.rs => logic/src/datasource/mod.rs +10 -10
@@ 90,7 90,7 @@ pub enum Error {
    NotFound,
    PermissionDenied,
    ReadOnly,
    IoError,
    Io,
}

impl From<dir::Error> for Error {


@@ 98,11 98,11 @@ impl From<dir::Error> for Error {
        match err {
            dir::Error::RootDirNotFound(_) => unreachable!(),
            dir::Error::InvalidPath(p) => Error::InvalidPath(p),
            dir::Error::IoError(e) => {
            dir::Error::Io(e) => {
                match e.kind() {
                    ErrorKind::NotFound => Error::NotFound,
                    ErrorKind::PermissionDenied => Error::PermissionDenied,
                    _ => Error::IoError
                    _ => Error::Io
                }
            }
        }


@@ 112,17 112,17 @@ impl From<dir::Error> for Error {
impl From<zip::Error> for Error {
    fn from(err: zip::Error) -> Self {
        match err {
            zip::Error::ZipError(ZipError::FileNotFound) => Error::NotFound,
            zip::Error::ZipError(ZipError::InvalidArchive(_)) |
            zip::Error::ZipError(ZipError::UnsupportedArchive(_)) => Error::IoError,
            zip::Error::ZipError(ZipError::Io(e)) => {
            zip::Error::Zip(ZipError::FileNotFound) => Error::NotFound,
            zip::Error::Zip(ZipError::InvalidArchive(_)) |
            zip::Error::Zip(ZipError::UnsupportedArchive(_)) => Error::Io,
            zip::Error::Zip(ZipError::Io(e)) => {
                match e.kind() {
                    ErrorKind::NotFound => Error::NotFound,
                    ErrorKind::PermissionDenied => Error::PermissionDenied,
                    _ => Error::IoError
                    _ => Error::Io
                }
            }
            zip::Error::IoError(_) => unreachable!(),
            zip::Error::Io(_) => unreachable!(),
            zip::Error::InvalidPath(p) => Error::InvalidPath(p)
        }
    }


@@ 135,7 135,7 @@ impl FfiError for Error {
            Error::NotFound => McrtError::NotFound,
            Error::PermissionDenied => McrtError::PermissionDenied,
            Error::ReadOnly => McrtError::ReadOnly,
            Error::IoError => McrtError::IoError,
            Error::Io => McrtError::Io,
        }
    }
}

M logic/src/datasource/zip.rs => logic/src/datasource/zip.rs +13 -13
@@ 39,25 39,25 @@ impl ZipDataSource {

    fn resolve_path_for_archive(path: impl AsRef<Path>) -> Option<String> {
        let pb = normalize_path(path)?;
        Some(pb.strip_prefix("/").unwrap().to_string_lossy().into_owned())
        Some(pb.strip_prefix("/").unwrap().to_str().unwrap().to_string())
    }
}

pub enum Error {
    InvalidPath(PathBuf),
    IoError(io::Error),
    ZipError(zip::result::ZipError),
    Io(io::Error),
    Zip(zip::result::ZipError),
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self {
        Error::IoError(err)
        Error::Io(err)
    }
}

impl From<zip::result::ZipError> for Error {
    fn from(err: zip::result::ZipError) -> Self {
        Error::ZipError(err)
        Error::Zip(err)
    }
}



@@ 66,15 66,15 @@ impl FfiError for Error {
        use zip::result::ZipError;

        match self {
            Error::IoError(e) | Error::ZipError(ZipError::Io(e)) => match e.kind() {
            Error::Io(e) | Error::Zip(ZipError::Io(e)) => match e.kind() {
                ErrorKind::NotFound => McrtError::NotFound,
                ErrorKind::PermissionDenied => McrtError::PermissionDenied,
                _ => McrtError::IoError
                _ => McrtError::Io
            }
            Error::ZipError(ZipError::UnsupportedArchive(text)) => McrtError::UnsupportedZip,
            Error::ZipError(ZipError::InvalidArchive(text)) => McrtError::InvalidZip,
            Error::ZipError(ZipError::FileNotFound) => McrtError::NotFound,
            _ => McrtError::IoError,
            Error::Zip(ZipError::UnsupportedArchive(text)) => McrtError::UnsupportedZip,
            Error::Zip(ZipError::InvalidArchive(text)) => McrtError::InvalidZip,
            Error::Zip(ZipError::FileNotFound) => McrtError::NotFound,
            _ => McrtError::Io,
        }
    }



@@ 82,8 82,8 @@ impl FfiError for Error {
        use zip::result::ZipError;

        match self {
            Error::ZipError(ZipError::UnsupportedArchive(text)) => text,
            Error::ZipError(ZipError::InvalidArchive(text)) => text,
            Error::Zip(ZipError::UnsupportedArchive(text)) => text,
            Error::Zip(ZipError::InvalidArchive(text)) => text,
            _ => self.kind().description(),
        }
    }

M logic/src/ffi.rs => logic/src/ffi.rs +9 -2
@@ 1,5 1,6 @@
use std::os::raw::c_char;
use std::ptr::null;
use std::ffi::CString;

#[no_mangle]
pub static mut MCRT_ERROR: McrtError = McrtError::None;


@@ 12,10 13,16 @@ pub enum McrtError {
    None,
    NotFound,
    PermissionDenied,
    IoError,
    Io,
    UnsupportedZip,
    InvalidZip,
    ReadOnly,
    CorruptedFile,
    NulError,
    Nul,
}

#[no_mangle]
pub unsafe extern "C" fn mcrt_str_delete(text: *const c_char) {
    if text.is_null() { return; }
    drop(CString::from_raw(text as *mut c_char));
}
\ No newline at end of file

M logic/src/ffihelper.rs => logic/src/ffihelper.rs +5 -5
@@ 37,12 37,12 @@ impl McrtError {
            McrtError::None => "",
            McrtError::NotFound => "File not found",
            McrtError::PermissionDenied => "Permission denied",
            McrtError::IoError => "I/O Error",
            McrtError::Io => "I/O Error",
            McrtError::UnsupportedZip => "Unsupported ZIP archive",
            McrtError::InvalidZip => "Invalid ZIP archive",
            McrtError::ReadOnly => "Filesystem is read-only",
            McrtError::CorruptedFile => "Corrupted file",
            McrtError::NulError => "0-byte found in string",
            McrtError::Nul => "0-byte found in string",
        }
    }
}


@@ 58,7 58,7 @@ macro_rules! try_ffi {
    ($e:expr, $rv:expr) => {
        match $e {
            ::std::result::Result::Ok(v) => v,
            ::std::result::Result::Err(e) => {
            ::std::result::Result::Err(e) =>  {
                $crate::ffihelper::set_error_from(e);
                return $rv;
            }


@@ 71,13 71,13 @@ impl FfiError for std::io::Error {
        match self.kind() {
            ErrorKind::NotFound => McrtError::NotFound,
            ErrorKind::PermissionDenied => McrtError::PermissionDenied,
            _ => McrtError::IoError
            _ => McrtError::Io
        }
    }
}

impl FfiError for std::ffi::NulError {
    fn kind(&self) -> McrtError {
        McrtError::NulError
        McrtError::Nul
    }
}
\ No newline at end of file

M logic/src/languagetable/ffi.rs => logic/src/languagetable/ffi.rs +20 -12
@@ 1,3 1,5 @@
#![allow(clippy::missing_safety_doc)]

use std::convert::TryFrom;
use std::ffi::{CStr, CString};
use std::mem;


@@ 19,18 21,30 @@ pub unsafe extern "C" fn languagetable_load_from(ds: &DataSource, dir: *const c_
    if dir.is_null() { return null_mut(); }
    let dir = CStr::from_ptr(dir);

    let lt = try_ffi!(LanguageTable::read_from(ds, &*dir.to_string_lossy()));
    let lt = try_ffi!(LanguageTable::read_from(ds, dir.to_str().unwrap()));

    Box::into_raw(Box::new(lt))
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_load_into(lt: &mut LanguageTable, ds: &DataSource, dir: *const c_char) -> bool {
    clear_error();
    if dir.is_null() { return false; }
    let dir = CStr::from_ptr(dir);

    *lt = try_ffi!(LanguageTable::read_from(ds, dir.to_str().unwrap()), false);
    true
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_write_to(lt: &LanguageTable, ds: &DataSource, dir: *const c_char) -> bool {
    clear_error();
    if dir.is_null() { return false; }
    let dir = CStr::from_ptr(dir);

    try_ffi!(lt.write_to(ds, &*dir.to_string_lossy()), false);
    let dir = dir.to_str().unwrap();
    try_ffi!(ds.create_dir_all(dir), false);
    try_ffi!(lt.write_to(ds, dir), false);

    true
}


@@ 39,14 53,14 @@ pub unsafe extern "C" fn languagetable_write_to(lt: &LanguageTable, ds: &DataSou
pub unsafe extern "C" fn languagetable_add_language(lt: &mut LanguageTable, lang: *const c_char) {
    if lang.is_null() { return; }
    let lang = CStr::from_ptr(lang);
    lt.add_language(&*lang.to_string_lossy());
    lt.add_language(lang.to_str().unwrap());
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_add_localization_key(lt: &mut LanguageTable, key: *const c_char) {
    if key.is_null() { return; }
    let key = CStr::from_ptr(key);
    lt.add_localization_key(&*key.to_string_lossy());
    lt.add_localization_key(key.to_str().unwrap());
}

#[no_mangle]


@@ 55,7 69,7 @@ pub unsafe extern "C" fn languagetable_insert(lt: &mut LanguageTable, lang: *con
    let lang = CStr::from_ptr(lang);
    let key = CStr::from_ptr(key);
    let name = CStr::from_ptr(name);
    lt.insert(&*lang.to_string_lossy(), &*key.to_string_lossy(), &*name.to_string_lossy());
    lt.insert(lang.to_str().unwrap(), key.to_str().unwrap(), name.to_str().unwrap());
}

#[no_mangle]


@@ 64,7 78,7 @@ pub unsafe extern "C" fn languagetable_get(lt: &mut LanguageTable, lang: *const 
    if lang.is_null() || key.is_null() { return null(); }
    let lang = CStr::from_ptr(lang);
    let key = CStr::from_ptr(key);
    let entry = lt.get(&*lang.to_string_lossy(), &*key.to_string_lossy());
    let entry = lt.get(lang.to_str().unwrap(), key.to_str().unwrap());
    match entry {
        None => null(),
        Some(s) => try_ffi!(CString::new(s)).into_raw(),


@@ 93,12 107,6 @@ pub unsafe extern "C" fn languagetable_get_row_name(lt: &mut LanguageTable, idx:


#[no_mangle]
pub unsafe extern "C" fn languagetable_content_delete(text: *const c_char) {
    if text.is_null() { return; }
    drop(CString::from_raw(text as *mut c_char));
}

#[no_mangle]
pub unsafe extern "C" fn languagetable_row_count(lt: &mut LanguageTable) -> usize {
    lt.row_count()
}

M logic/src/languagetable/mod.rs => logic/src/languagetable/mod.rs +8 -7
@@ 54,6 54,7 @@ impl LanguageTable {
    }

    fn replace_language(&mut self, lang: &str, map: HashMap<String, String>) -> Option<HashMap<String, String>> {
        self.add_language(lang);
        map.keys().for_each(|el| self.add_localization_key(el));
        self.table.insert(lang.to_owned(), map)
    }


@@ 82,7 83,7 @@ impl LanguageTable {
        let mut lt = LanguageTable::new();
        for x in ds.list_dir(&dir)? {
            if x.extension().unwrap().to_str() == Some("json") {
                let file_name = x.file_name().unwrap().to_string_lossy();
                let file_name = x.file_name().unwrap().to_str().unwrap();
                let lang = &file_name[..file_name.len() - 5];
                let mut file = ds.open(&x, OpenOptions::reading())?;
                let map: HashMap<String, String> = serde_json::from_reader(&mut file)?;


@@ 94,27 95,27 @@ impl LanguageTable {
}

pub enum Error {
    IoError(datasource::Error),
    SerdeError(serde_json::Error),
    Io(datasource::Error),
    Serde(serde_json::Error),
}

impl From<datasource::Error> for Error {
    fn from(err: datasource::Error) -> Self {
        Error::IoError(err)
        Error::Io(err)
    }
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Error::SerdeError(err)
        Error::Serde(err)
    }
}

impl FfiError for Error {
    fn kind(&self) -> McrtError {
        match self {
            Error::IoError(e) => e.kind(),
            Error::SerdeError(_) => McrtError::CorruptedFile,
            Error::Io(e) => e.kind(),
            Error::Serde(_) => McrtError::CorruptedFile,
        }
    }
}
\ No newline at end of file

M logic/src/lib.rs => logic/src/lib.rs +3 -0
@@ 1,7 1,10 @@
#![allow(clippy::new_without_default)]

#[macro_use]
pub mod ffihelper;

pub mod datasource;
pub mod languagetable;
pub mod restree;

pub mod ffi;
\ No newline at end of file

A logic/src/restree/ffi.rs => logic/src/restree/ffi.rs +10 -0
@@ 0,0 1,10 @@
use std::os::raw::c_char;

use crate::restree::FileTree;
use std::ptr::null;
use std::ffi::CString;

#[no_mangle]
pub extern "C" fn filetree_get_name(rt: &FileTree) -> *const c_char {
    null()
}
\ No newline at end of file

A logic/src/restree/mod.rs => logic/src/restree/mod.rs +71 -0
@@ 0,0 1,71 @@
use std::ffi::CString;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::path::PathBuf;
use crate::datasource::DataSource;

pub mod ffi;

pub struct FileTreeRoot<'a> {
    ds: &'a DataSource,

}

pub struct FileTree {
    name: String,
    ft_path: FileTreePath,
    path: Option<PathBuf>,
    file_type: FileType,
    children: Vec<FileTree>,
}

#[repr(u8)]
pub enum FileType {
    None,
    Language,
}

#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct FileTreePath(Vec<String>);

impl Default for FileTreePath {
    fn default() -> Self { FileTreePath(vec![]) }
}

impl Display for FileTreePath {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        for x in &self.0 {
            write!(f, "/{}", x)?;
        }
        Ok(())
    }
}

impl FileTreePath {
    pub fn is_root(&self) -> bool {
        self.0.is_empty()
    }

    pub fn parent(&self) -> Option<FileTreePath> {
        if !self.is_root() {
            Some(FileTreePath(self.0[..self.0.len() - 1].to_vec()))
        } else {
            None
        }
    }

    pub fn child(&self, name: &str) -> FileTreePath {
        let mut vec = self.0.clone();
        vec.push(name.to_owned());
        FileTreePath(vec)
    }

    pub fn full_path(&self) -> String {
        format!("{}", self)
    }

    pub fn path(&self) -> Option<&str> {
        self.0.last().map(|s| &**s)
    }
}

M ui/CMakeLists.txt => ui/CMakeLists.txt +1 -2
@@ 32,7 32,6 @@ set(mcrestool_ui_SRC
        src/ui/shapedcraftingwidget.cpp src/ui/shapedcraftingwidget.h
        src/ui/recipeeditextensionwidget.cpp src/ui/recipeeditextensionwidget.h
        src/util.h
        src/fs/langfile.cpp src/fs/langfile.h
        src/result.h
        src/except.h
        src/ui/smeltingwidget.cpp src/ui/smeltingwidget.h


@@ 40,7 39,7 @@ set(mcrestool_ui_SRC
        src/project/projectsource.cpp src/project/projectsource.h
        src/fs/datasource.cpp src/fs/datasource.h
        src/project/languagetablecontainer.cpp src/project/languagetablecontainer.h
        src/project/project.h src/fs/resfile.cpp src/fs/resfile.h)
        src/project/project.h src/fs/resfile.cpp src/fs/resfile.h src/ui/geneditorwindow.cpp src/ui/geneditorwindow.h src/util.cpp)

add_executable(mcrestool_ui ${mcrestool_ui_SRC})


M ui/src/fs/datasource.cpp => ui/src/fs/datasource.cpp +5 -0
@@ 9,6 9,7 @@ DataSourceW::~DataSourceW() {
DataSourceW* DataSourceW::from_dir(const QString& dir, QObject* parent) {
    DataSource* p_source = datasource_dir_create(dir.toLocal8Bit());
    if (!p_source) {
        printf("error %d while trying to open datasource: %s\n", MCRT_ERROR, MCRT_ERROR_TEXT);
        return nullptr;
    }
    return new DataSourceW(p_source, parent);


@@ 44,3 45,7 @@ QStringList DataSourceW::list_dir(const QString& path) {
bool DataSourceW::delete_file(const QString& path) {
    return datasource_delete_file(ds, path.toLocal8Bit());
}

DataSource* DataSourceW::inner() {
    return ds;
}

M ui/src/fs/datasource.h => ui/src/fs/datasource.h +2 -0
@@ 28,6 28,8 @@ public:

    bool read_only();

    DataSource* inner();

private:
    DataSource* ds;


D ui/src/fs/langfile.cpp => ui/src/fs/langfile.cpp +0 -44
@@ 1,44 0,0 @@
#include <QJsonDocument>
#include <QFile>
#include <QJsonObject>
#include "src/util.h"
#include "langfile.h"

namespace langfile {
    QMap<QString, QString> load_from_json(DataSourceW& source, const QString& path) {
        QMap<QString, QString> map;
        QFile file(path);
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            unimplemented(); // TODO: implement error handling
        }
        QString content = file.readAll();
        file.close();
        QJsonDocument doc = QJsonDocument::fromJson(content.toUtf8());
        if (!doc.isObject()) {
            unimplemented(); // TODO: implement error handling
        }
        QJsonObject obj = doc.object();
        auto iterator = obj.begin();
        while (iterator != obj.end()) {
            map.insert(iterator.key(), iterator->toString());
            iterator += 1;
        }
        return map;
    }

    void save_to_json(DataSourceW& source, const QString& path, const QMap<QString, QString>& localization) {
        QIODevice* file = source.file(path);
        if (!file->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
            unimplemented(); // TODO: implement error handling
        }
        QJsonObject obj;
        auto iterator = localization.begin();
        while (iterator != localization.end()) {
            obj.insert(iterator.key(), iterator.value());
        }
        QJsonDocument doc = QJsonDocument(obj);
        QString str = doc.toJson();
        file->write(str.toUtf8());
        file->close();
    }
}

D ui/src/fs/langfile.h => ui/src/fs/langfile.h +0 -13
@@ 1,13 0,0 @@
#ifndef MCRESTOOL_LANGFILE_H
#define MCRESTOOL_LANGFILE_H

#include <QMap>
#include "datasource.h"

namespace langfile {
    QMap<QString, QString> load_from_json(DataSourceW& source, const QString& path);

    void save_to_json(DataSourceW& source, const QString& path, const QMap<QString, QString>& localization);
}

#endif //MCRESTOOL_LANGFILE_H

M ui/src/fs/resfile.cpp => ui/src/fs/resfile.cpp +1 -1
@@ 39,7 39,7 @@ void ResFileW::close() {
    }
}

static OpenOptions as_open_options(QIODevice::OpenMode om) {
OpenOptions as_open_options(QIODevice::OpenMode om) {
    OpenOptions opts = OpenOptions();
    opts.read = om & QIODevice::ReadOnly;
    opts.write = om & QIODevice::WriteOnly;

M ui/src/fs/resfile.h => ui/src/fs/resfile.h +1 -1
@@ 34,6 34,6 @@ private:

};

static OpenOptions as_open_options(QIODevice::OpenMode om);
OpenOptions as_open_options(QIODevice::OpenMode om);

#endif //MCRESTOOL_RESFILE_H

M ui/src/model/languagetablemodel.cpp => ui/src/model/languagetablemodel.cpp +1 -1
@@ 32,7 32,7 @@ QVariant LanguageTableModel::data(const QModelIndex& index, int role) const {
        QString row = get_row_name(index.row());
        const char* str = languagetable_get(lt, column.toLocal8Bit(), row.toLocal8Bit());
        QString content = QString(str);
        languagetable_content_delete(str);
        mcrt_str_delete(str);
        if (!content.isNull()) {
            return content;
        }

M ui/src/model/languagetablemodel.h => ui/src/model/languagetablemodel.h +1 -1
@@ 32,7 32,7 @@ public:

    void add_language(QString language);

    [[nodiscard]] LanguageTable* data() ;
    [[nodiscard]] LanguageTable* data();

signals:


M ui/src/project/languagetablecontainer.cpp => ui/src/project/languagetablecontainer.cpp +11 -7
@@ 1,5 1,4 @@
#include "languagetablecontainer.h"
#include "src/fs/langfile.h"

LanguageTableContainer::LanguageTableContainer(
    ProjectSource* src,


@@ 41,9 40,9 @@ void LanguageTableContainer::delete_file() {
    if (read_only()) return;

    if (persistent()) {
        QStringList files = src->data_source().list_dir("/assets/" + domain + "/lang/");
        QStringList files = src->data_source()->list_dir("/assets/" + domain + "/lang/");
        for (const auto& str: files) {
            src->data_source().delete_file(str);
            src->data_source()->delete_file(str);
        }
    }
    _deleted = true;


@@ 53,13 52,18 @@ void LanguageTableContainer::delete_file() {
void LanguageTableContainer::save() {
    if (read_only()) return;

    for (auto lang: languagetable_col_count(lt->data())) {
        QMap<QString, QString> lang_map = lt->data().column(lang);
        langfile::save_to_json(src->data_source(), lang + ".json", lang_map);
    }
    languagetable_write_to(lt->data(), src->data_source()->inner(), ("/assets/" + domain + "/lang/").toLocal8Bit());

    _persistent = true;
    _changed = false;
}

void LanguageTableContainer::load() {
    languagetable_load_into(lt->data(), src->data_source()->inner(),  ("/assets/" + domain + "/lang/").toLocal8Bit());

    _persistent = true;
    _changed = false;
    emit lt->layoutChanged();
}

void LanguageTableContainer::on_changed() {

M ui/src/project/languagetablecontainer.h => ui/src/project/languagetablecontainer.h +2 -0
@@ 27,6 27,8 @@ public:

    void save();

    void load();

public slots:

    void on_changed();

M ui/src/project/projectsource.cpp => ui/src/project/projectsource.cpp +3 -3
@@ 1,11 1,11 @@
#include "projectsource.h"

ProjectSource::ProjectSource(DataSourceW& src, const QString& name, QObject* parent) : QObject(parent), name(name), src(src) {
ProjectSource::ProjectSource(DataSourceW* src, const QString& name, QObject* parent) : QObject(parent), name(name), src(src) {

}

bool ProjectSource::read_only() {
    return src.read_only();
    return src->read_only();
}

LanguageTableContainer* ProjectSource::get_language_table(const QString& domain) {


@@ 15,6 15,6 @@ LanguageTableContainer* ProjectSource::get_language_table(const QString& domain)
    return languages.value(domain, nullptr);
}

DataSourceW& ProjectSource::data_source() {
DataSourceW* ProjectSource::data_source() {
    return src;
}

M ui/src/project/projectsource.h => ui/src/project/projectsource.h +3 -3
@@ 14,7 14,7 @@ class ProjectSource : public QObject {
Q_OBJECT

public:
    explicit ProjectSource(DataSourceW& src, const QString& name, QObject* parent = nullptr);
    explicit ProjectSource(DataSourceW* src, const QString& name, QObject* parent = nullptr);

    LanguageTableContainer* get_language_table(const QString& domain);



@@ 22,12 22,12 @@ public:

    bool changed();

    DataSourceW& data_source();
    DataSourceW* data_source();

private:
    QString name;

    DataSourceW& src;
    DataSourceW* src;

    QMap<QString, LanguageTableContainer*> languages;


A ui/src/ui/geneditorwindow.cpp => ui/src/ui/geneditorwindow.cpp +1 -0
@@ 0,0 1,1 @@
#include "geneditorwindow.h"

A ui/src/ui/geneditorwindow.h => ui/src/ui/geneditorwindow.h +15 -0
@@ 0,0 1,15 @@
#ifndef MCRESTOOL_GENEDITORWINDOW_H
#define MCRESTOOL_GENEDITORWINDOW_H

class GenEditorWindow {

public:
    virtual ~GenEditorWindow() = default;

    virtual void save() = 0;

    virtual void reload() = 0;

};

#endif //MCRESTOOL_GENEDITORWINDOW_H

M ui/src/ui/languagetablewindow.cpp => ui/src/ui/languagetablewindow.cpp +11 -0
@@ 1,6 1,7 @@
#include <QInputDialog>
#include "languagetablewindow.h"
#include "ui_languagetablewindow.h"
#include "src/util.h"

LanguageTableWindow::LanguageTableWindow(LanguageTableContainer* ltc, QWidget* parent) : QWidget(parent),
                                                                                         ui(new Ui::LanguageTableWindow),


@@ 29,4 30,14 @@ void LanguageTableWindow::add_locale_key() {
    }
}

void LanguageTableWindow::save() {
    ltc->save();
    check_for_error(this);
}

void LanguageTableWindow::reload() {
    ltc->load();
    check_for_error(this);
}

LanguageTableWindow::~LanguageTableWindow() = default;

M ui/src/ui/languagetablewindow.h => ui/src/ui/languagetablewindow.h +6 -1
@@ 4,12 4,13 @@
#include <QScopedPointer>
#include <QWidget>
#include "src/project/languagetablecontainer.h"
#include "geneditorwindow.h"

namespace Ui {
    class LanguageTableWindow;
}

class LanguageTableWindow : public QWidget {
class LanguageTableWindow : public QWidget, public GenEditorWindow {
Q_OBJECT

public:


@@ 21,6 22,10 @@ public:

    void add_locale_key();

    void save() override;

    void reload() override;

private:
    QScopedPointer<Ui::LanguageTableWindow> ui;
    LanguageTableContainer* ltc;

M ui/src/ui/mainwindow.cpp => ui/src/ui/mainwindow.cpp +26 -7
@@ 2,12 2,14 @@
#include "ui_mainwindow.h"
#include "languagetablewindow.h"
#include "recipeeditwindow.h"
#include "src/util.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QScreen>
#include <QFileDialog>
#include <QInputDialog>
#include <iostream>
#include <QDebug>

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);


@@ 15,7 17,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
    connect(ui->action_quit, SIGNAL(triggered()), this, SLOT(quit()));
    connect(ui->action_open, SIGNAL(triggered()), this, SLOT(open()));
    connect(ui->action_save, SIGNAL(triggered()), this, SLOT(save()));
    connect(ui->action_save_as, SIGNAL(triggered()), this, SLOT(save_as()));
    connect(ui->action_save_workspace_as, SIGNAL(triggered()), this, SLOT(save_as()));
    connect(ui->action_add_res_file, SIGNAL(triggered()), this, SLOT(add_res_file()));
    connect(ui->action_add_res_folder, SIGNAL(triggered()), this, SLOT(add_res_folder()));
    connect(ui->action_about_qt, &QAction::triggered, &QApplication::aboutQt);


@@ 25,14 27,18 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
    connect(ui->action_game_objects, SIGNAL(triggered(bool)), this, SLOT(show_game_objects(bool)));
    connect(ui->game_objects, SIGNAL(visibilityChanged(bool)), this, SLOT(show_game_objects(bool)));

//    auto* ltw = new LanguageTableWindow(new LanguageTableContainer(new ProjectSource()), this);
//    ui->mdi_area->addSubWindow(ltw);
    connect(ui->mdi_area, SIGNAL(subWindowActivated(QMdiSubWindow * )), this, SLOT(sub_window_focus_change(QMdiSubWindow * )));

    auto* ds = DataSourceW::from_dir("../../testres", this);
    auto* ltw = new LanguageTableWindow(new LanguageTableContainer(new ProjectSource(ds, "testres", this), "testmod", this), this);
    ltw->reload();
    ui->mdi_area->addSubWindow(ltw);

    auto* crw = new RecipeEditWindow(this);
    ui->mdi_area->addSubWindow(crw);

//    connect(ui->action_insert_language, &QAction::triggered, ltw, &LanguageTableWindow::add_language);
//    connect(ui->action_insert_translation_key, &QAction::triggered, ltw, &LanguageTableWindow::add_locale_key);
    connect(ui->action_insert_language, &QAction::triggered, ltw, &LanguageTableWindow::add_language);
    connect(ui->action_insert_translation_key, &QAction::triggered, ltw, &LanguageTableWindow::add_locale_key);

    ui->res_tree_view->setModel(new ResourceTree(this));
}


@@ 50,14 56,22 @@ void MainWindow::quit() {
}

void MainWindow::open() {

}

void MainWindow::save() {

    QWidget* window = ui->mdi_area->activeSubWindow()->widget();
    GenEditorWindow* editorWindow = dynamic_cast<GenEditorWindow*>(window);
    if (editorWindow) {
        editorWindow->save();
        check_for_error(this);
    } else {
        qDebug() << "Failed to save because" << editorWindow << "is not a GenEditorWindow!";
    }
}

void MainWindow::save_as() {
    QString filename = QFileDialog::getSaveFileName(this, tr("Save Project"), QString(), "mcrestool Project(*.rtp)");
    QString filename = QFileDialog::getSaveFileName(this, tr("Save Workspace"), QString(), "mcrestool Workspace(*.rtw)");
}

void MainWindow::show_resource_tree(bool shown) {


@@ 86,4 100,9 @@ void MainWindow::add_res_folder() {
    QString source = QFileDialog::getExistingDirectory(this, tr("Add Resource Folder"));
}

void MainWindow::sub_window_focus_change(QMdiSubWindow* window) {
    if (window)
        puts(window->widget()->objectName().toLocal8Bit());
}

MainWindow::~MainWindow() = default;

M ui/src/ui/mainwindow.h => ui/src/ui/mainwindow.h +9 -0
@@ 3,6 3,7 @@

#include <QMainWindow>
#include <QScopedPointer>
#include <QMdiSubWindow>
#include "src/model/resourcetree.h"
#include "src/model/languagetablemodel.h"



@@ 20,6 21,12 @@ public:

    void center();

signals:

    void save_clicked();

    void save_all_clicked();

private slots:

    void quit();


@@ 38,6 45,8 @@ private slots:

    void show_game_objects(bool shown);

    void sub_window_focus_change(QMdiSubWindow* window);

private:
    QScopedPointer<Ui::MainWindow> ui;
    // ResourceTree rt;

M ui/src/ui/mainwindow.ui => ui/src/ui/mainwindow.ui +65 -5
@@ 64,11 64,15 @@
    <addaction name="action_open"/>
    <addaction name="separator"/>
    <addaction name="action_save"/>
    <addaction name="action_save_as"/>
    <addaction name="action_save_all"/>
    <addaction name="separator"/>
    <addaction name="action_add_res_file"/>
    <addaction name="action_add_res_folder"/>
    <addaction name="separator"/>
    <addaction name="action_New_Workspace"/>
    <addaction name="action_save_workspace"/>
    <addaction name="action_save_workspace_as"/>
    <addaction name="separator"/>
    <addaction name="action_quit"/>
   </widget>
   <widget class="QMenu" name="menu_edit">


@@ 169,6 173,9 @@
   <property name="text">
    <string>&amp;Quit</string>
   </property>
   <property name="statusTip">
    <string>Quits the program.</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+Q</string>
   </property>


@@ 181,17 188,26 @@
   <property name="text">
    <string>&amp;Save</string>
   </property>
   <property name="statusTip">
    <string>Saves the active editor.</string>
   </property>
   <property name="whatsThis">
    <string/>
   </property>
   <property name="shortcut">
    <string>Ctrl+S</string>
   </property>
  </action>
  <action name="action_save_as">
  <action name="action_save_workspace_as">
   <property name="icon">
    <iconset theme="document-save-as">
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>Save &amp;As…</string>
    <string>Save W&amp;orkspace As…</string>
   </property>
   <property name="statusTip">
    <string>Saves the workspace settings as a new file.</string>
   </property>
  </action>
  <action name="action_insert_language">


@@ 255,19 271,63 @@
  </action>
  <action name="action_add_res_file">
   <property name="icon">
    <iconset theme="document-import"/>
    <iconset theme="document-import">
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>&amp;Add Resource Pack/Mod…</string>
   </property>
   <property name="statusTip">
    <string>Adds a ZIP file to the workspace. Can be used for resource packs, data packs, mod files, or the minecraft.jar.</string>
   </property>
  </action>
  <action name="action_add_res_folder">
   <property name="icon">
    <iconset theme="document-import"/>
    <iconset theme="document-import">
     <normaloff>.</normaloff>.</iconset>
   </property>
   <property name="text">
    <string>Add Resource &amp;Folder…</string>
   </property>
   <property name="statusTip">
    <string>Adds a resource folder. Can be used for src/main/resources in mod projects.</string>
   </property>
  </action>
  <action name="action_save_all">
   <property name="icon">
    <iconset theme="document-save-all"/>
   </property>
   <property name="text">
    <string>Save All</string>
   </property>
   <property name="statusTip">
    <string>Saves all the open editors.</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+Shift+S</string>
   </property>
  </action>
  <action name="action_save_workspace">
   <property name="icon">
    <iconset theme="document-save"/>
   </property>
   <property name="text">
    <string>Save &amp;Workspace</string>
   </property>
   <property name="statusTip">
    <string>Saves the workspace settings.</string>
   </property>
   <property name="shortcut">
    <string>Alt+Shift+S</string>
   </property>
  </action>
  <action name="action_New_Workspace">
   <property name="icon">
    <iconset theme="document-new"/>
   </property>
   <property name="text">
    <string>&amp;New Workspace</string>
   </property>
  </action>
 </widget>
 <resources/>

A ui/src/util.cpp => ui/src/util.cpp +8 -0
@@ 0,0 1,8 @@
#include <QMessageBox>
#include "mcrestool_logic.h"

void check_for_error(QWidget* parent) {
    if (MCRT_ERROR) {
        QMessageBox::critical(parent, "Error", QString("%2 (error %1)").arg(MCRT_ERROR).arg(MCRT_ERROR_TEXT));
    }
}

M ui/src/util.h => ui/src/util.h +3 -0
@@ 2,9 2,12 @@
#define MCRESTOOL_UTIL_H

#include <QtGlobal>
#include <QWidget>

#define unimplemented() (qt_assert("unimplemented", __FILE__, __LINE__))

#define unreachable() (qt_assert("unreachable", __FILE__, __LINE__))

void check_for_error(QWidget* parent);

#endif //MCRESTOOL_UTIL_H