aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Chris Xiong <chirs241097@gmail.com> 2023-09-09 20:09:50 -0400
committerGravatar Chris Xiong <chirs241097@gmail.com> 2023-09-09 20:09:50 -0400
commit93cf929f29dea490ed60e5300cacdd99886c988e (patch)
tree0f8669c56e22a287047ee20846b92f1580d7364f
parent40432e083b11271cf3148b9c38156cf759436699 (diff)
downloadmeteor-trashy-addon-93cf929f29dea490ed60e5300cacdd99886c988e.tar.xz
Add the standalone portion of mapman.
-rw-r--r--.gitignore2
-rw-r--r--mapman/CMakeLists.txt52
-rw-r--r--mapman/src/diffview.cpp37
-rw-r--r--mapman/src/diffview.hpp21
-rw-r--r--mapman/src/groupview.cpp237
-rw-r--r--mapman/src/groupview.hpp48
-rw-r--r--mapman/src/library.cpp329
-rw-r--r--mapman/src/library.hpp41
-rw-r--r--mapman/src/main.cpp13
-rw-r--r--mapman/src/mainwindow.cpp133
-rw-r--r--mapman/src/mainwindow.hpp26
-rw-r--r--mapman/src/mapdump.cpp52
-rw-r--r--mapman/src/mapdump.hpp31
-rw-r--r--mapman/src/painter.cpp113
-rw-r--r--mapman/src/painter.hpp34
-rw-r--r--mapman/src/sliceview.cpp101
-rw-r--r--mapman/src/sliceview.hpp32
-rw-r--r--mapman/src/utils.cpp40
-rw-r--r--mapman/src/utils.hpp82
19 files changed, 1424 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 09cd281..b3a7cb5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,5 @@ bin/
# fabric
run/
+
+mapman/build
diff --git a/mapman/CMakeLists.txt b/mapman/CMakeLists.txt
new file mode 100644
index 0000000..99f1d67
--- /dev/null
+++ b/mapman/CMakeLists.txt
@@ -0,0 +1,52 @@
+cmake_minimum_required(VERSION 3.14.0)
+project(mapman CXX)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+option(USE_QT6 "Build with Qt 6" OFF)
+if (USE_QT6)
+ find_package(Qt6 REQUIRED COMPONENTS Widgets)
+else()
+ find_package(Qt5 REQUIRED COMPONENTS Widgets)
+endif()
+
+find_package(ZLIB REQUIRED)
+find_package(SQLite3 REQUIRED)
+include_directories(${ZLIB_INCLUDE_DIRS})
+include_directories(${SQLite3_INCLUDE_DIRS})
+
+INCLUDE (CheckTypeSize)
+
+SET(CMAKE_EXTRA_INCLUDE_FILES "filesystem")
+check_type_size("std::filesystem::path::value_type" PATH_VALSIZE LANGUAGE CXX)
+SET(CMAKE_EXTRA_INCLUDE_FILES)
+
+add_compile_definitions(PATH_VALSIZE=${PATH_VALSIZE})
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+set(mapman_SOURCES
+ src/main.cpp
+ src/mapdump.cpp
+ src/utils.cpp
+ src/library.cpp
+ src/painter.cpp
+ src/sliceview.cpp
+ src/groupview.cpp
+ src/diffview.cpp
+ src/mainwindow.cpp
+)
+add_executable(mapman ${mapman_SOURCES})
+
+target_link_libraries(mapman
+ ${ZLIB_LIBRARIES}
+)
+
+if (USE_QT6)
+ target_link_libraries(mapman Qt6::Widgets ${SQLite3_LIBRARIES})
+else()
+ target_link_libraries(mapman Qt5::Widgets ${SQLite3_LIBRARIES})
+endif()
diff --git a/mapman/src/diffview.cpp b/mapman/src/diffview.cpp
new file mode 100644
index 0000000..3c437c4
--- /dev/null
+++ b/mapman/src/diffview.cpp
@@ -0,0 +1,37 @@
+#include "diffview.hpp"
+
+#include <QTextEdit>
+#include <QLabel>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <qboxlayout.h>
+
+diff_view::diff_view(QWidget *par) : QWidget(par)
+{
+ this->setWindowTitle("Map tally comparison results");
+ tea_b = new QTextEdit();
+ teb_a = new QTextEdit();
+ tea_b->setReadOnly(true);
+ teb_a->setReadOnly(true);
+ auto hl = new QHBoxLayout();
+ auto lvl = new QVBoxLayout();
+ lvl->addWidget(new QLabel("Maps present in library but not in tally"));
+ lvl->addWidget(tea_b);
+ auto rvl = new QVBoxLayout();
+ rvl->addWidget(new QLabel("Maps present in tally but not in library"));
+ rvl->addWidget(teb_a);
+ hl->addLayout(lvl);
+ hl->addLayout(rvl);
+ this->setLayout(hl);
+}
+
+void diff_view::set_results(const std::vector<int> &a_b, const std::vector<int> &b_a)
+{
+ tea_b->clear();
+ teb_a->clear();
+ QString sa_b, sb_a;
+ for (auto &i : a_b) sa_b.append(QString("%1\n").arg(i));
+ for (auto &i : b_a) sb_a.append(QString("%1\n").arg(i));
+ tea_b->setText(sa_b);
+ teb_a->setText(sb_a);
+}
diff --git a/mapman/src/diffview.hpp b/mapman/src/diffview.hpp
new file mode 100644
index 0000000..d506f3a
--- /dev/null
+++ b/mapman/src/diffview.hpp
@@ -0,0 +1,21 @@
+#ifndef DIFFVIEW_HPP
+#define DIFFVIEW_HPP
+
+#include <QWidget>
+
+#include <vector>
+
+class QTextEdit;
+
+class diff_view : public QWidget
+{
+ Q_OBJECT
+public:
+ diff_view(QWidget* par = nullptr);
+ void set_results(const std::vector<int> &a_b, const std::vector<int> &b_a);
+private:
+ QTextEdit *tea_b;
+ QTextEdit *teb_a;
+};
+
+#endif
diff --git a/mapman/src/groupview.cpp b/mapman/src/groupview.cpp
new file mode 100644
index 0000000..32917d4
--- /dev/null
+++ b/mapman/src/groupview.cpp
@@ -0,0 +1,237 @@
+#include "groupview.hpp"
+#include "library.hpp"
+#include "painter.hpp"
+
+#include <algorithm>
+
+#include <QTableView>
+#include <QStandardItemModel>
+#include <QPushButton>
+#include <QLineEdit>
+#include <QSpinBox>
+#include <QLabel>
+#include <QSplitter>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QGraphicsView>
+#include <QSpacerItem>
+#include <QHeaderView>
+#include <QMessageBox>
+#include <qmessagebox.h>
+
+group_view::group_view() : QMdiSubWindow()
+{
+ auto sp = new QSplitter(this);
+ this->setWidget(sp);
+ auto leftpane = new QWidget();
+ auto l1 = new QVBoxLayout();
+ leftpane->setLayout(l1);
+ tv = new QTableView();
+ m = new QStandardItemModel();
+ tv->setModel(m);
+ tv->setColumnHidden(3, true);
+ tv->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
+ tv->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
+ connect(tv->selectionModel(), &QItemSelectionModel::currentRowChanged,
+ [this](const QModelIndex &curidx, const QModelIndex &oldidx) {
+ if (oldidx.isValid() && curidx.row() == oldidx.row()) return;
+ bool dirty = oldidx.isValid() && this->dirty;
+ if (oldidx.isValid())
+ {
+ int64_t oldgid = m->item(oldidx.row(), 3)->data(Qt::ItemDataRole::DisplayRole).toLongLong();
+ auto oldgroup = l->get_group(oldgid);
+ dirty |= tetitle->text() != QString::fromStdString(oldgroup.title);
+ dirty |= teauthor->text() != QString::fromStdString(oldgroup.author);
+ dirty |= sbh->value() != oldgroup.hc;
+ dirty |= sbv->value() != oldgroup.vc;
+ }
+ if (dirty)
+ {
+ if (QMessageBox::question(this, "Unsaved changes", "Changes to map art not saved! Discard and switch to another map art?") != QMessageBox::StandardButton::Yes)
+ {
+ int row = oldidx.row();
+ QMetaObject::invokeMethod(this, [this, row]() {
+ QSignalBlocker b(tv->selectionModel());
+ tv->selectRow(row);
+ }, Qt::ConnectionType::QueuedConnection);
+ return;
+ }
+ }
+ this->update_fields();
+ });
+ l1->addWidget(tv);
+ auto l2 = new QHBoxLayout();
+ pbadd = new QPushButton("+");
+ pbrem = new QPushButton("-");
+ connect(pbadd, &QPushButton::pressed, this, &group_view::add_group);
+ connect(pbrem, &QPushButton::pressed, this, &group_view::rem_group);
+ l2->addWidget(pbadd);
+ l2->addWidget(pbrem);
+ l1->addLayout(l2);
+ sp->addWidget(leftpane);
+
+ auto rightpane = new QWidget();
+ auto l3 = new QVBoxLayout();
+ rightpane->setLayout(l3);
+ p = new map_painter();
+ l3->addWidget(p->view());
+ connect(p, &map_painter::map_id_changed, this, &group_view::painter_drop);
+ tetitle = new QLineEdit();
+ teauthor = new QLineEdit();
+ tetitle->setPlaceholderText("Title");
+ teauthor->setPlaceholderText("Author(s)");
+ l3->addWidget(tetitle);
+ l3->addWidget(teauthor);
+ auto l4 = new QHBoxLayout();
+ sbv = new QSpinBox();
+ sbh = new QSpinBox();
+ sbv->setValue(1);
+ sbv->setMinimum(1);
+ sbv->setMaximum(100);
+ connect(sbv, QOverload<int>::of(&QSpinBox::valueChanged), this, &group_view::reset_dim);
+ sbh->setValue(1);
+ sbh->setMinimum(1);
+ sbh->setMaximum(100);
+ connect(sbh, QOverload<int>::of(&QSpinBox::valueChanged), this, &group_view::reset_dim);
+ l4->addWidget(sbh);
+ l4->addWidget(new QLabel("x"));
+ l4->addWidget(sbv);
+ l4->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
+ pbapply = new QPushButton("Save");
+ connect(pbapply, &QPushButton::pressed, this, &group_view::update_library);
+
+ l4->addWidget(pbapply);
+ l3->addLayout(l4);
+ sp->addWidget(rightpane);
+
+ sp->setStretchFactor(0, 1);
+ sp->setStretchFactor(1, 3);
+ sp->setCollapsible(0, false);
+ sp->setCollapsible(1, false);
+ l = nullptr;
+ dirty = false;
+ this->setWindowTitle("Map art listings");
+ this->setAttribute(Qt::WA_DeleteOnClose, false);
+}
+
+group_view::~group_view()
+{
+ delete p;
+}
+
+void group_view::set_library(map_library *lib)
+{
+ l = lib;
+ p->set_map_library(l);
+ refresh_list();
+}
+
+void group_view::add_group()
+{
+ map_group_t g {
+ std::string(),
+ std::string(),
+ 1, 1,
+ {0},
+ {false}
+ };
+ l->new_group(g);
+ refresh_list();
+}
+
+void group_view::rem_group()
+{
+ if (!tv->currentIndex().isValid())
+ return;
+ int64_t curgid = m->item(tv->currentIndex().row(), 3)->data(Qt::ItemDataRole::DisplayRole).toLongLong();
+ l->remove_group(curgid);
+ refresh_list();
+}
+
+void group_view::update_fields()
+{
+ if (!tv->currentIndex().isValid())
+ return;
+ int64_t curgid = m->item(tv->currentIndex().row(), 3)->data(Qt::ItemDataRole::DisplayRole).toLongLong();
+ current_group = l->get_group(curgid);
+ auto &g = current_group;
+ tetitle->setText(QString::fromStdString(g.title));
+ teauthor->setText(QString::fromStdString(g.author));
+ QSignalBlocker bh(sbh);
+ QSignalBlocker bv(sbv);
+ sbh->setValue(g.hc);
+ sbv->setValue(g.vc);
+ update_map_view();
+ dirty = false;
+}
+
+void group_view::painter_drop(int pos, bool populated, int id)
+{
+ if (!tv->currentIndex().isValid())
+ return;
+ auto &g = current_group;
+ g.populated[pos] = populated;
+ g.ids[pos] = id;
+ dirty = true;
+}
+
+void group_view::update_library()
+{
+ if (!tv->currentIndex().isValid())
+ return;
+ int64_t curgid = m->item(tv->currentIndex().row(), 3)->data(Qt::ItemDataRole::DisplayRole).toLongLong();
+ auto &g = current_group;
+ g.title = tetitle->text().toStdString();
+ g.author = teauthor->text().toStdString();
+ g.hc = sbh->value();
+ g.vc = sbv->value();
+ l->set_group(curgid, g);
+ refresh_list();
+ dirty = false;
+}
+
+void group_view::refresh_list()
+{
+ int64_t curgid = -1;
+ if (tv->currentIndex().isValid())
+ curgid = m->item(tv->currentIndex().row(), 3)->data(Qt::ItemDataRole::DisplayRole).toLongLong();
+ m->clear();
+ m->setHorizontalHeaderLabels({"Title", "Author(s)", "Dimension", "id"});
+ tv->setColumnHidden(3, true);
+ auto gids = l->groups();
+ for (auto gid : gids)
+ {
+ map_group_t g = l->get_group(gid);
+ QStandardItem *t = new QStandardItem(QString::fromStdString(g.title));
+ QStandardItem *a = new QStandardItem(QString::fromStdString(g.author));
+ QStandardItem *d = new QStandardItem(QString("%1x%2").arg(g.hc).arg(g.vc));
+ QStandardItem *i = new QStandardItem();
+ i->setData(QVariant((qlonglong)gid), Qt::ItemDataRole::DisplayRole);
+ m->appendRow({t, a, d, i});
+ if (gid == curgid)
+ tv->setCurrentIndex(t->index());
+ }
+ tv->resizeColumnsToContents();
+}
+
+void group_view::reset_dim()
+{
+ p->set_dimension(sbh->value(), sbv->value());
+ if (!tv->currentIndex().isValid())
+ return;
+ auto &g = current_group;
+ g.hc = sbh->value();
+ g.vc = sbv->value();
+ g.ids.resize(g.hc * g.vc);
+ g.populated.resize(g.hc * g.vc);
+ std::fill(g.populated.begin(), g.populated.end(), false);
+ dirty = true;
+}
+
+void group_view::update_map_view()
+{
+ auto &g = current_group;
+ p->set_dimension(g.hc, g.vc);
+ for (int i = 0; i < g.hc * g.vc; ++i)
+ p->set_map_id(i, g.populated[i], g.ids[i]);
+}
diff --git a/mapman/src/groupview.hpp b/mapman/src/groupview.hpp
new file mode 100644
index 0000000..ca4cf30
--- /dev/null
+++ b/mapman/src/groupview.hpp
@@ -0,0 +1,48 @@
+#ifndef GROUPVIEW_HPP
+#define GROUPVIEW_HPP
+
+#include <QMdiSubWindow>
+#include "mapdump.hpp"
+
+class QTableView;
+class QPushButton;
+class QLineEdit;
+class QSpinBox;
+class QLabel;
+class QStandardItemModel;
+class map_library;
+class map_painter;
+
+class group_view : public QMdiSubWindow
+{
+Q_OBJECT
+public:
+ group_view();
+ ~group_view();
+ void set_library(map_library *lib);
+public slots:
+ void add_group();
+ void rem_group();
+ void update_fields();
+ void update_library();
+ void painter_drop(int pos, bool populated, int id);
+ void refresh_list();
+ void reset_dim();
+ void update_map_view();
+private:
+ QTableView *tv;
+ QStandardItemModel *m;
+ QPushButton *pbadd;
+ QPushButton *pbrem;
+ QLineEdit *tetitle;
+ QLineEdit *teauthor;
+ QSpinBox *sbh;
+ QSpinBox *sbv;
+ QPushButton *pbapply;
+ bool dirty;
+ map_library *l;
+ map_painter *p;
+ map_group_t current_group;
+};
+
+#endif
diff --git a/mapman/src/library.cpp b/mapman/src/library.cpp
new file mode 100644
index 0000000..2361441
--- /dev/null
+++ b/mapman/src/library.cpp
@@ -0,0 +1,329 @@
+#include "library.hpp"
+#include "mapdump.hpp"
+
+#include <cstring>
+#include <filesystem>
+#include <sqlite3.h>
+
+const int MAPDB_VERSION = 1;
+
+map_library::map_library() : db(nullptr) {}
+
+map_library::~map_library()
+{
+ if (db)
+ sqlite3_close(db);
+}
+
+std::vector<int> map_library::map_ids() const
+{
+ std::vector<int> ret;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select id from maps;", -1, &st, 0);
+ while (1)
+ {
+ int r = sqlite3_step(st);
+ if (r != SQLITE_ROW) break;
+ ret.push_back(sqlite3_column_int(st, 0));
+ }
+ sqlite3_finalize(st);
+ return ret;
+}
+
+bool map_library::has_map(int id) const
+{
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select id from maps where id = ?;", -1, &st, 0);
+ sqlite3_bind_int(st, 1, id);
+ int r = sqlite3_step(st);
+ sqlite3_finalize(st);
+ return r == SQLITE_ROW;
+}
+
+void map_library::set_map(const map_t &map)
+{
+ sqlite3_stmt *st = nullptr;
+ if (has_map(map.id))
+ {
+ sqlite3_prepare_v2(db, "update maps set custom_name = ?, data = ? where id = ?;", -1, &st, 0);
+ sqlite3_bind_text(st, 1, map.custom_name.c_str(), map.custom_name.length(), SQLITE_STATIC);
+ sqlite3_bind_blob(st, 2, map.map_data.data(), map.map_data.size(), SQLITE_STATIC);
+ sqlite3_bind_int(st, 3, map.id);
+ sqlite3_step(st);
+ }
+ else
+ {
+ sqlite3_prepare_v2(db, "insert into maps (id, custom_name, data) values(?, ?, ?);", -1, &st, 0);
+ sqlite3_bind_int(st, 1, map.id);
+ sqlite3_bind_text(st, 2, map.custom_name.c_str(), map.custom_name.length(), SQLITE_STATIC);
+ sqlite3_bind_blob(st, 3, map.map_data.data(), map.map_data.size(), SQLITE_STATIC);
+ sqlite3_step(st);
+ }
+ sqlite3_finalize(st);
+}
+
+map_t map_library::get_map(int id) const
+{
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select custom_name, data from maps where id = ?;", -1, &st, 0);
+ sqlite3_bind_int(st, 1, id);
+ map_t ret{id, std::string(), map_data_t()};
+ if (sqlite3_step(st) == SQLITE_ROW)
+ {
+ ret.custom_name = std::string((char*)sqlite3_column_text(st, 0));
+ memcpy(ret.map_data.data(), sqlite3_column_blob(st, 1), ret.map_data.size());
+ }
+ sqlite3_finalize(st);
+ return ret;
+}
+
+std::vector<int64_t> map_library::groups() const
+{
+ std::vector<int64_t> ret;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select rowid from groups;", -1, &st, 0);
+ while (1)
+ {
+ int r = sqlite3_step(st);
+ if (r != SQLITE_ROW) break;
+ ret.push_back(sqlite3_column_int64(st, 0));
+ }
+ sqlite3_finalize(st);
+ return ret;
+}
+
+int64_t map_library::new_group(const map_group_t &g)
+{
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "insert into groups (title, author, horizontal_count, vertical_count) values(?, ?, ?, ?);", -1, &st, 0);
+ sqlite3_bind_text(st, 1, g.title.c_str(), g.title.length(), SQLITE_STATIC);
+ sqlite3_bind_text(st, 2, g.author.c_str(), g.author.length(), SQLITE_STATIC);
+ sqlite3_bind_int(st, 3, g.hc);
+ sqlite3_bind_int(st, 4, g.vc);
+ sqlite3_step(st);
+ sqlite3_finalize(st);
+ int64_t id = sqlite3_last_insert_rowid(db);
+ sqlite3_prepare_v2(db, "insert into group_maps (gid, pos, map_id) values(?, ?, ?);", -1, &st, 0);
+ sqlite3_bind_int64(st, 1, id);
+ for (size_t i = 0; i < g.ids.size(); ++i)
+ {
+ if (!g.populated[i])
+ continue;
+ sqlite3_bind_int(st, 2, i);
+ sqlite3_bind_int(st, 3, g.ids[i]);
+ sqlite3_step(st);
+ if (i + 1 < g.ids.size())
+ sqlite3_reset(st);
+ }
+ sqlite3_finalize(st);
+ return id;
+}
+
+bool map_library::has_group(int64_t gid) const
+{
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select rowid from groups where rowid = ?;", -1, &st, 0);
+ sqlite3_bind_int(st, 1, gid);
+ int r = sqlite3_step(st);
+ sqlite3_finalize(st);
+ return r == SQLITE_ROW;
+}
+
+void map_library::set_group(int64_t gid, const map_group_t &g)
+{
+ if (!has_group(gid))
+ return;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, R"sql(
+ update groups set title = ?, author = ?, horizontal_count = ?, vertical_count = ? where rowid = ?;
+ )sql", -1, &st, 0);
+ sqlite3_bind_text(st, 1, g.title.c_str(), g.title.length(), SQLITE_STATIC);
+ sqlite3_bind_text(st, 2, g.author.c_str(), g.author.length(), SQLITE_STATIC);
+ sqlite3_bind_int(st, 3, g.hc);
+ sqlite3_bind_int(st, 4, g.vc);
+ sqlite3_bind_int64(st, 5, gid);
+ sqlite3_step(st);
+ sqlite3_finalize(st);
+ sqlite3_prepare_v2(db, R"sql(
+ delete from group_maps where gid = ?;
+ )sql", -1, &st, 0);
+ sqlite3_bind_int64(st, 1, gid);
+ sqlite3_step(st);
+ sqlite3_finalize(st);
+ sqlite3_prepare_v2(db, "insert into group_maps (gid, pos, map_id) values(?, ?, ?);", -1, &st, 0);
+ sqlite3_bind_int(st, 1, gid);
+ for (size_t i = 0; i < g.ids.size(); ++i)
+ {
+ if (!g.populated[i])
+ continue;
+ sqlite3_bind_int(st, 2, i);
+ sqlite3_bind_int(st, 3, g.ids[i]);
+ sqlite3_step(st);
+ if (i + 1 < g.ids.size())
+ sqlite3_reset(st);
+ }
+ sqlite3_finalize(st);
+}
+
+map_group_t map_library::get_group(int64_t gid)
+{
+ map_group_t ret{};
+ if (!has_group(gid))
+ return ret;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select title, author, horizontal_count, vertical_count from groups where rowid = ?;", -1, &st, 0);
+ sqlite3_bind_int64(st, 1, gid);
+ if (sqlite3_step(st) == SQLITE_ROW)
+ {
+ ret.title = std::string((char*)sqlite3_column_text(st, 0));
+ ret.author = std::string((char*)sqlite3_column_text(st, 1));
+ ret.hc = sqlite3_column_int(st, 2);
+ ret.vc = sqlite3_column_int(st, 3);
+ }
+ sqlite3_finalize(st);
+ ret.ids.resize(ret.hc * ret.vc);
+ ret.populated.resize(ret.hc * ret.vc, false);
+ sqlite3_prepare_v2(db, "select pos, map_id from group_maps where gid = ?;", -1, &st, 0);
+ sqlite3_bind_int(st, 1, gid);
+ while (sqlite3_step(st) == SQLITE_ROW)
+ {
+ int pos = sqlite3_column_int(st, 0);
+ int id = sqlite3_column_int(st, 1);
+ if (pos < ret.hc * ret.vc)
+ {
+ ret.ids[pos] = id;
+ ret.populated[pos] = true;
+ }
+ }
+ sqlite3_finalize(st);
+ return ret;
+}
+
+void map_library::remove_group(int64_t gid) const
+{
+ if (!has_group(gid))
+ return;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, R"sql(
+ delete from groups where rowid = ?;
+ delete from group_maps where gid = ?1;
+ )sql", -1, &st, 0);
+ sqlite3_bind_int64(st, 1, gid);
+ sqlite3_step(st);
+ sqlite3_finalize(st);
+}
+
+/*
+ * tally (in): list of maps in storage containers
+ * a_b (out): maps that are in the library but not in containers
+ * b_a (out): maps that are in containers but not in the library
+ */
+void map_library::tally_diff(const std::vector<int> &tally,
+ std::vector<int> &a_b,
+ std::vector<int> &b_a) const
+{
+ sqlite3_exec(db, "create table temp_tally(id int);", nullptr, nullptr, nullptr);
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "insert into temp_tally (id) values(?);", -1, &st, 0);
+ for (auto &id : tally)
+ {
+ sqlite3_bind_int(st, 1, id);
+ sqlite3_step(st);
+ sqlite3_reset(st);
+ }
+ sqlite3_finalize(st);
+ sqlite3_prepare_v2(db, R"sql(
+ select maps.id as mid, temp_tally.id as tid
+ from maps full outer join temp_tally on maps.id = temp_tally.id
+ where mid is NULL or tid is NULL;
+ )sql", -1, &st, 0);
+ a_b.clear();
+ b_a.clear();
+ while (sqlite3_step(st) == SQLITE_ROW)
+ {
+ int mid = sqlite3_column_int(st, 0);
+ int tid = sqlite3_column_int(st, 1);
+ bool mid_null = sqlite3_column_type(st, 0) == SQLITE_NULL;
+ bool tid_null = sqlite3_column_type(st, 1) == SQLITE_NULL;
+ if (mid_null)
+ b_a.push_back(tid);
+ if (tid_null)
+ a_b.push_back(mid);
+ }
+ sqlite3_finalize(st);
+ sqlite3_exec(db, "drop table temp_tally;", nullptr, nullptr, nullptr);
+ std::sort(a_b.begin(), a_b.end());
+ std::sort(b_a.begin(), b_a.end());
+}
+
+
+bool map_library::open_db(const std::filesystem::path &p)
+{
+ bool needs_creation = !std::filesystem::is_regular_file(p);
+#if PATH_VALSIZE == 2
+ sqlite3_open16(p.c_str(), &db);
+#else
+ sqlite3_open(p.c_str(), &db);
+#endif
+ if (needs_creation)
+ init_db();
+ else
+ {
+ if (!verify_db())
+ {
+ sqlite3_close(db);
+ db = nullptr;
+ return false;
+ }
+ }
+ return true;
+}
+
+void map_library::init_db()
+{
+ sqlite3_exec(db, R"sql(
+ create table mapdbinfo(
+ version int,
+ name text
+ );
+ )sql", nullptr, nullptr, nullptr);
+
+ sqlite3_stmt *vst;
+ sqlite3_prepare_v2(db, "insert into mapdbinfo (version) values(?);", -1, &vst, 0);
+ sqlite3_bind_int(vst, 1, MAPDB_VERSION);
+ sqlite3_step(vst);
+ sqlite3_finalize(vst);
+
+ sqlite3_exec(db, R"sql(
+ create table maps(
+ id integer primary key,
+ custom_name text,
+ data blob
+ );
+ )sql", nullptr, nullptr, nullptr);
+ sqlite3_exec(db, R"sql(
+ create table groups(
+ title text,
+ author text,
+ horizontal_count integer,
+ vertical_count integer
+ );
+ )sql", nullptr, nullptr, nullptr);
+ sqlite3_exec(db, R"sql(
+ create table group_maps(
+ gid integer,
+ pos integer,
+ map_id integer
+ );
+ )sql", nullptr, nullptr, nullptr);
+}
+
+bool map_library::verify_db()
+{
+ sqlite3_stmt *vst;
+ sqlite3_prepare_v2(db, "select version from mapdbinfo;", -1, &vst, 0);
+ if (sqlite3_step(vst) != SQLITE_ROW) {sqlite3_finalize(vst); return false;}
+ if (MAPDB_VERSION != sqlite3_column_int(vst, 0)) {sqlite3_finalize(vst); return false;}
+ sqlite3_finalize(vst);
+ return true;
+}
diff --git a/mapman/src/library.hpp b/mapman/src/library.hpp
new file mode 100644
index 0000000..82babe0
--- /dev/null
+++ b/mapman/src/library.hpp
@@ -0,0 +1,41 @@
+#ifndef LIBRARY_HPP
+#define LIBRARY_HPP
+
+#include "mapdump.hpp"
+
+#include <filesystem>
+#include <string>
+#include <vector>
+
+struct sqlite3;
+
+class map_library
+{
+public:
+ map_library();
+ ~map_library();
+
+ std::vector<int> map_ids() const;
+ bool has_map(int id) const;
+ void set_map(const map_t &map);
+ map_t get_map(int id) const;
+
+ std::vector<int64_t> groups() const;
+ int64_t new_group(const map_group_t &g);
+ bool has_group(int64_t gid) const;
+ void set_group(int64_t gid, const map_group_t &g);
+ map_group_t get_group(int64_t gid);
+ void remove_group(int64_t gid) const;
+
+ void tally_diff(const std::vector<int> &tally,
+ std::vector<int> &a_b,
+ std::vector<int> &b_a) const;
+
+ bool open_db(const std::filesystem::path &p);
+private:
+ void init_db();
+ bool verify_db();
+ sqlite3 *db;
+};
+
+#endif
diff --git a/mapman/src/main.cpp b/mapman/src/main.cpp
new file mode 100644
index 0000000..b30b0f2
--- /dev/null
+++ b/mapman/src/main.cpp
@@ -0,0 +1,13 @@
+#include "mainwindow.hpp"
+#include <QApplication>
+
+int main(int argc, char **argv)
+{
+ QApplication a(argc, argv);
+
+ mapman_main_window mw;
+ mw.show();
+
+ a.exec();
+ return 0;
+}
diff --git a/mapman/src/mainwindow.cpp b/mapman/src/mainwindow.cpp
new file mode 100644
index 0000000..83d70c0
--- /dev/null
+++ b/mapman/src/mainwindow.cpp
@@ -0,0 +1,133 @@
+#include "mainwindow.hpp"
+#include "groupview.hpp"
+#include "mapdump.hpp"
+#include "sliceview.hpp"
+#include "diffview.hpp"
+#include "library.hpp"
+
+#include <QMdiArea>
+#include <QMenuBar>
+#include <QMenu>
+#include <QAction>
+#include <QFileDialog>
+#include <QApplication>
+#include <QMessageBox>
+
+#include <filesystem>
+#include <vector>
+
+#define MAPMAN_VERSION "pre-alpha"
+
+mapman_main_window::mapman_main_window() : QMainWindow()
+{
+ l = nullptr;
+ cw = new QMdiArea();
+ setCentralWidget(cw);
+ gv = new group_view();
+ sv = new slice_view();
+ dv = new diff_view();
+ cw->addSubWindow(gv);
+ cw->addSubWindow(sv);
+ dv->hide();
+ this->setWindowTitle("Mapman");
+ cw->cascadeSubWindows();
+ auto fm = this->menuBar()->addMenu("&File");
+ auto cra = fm->addAction("Create / L&oad MapDB...");
+ auto cla = fm->addAction("&Close MapDB");
+ fm->addSeparator();
+ auto lda = fm->addAction("&Load Map Dump...");
+ fm->addSeparator();
+ auto cta = fm->addAction("&Compare Map Tally...");
+ fm->addSeparator();
+ auto qa = fm->addAction("&Quit");
+ cla->setEnabled(false);
+ lda->setEnabled(false);
+ cta->setEnabled(false);
+ connect(cra, &QAction::triggered, [this, cla, lda, cta] {
+ QString fn = QFileDialog::getSaveFileName(this, "Create / Load MapDB", QString(), "*.mapdb", nullptr, QFileDialog::Option::DontConfirmOverwrite);
+ if (fn.length())
+ {
+ if (l) delete l;
+ l = new map_library();
+ std::filesystem::path p(fn.toStdWString());
+ if (!l->open_db(p))
+ {
+ delete l;
+ l = nullptr;
+ return;
+ }
+ sv->set_library(l);
+ gv->set_library(l);
+ sv->refresh();
+ gv->refresh_list();
+ cla->setEnabled(true);
+ lda->setEnabled(true);
+ cta->setEnabled(true);
+ }
+ });
+ connect(cla, &QAction::triggered, [this, cla, lda, cta] {
+ if (l) delete l;
+ l = nullptr;
+ cla->setEnabled(false);
+ lda->setEnabled(false);
+ cta->setEnabled(false);
+ });
+ connect(lda, &QAction::triggered, [this] {
+ if (!l) return;
+ QString fn = QFileDialog::getOpenFileName(this, "Load Map Dump", QString(), "*.gz");
+ if (fn.length())
+ {
+ std::vector<map_t> m;
+ if (load_dumps(fn.toStdString().c_str(), m))
+ {
+ for (auto &map : m)
+ l->set_map(map);
+ sv->refresh();
+ }
+ }
+ });
+ connect(cta, &QAction::triggered, [this] {
+ if (!l) return;
+ QString fn = QFileDialog::getOpenFileName(this, "Select Map Tally", QString(), "*.gz");
+ if (fn.length())
+ {
+ auto tally = load_tally(fn.toStdString().c_str());
+ std::vector<int> a_b, b_a;
+ l->tally_diff(tally, a_b, b_a);
+ dv->set_results(a_b, b_a);
+ dv->show();
+ }
+ });
+ connect(qa, &QAction::triggered, [] {
+ QApplication::exit();
+ });
+ auto wm = this->menuBar()->addMenu("&Windows");
+ auto swa = wm->addAction("Map &listings");
+ auto gwa = wm->addAction("Map &art listings");
+ connect(swa, &QAction::triggered, [this]{ sv->widget()->show(); sv->show(); });
+ connect(gwa, &QAction::triggered, [this]{ gv->widget()->show(); gv->show(); });
+ auto hm = this->menuBar()->addMenu("&Help");
+ auto aba = hm->addAction("&About");
+ auto aqa = hm->addAction("About &Qt");
+ connect(aba, &QAction::triggered, [this]{
+ QMessageBox::about(this, "About Mapman", QString(R"(
+Mapman
+A minecraft map art manager.
+
+%1
+
+Chris Xiong 2023
+
+License: MIT (expat)
+)")
+ .arg(MAPMAN_VERSION).trimmed());
+ });
+ connect(aqa, &QAction::triggered, []{ QApplication::aboutQt(); });
+}
+
+mapman_main_window::~mapman_main_window()
+{
+ if (l)
+ delete l;
+ delete dv;
+}
diff --git a/mapman/src/mainwindow.hpp b/mapman/src/mainwindow.hpp
new file mode 100644
index 0000000..647fcc9
--- /dev/null
+++ b/mapman/src/mainwindow.hpp
@@ -0,0 +1,26 @@
+#ifndef MAINWINDOW_HPP
+#define MAINWINDOW_HPP
+
+#include <QMainWindow>
+
+class map_library;
+class group_view;
+class slice_view;
+class diff_view;
+class QMdiArea;
+
+class mapman_main_window : public QMainWindow
+{
+ Q_OBJECT
+public:
+ mapman_main_window();
+ ~mapman_main_window();
+private:
+ map_library *l;
+ group_view *gv;
+ slice_view *sv;
+ diff_view *dv;
+ QMdiArea *cw;
+};
+
+#endif
diff --git a/mapman/src/mapdump.cpp b/mapman/src/mapdump.cpp
new file mode 100644
index 0000000..6d8fbcc
--- /dev/null
+++ b/mapman/src/mapdump.cpp
@@ -0,0 +1,52 @@
+#include "mapdump.hpp"
+
+#include <zlib.h>
+
+bool load_dump(gzFile f, map_t &d)
+{
+ map_t ret;
+ if (gzread(f, &d.id, 4) < 4) return false;
+ int name_len;
+ if (gzread(f, &name_len, 4) < 4) return false;
+ if (name_len)
+ {
+ char *name = new char[name_len];
+ if (gzread(f, name, name_len) != name_len)
+ {
+ delete[] name;
+ return false;
+ }
+ d.custom_name = std::string(name);
+ delete[] name;
+ } else d.custom_name = std::string();
+ if (gzread(f, d.map_data.data(), 128 * 128) < 128 * 128)
+ return false;
+ return true;
+}
+
+bool load_dumps(const char *fn, std::vector<map_t> &dumps)
+{
+ gzFile f = gzopen(fn, "rb");
+ dumps.clear();
+ while (!gzeof(f))
+ {
+ map_t d;
+ if (load_dump(f, d))
+ dumps.emplace_back(std::move(d));
+ }
+ gzclose(f);
+ return dumps.size() != 0;
+}
+
+std::vector<int> load_tally(const char *fn)
+{
+ std::vector<int> ret;
+ gzFile f = gzopen(fn, "rb");
+ while (!gzeof(f))
+ {
+ int t;
+ gzread(f, &t, 4);
+ ret.push_back(t);
+ }
+ return ret;
+}
diff --git a/mapman/src/mapdump.hpp b/mapman/src/mapdump.hpp
new file mode 100644
index 0000000..aa771e6
--- /dev/null
+++ b/mapman/src/mapdump.hpp
@@ -0,0 +1,31 @@
+#ifndef MAPDUMP_HPP
+#define MAPDUMP_HPP
+
+#include <cstdint>
+#include <array>
+#include <string>
+#include <vector>
+
+#include "utils.hpp"
+
+struct map_t
+{
+ int id;
+ std::string custom_name;
+ map_data_t map_data;
+};
+
+struct map_group_t
+{
+ std::string title;
+ std::string author;
+ int hc;
+ int vc;
+ std::vector<int> ids;
+ std::vector<bool> populated;
+};
+
+bool load_dumps(const char *fn, std::vector<map_t> &dumps);
+std::vector<int> load_tally(const char *fn);
+
+#endif
diff --git a/mapman/src/painter.cpp b/mapman/src/painter.cpp
new file mode 100644
index 0000000..876184e
--- /dev/null
+++ b/mapman/src/painter.cpp
@@ -0,0 +1,113 @@
+#include "painter.hpp"
+#include "library.hpp"
+#include "utils.hpp"
+
+#include <QGraphicsView>
+#include <QGraphicsScene>
+#include <QGraphicsSimpleTextItem>
+#include <QGraphicsRectItem>
+#include <QGraphicsPixmapItem>
+#include <QGraphicsSceneDragDropEvent>
+#include <QMimeData>
+#include <QPixmapCache>
+
+class drop_rect : public QGraphicsRectItem
+{
+public:
+ drop_rect(int pos, map_painter *painter);
+signals:
+ void dropped(int pos, int id);
+protected:
+ void dragEnterEvent(QGraphicsSceneDragDropEvent *e);
+ void dropEvent(QGraphicsSceneDragDropEvent *e);
+private:
+ int p;
+ map_painter *pt;
+};
+
+drop_rect::drop_rect(int pos, map_painter *painter) :
+ QGraphicsRectItem(QRectF(0, 0, 128, 128), nullptr),
+ p(pos),
+ pt(painter)
+{
+ setAcceptDrops(true);
+ setPen(QPen(Qt::GlobalColor::black, 4));
+}
+
+void drop_rect::dragEnterEvent(QGraphicsSceneDragDropEvent *e)
+{
+ auto ba = e->mimeData()->data("application/x-map-id");
+ e->setAccepted(ba.length() >= 4);
+}
+
+void drop_rect::dropEvent(QGraphicsSceneDragDropEvent *e)
+{
+ auto ba = e->mimeData()->data("application/x-map-id");
+ if (ba.length() >= 4)
+ {
+ e->setDropAction(Qt::DropAction::CopyAction);
+ pt->set_map_id(p, true, *reinterpret_cast<int*>(ba.data()), true);
+ }
+ else e->setDropAction(Qt::DropAction::IgnoreAction);
+}
+
+map_painter::map_painter() : l(nullptr)
+{
+ s = new QGraphicsScene(0, 0, 0, 0);
+ v = new QGraphicsView(s);
+ hc = vc = 0;
+}
+
+map_painter::~map_painter()
+{
+ delete s;
+ delete v;
+}
+
+void map_painter::set_dimension(int h, int v)
+{
+ hc = h;
+ vc = v;
+ s->setSceneRect(0, 0, hc * 128, vc * 128);
+ s->clear();
+ slices.clear();
+ slices.resize(hc * vc, nullptr);
+ for (int i = 0; i < vc; ++i)
+ for (int j = 0; j < hc; ++j)
+ {
+ auto t = s->addSimpleText(QString::number(i * hc + j));
+ t->setPos(j * 128 + 64 - t->boundingRect().width() / 2, i * 128 + 64 - t->boundingRect().height() / 2);
+ auto r = new drop_rect(i * hc + j, this);
+ s->addItem(r);
+ r->setPos(j * 128, i * 128);
+ }
+}
+
+void map_painter::set_map_id(int pos, bool populated, int id, bool user_input)
+{
+ if (!l || pos >= hc * vc || pos < 0) return;
+ if (slices[pos])
+ {
+ s->removeItem(slices[pos]);
+ delete slices[pos];
+ slices[pos] = nullptr;
+ }
+ if (populated)
+ {
+ QPixmap pm;
+ if (!QPixmapCache::find(QString("map_%1").arg(id), &pm))
+ {
+ pm = pixmap_of_map_data(l->get_map(id).map_data);
+ QPixmapCache::insert(QString("map_%1").arg(id), pm);
+ }
+ auto p = s->addPixmap(pm);
+ int x = pos / hc;
+ int y = pos % hc;
+ p->setPos(y * 128, x * 128);
+ slices[pos] = p;
+ }
+ if (user_input)
+ emit map_id_changed(pos, populated, id);
+}
+
+void map_painter::set_map_library(map_library *lib) { l = lib; }
diff --git a/mapman/src/painter.hpp b/mapman/src/painter.hpp
new file mode 100644
index 0000000..489a87b
--- /dev/null
+++ b/mapman/src/painter.hpp
@@ -0,0 +1,34 @@
+#ifndef MAPPAINTER_HPP
+#define MAPPAINTER_HPP
+
+#include <vector>
+#include <QObject>
+
+class QGraphicsScene;
+class QGraphicsView;
+class QGraphicsItem;
+class map_library;
+
+class map_painter : public QObject
+{
+ Q_OBJECT
+public:
+ map_painter();
+ ~map_painter();
+ void set_dimension(int h, int v);
+ void set_map_library(map_library *lib);
+ void set_map_id(int pos, bool populated, int id, bool user_input = false);
+
+ QGraphicsView* view() { return v; }
+signals:
+ void map_id_changed(int pos, bool populated, int id);
+private:
+ QGraphicsView *v;
+ QGraphicsScene *s;
+ map_library *l;
+ int hc;
+ int vc;
+ std::vector<QGraphicsItem*> slices;
+};
+
+#endif
diff --git a/mapman/src/sliceview.cpp b/mapman/src/sliceview.cpp
new file mode 100644
index 0000000..1dc385b
--- /dev/null
+++ b/mapman/src/sliceview.cpp
@@ -0,0 +1,101 @@
+#include "sliceview.hpp"
+#include "library.hpp"
+#include "painter.hpp"
+#include "utils.hpp"
+
+#include <QListView>
+#include <QGraphicsView>
+#include <QStandardItemModel>
+#include <QSplitter>
+#include <QDrag>
+#include <QMimeData>
+#include <QEvent>
+#include <QMouseEvent>
+#include <QApplication>
+#include <qnamespace.h>
+
+slice_view::slice_view() : l(nullptr)
+{
+ lv = new QListView(this);
+ m = new QStandardItemModel(this);
+ lv->setModel(m);
+ lv->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
+ lv->setDragDropMode(QAbstractItemView::DragDropMode::NoDragDrop);
+ lv->viewport()->installEventFilter(this);
+ p = new map_painter();
+ p->set_dimension(1, 1);
+ auto layout = new QSplitter(Qt::Orientation::Horizontal, this);
+ layout->setContentsMargins(6, 6, 6, 6);
+ layout->addWidget(lv);
+ layout->addWidget(p->view());
+ layout->setStretchFactor(0, 1);
+ layout->setStretchFactor(1, 3);
+ layout->setCollapsible(0, false);
+ layout->setCollapsible(1, false);
+ this->setWidget(layout);
+ connect(lv->selectionModel(), &QItemSelectionModel::currentChanged,
+ [this](const QModelIndex &cur, const QModelIndex&) {
+ if (this->l)
+ this->p->set_map_id(0, true, cur.data(Qt::UserRole + 1).toInt());
+ });
+ connect(lv, &QAbstractItemView::pressed,
+ [this](const QModelIndex &idx) {
+ dragidx = idx;
+ });
+ this->setWindowTitle("Map listings");
+ this->setAttribute(Qt::WA_DeleteOnClose, false);
+}
+
+slice_view::~slice_view()
+{
+ delete p;
+}
+
+void slice_view::set_library(map_library *lib)
+{
+ l = lib;
+ p->set_map_library(l);
+ refresh();
+}
+
+bool slice_view::eventFilter(QObject *o, QEvent *e)
+{
+ if (e->type() == QEvent::MouseButtonRelease)
+ dragidx = QModelIndex();
+ if (e->type() == QEvent::MouseButtonPress)
+ dragpos = static_cast<QMouseEvent*>(e)->screenPos();
+ if (e->type() == QEvent::MouseMove)
+ {
+ auto pos = static_cast<QMouseEvent*>(e)->screenPos();
+ if (dragidx.isValid() && (pos - dragpos).manhattanLength() >= QApplication::startDragDistance())
+ {
+ auto *d = new QDrag(lv);
+ int mapid = dragidx.data(Qt::ItemDataRole::UserRole + 1).toInt();
+ d->setPixmap(qvariant_cast<QIcon>(dragidx.data(Qt::ItemDataRole::DecorationRole)).pixmap(128, 128));
+ auto *m = new QMimeData();
+ m->setData("application/x-map-id", QByteArray(reinterpret_cast<char*>(&mapid), 4));
+ d->setMimeData(m);
+ d->exec(Qt::DropAction::CopyAction);
+ }
+ }
+ return false;
+}
+
+void slice_view::refresh()
+{
+ int curid = lv->currentIndex().data().toInt();
+ m->clear();
+ auto ids = l->map_ids();
+ for (auto id : ids)
+ {
+ map_t map = l->get_map(id);
+ QPixmap pm = pixmap_of_map_data(map.map_data);
+ QString text = QString("(%1)").arg(id);
+ if (map.custom_name.length()) text = QString::fromStdString(map.custom_name) + " " + text;
+ QStandardItem *itm = new QStandardItem(QIcon(pm), text);
+ itm->setData(QVariant(id));
+ m->appendRow(itm);
+ if (id == curid)
+ lv->setCurrentIndex(itm->index());
+ }
+}
diff --git a/mapman/src/sliceview.hpp b/mapman/src/sliceview.hpp
new file mode 100644
index 0000000..3f7ee79
--- /dev/null
+++ b/mapman/src/sliceview.hpp
@@ -0,0 +1,32 @@
+#ifndef SLICEVIEW_HPP
+#define SLICEVIEW_HPP
+
+#include <QMdiSubWindow>
+#include <QModelIndex>
+
+class map_library;
+class map_painter;
+class QListView;
+class QStandardItemModel;
+
+class slice_view : public QMdiSubWindow
+{
+ Q_OBJECT
+public:
+ slice_view();
+ ~slice_view();
+ void set_library(map_library *lib);
+public slots:
+ void refresh();
+protected:
+ bool eventFilter(QObject *o, QEvent *e);
+private:
+ QListView *lv;
+ QStandardItemModel *m;
+ map_painter *p;
+ map_library *l;
+ QModelIndex dragidx;
+ QPointF dragpos;
+};
+
+#endif
diff --git a/mapman/src/utils.cpp b/mapman/src/utils.cpp
new file mode 100644
index 0000000..f01d946
--- /dev/null
+++ b/mapman/src/utils.cpp
@@ -0,0 +1,40 @@
+#include "utils.hpp"
+
+#include <QColor>
+#include <QImage>
+
+rgb_t modify_color(rgb_t c, uint8_t variant)
+{
+ int m;
+ switch (variant)
+ {
+ case 0: m = 180; break;
+ case 1: m = 200; break;
+ case 2: m = 255; break;
+ case 3: m = 135; break;
+ default: m = 0; break;
+ }
+ return {uint8_t(m * c.r / 255),
+ uint8_t(m * c.g / 255),
+ uint8_t(m * c.b / 255)};
+}
+
+QColor rgb2qcolor(rgb_t c) {return QColor(c.r, c.g, c.b);}
+
+QPixmap pixmap_of_map_data(const std::array<uint8_t, 128 * 128> &map_data)
+{
+ QImage ret(128, 128, QImage::Format_ARGB32);
+ for (size_t i = 0; i < 128; ++i)
+ for (size_t j = 0; j < 128; ++j)
+ {
+ uint8_t d = map_data[j * 128 + i];
+ uint8_t b = d >> 2;
+ uint8_t v = d & 3;
+ QColor color = Qt::GlobalColor::transparent;
+ if (b > 0 && b < 62)
+ color = rgb2qcolor(modify_color(MAP_COLORS[b], v));
+ ret.setPixelColor(i, j, color);
+ }
+ return QPixmap::fromImage(ret);
+}
+
diff --git a/mapman/src/utils.hpp b/mapman/src/utils.hpp
new file mode 100644
index 0000000..47df676
--- /dev/null
+++ b/mapman/src/utils.hpp
@@ -0,0 +1,82 @@
+#ifndef UTILS_HPP
+#define UTILS_HPP
+
+#include <cstdint>
+#include <array>
+
+#include <QPixmap>
+
+struct rgb_t
+{
+ uint8_t r,g,b;
+};
+
+typedef std::array<uint8_t, 128 * 128> map_data_t;
+
+const rgb_t MAP_COLORS[62] = {
+ {0, 0, 0},
+ {127, 178, 56},
+ {247, 233, 163},
+ {199, 199, 199},
+ {255, 0, 0},
+ {160, 160, 255},
+ {167, 167, 167},
+ {0, 124, 0},
+ {255, 255, 255},
+ {164, 168, 184},
+ {151, 109, 77},
+ {112, 112, 112},
+ {64, 64, 255},
+ {143, 119, 72},
+ {255, 252, 245},
+ {216, 127, 51},
+ {178, 76, 216},
+ {102, 153, 216},
+ {229, 229, 51},
+ {127, 204, 25},
+ {242, 127, 165},
+ {76, 76, 76},
+ {153, 153, 153},
+ {76, 127, 153},
+ {127, 63, 178},
+ {51, 76, 178},
+ {102, 76, 51},
+ {102, 127, 51},
+ {153, 51, 51},
+ {25, 25, 25},
+ {250, 238, 77},
+ {92, 219, 213},
+ {74, 128, 255},
+ {0, 217, 58},
+ {129, 86, 49},
+ {112, 2, 0},
+ {209, 177, 161},
+ {159, 82, 36},
+ {149, 87, 108},
+ {112, 108, 138},
+ {186, 133, 36},
+ {103, 117, 53},
+ {160, 77, 78},
+ {57, 41, 35},
+ {135, 107, 98},
+ {87, 92, 92},
+ {122, 73, 88},
+ {76, 62, 92},
+ {76, 50, 35},
+ {76, 82, 42},
+ {142, 60, 46},
+ {37, 22, 16},
+ {189, 48, 49},
+ {148, 63, 97},
+ {92, 25, 29},
+ {22, 126, 134},
+ {58, 142, 140},
+ {86, 44, 62},
+ {20, 180, 133},
+ {100, 100, 100},
+ {216, 175, 147},
+ {127, 167, 150}
+};
+
+QPixmap pixmap_of_map_data(const map_data_t &map_data);
+#endif