Skip to content

Commit

Permalink
[texi] change: QListWidget to QListView and a list model
Browse files Browse the repository at this point in the history
add: ability to open key, erf, and zip containers
update: rollnw
  • Loading branch information
jd28 committed Apr 9, 2024
1 parent 2a29c41 commit f2869f8
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 53 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Build
build/
build-debug/
build-doc/
vcpkg/
vcpkg_installed/
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Tools subproject of something referred to as Arclight

- Linux: The latest LTS of Ubuntu, roughly
- macOS:
- Windows: Windows 10 and above, Visual Studio 2019 and above
- Windows: Windows 10 and above, Visual Studio 2019 seems to be the latest Qt supported version

## erfherder

Expand All @@ -25,9 +25,9 @@ erfherder is an editor for [BioWare's ERF File Format](docs/bioware_aurora_engin

## texi

A super mid and basic texture gallery viewer. ERF's only for now. There are probably issues on account of a lack of knowledge. Screenshot is using Bill Harper's great work [HD Clubs](https://neverwintervault.org/project/nwnee/prefab/item/hd-clubs).
A super mid and basic texture gallery viewer. It can open Key, Erf, and Zip containers. Screenshot is using NWN:EE main key file circa 36-2.

![texi](screenshots/texi-2024-04-04.gif)
![texi](screenshots/texi-2024-04-09.gif)

## Acknowledgements, Credits, & Inspirations

Expand Down
Binary file added screenshots/texi-2024-04-09.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/texi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ set(SRC_FILES
mainwindow.h
mainwindow.cpp
mainwindow.ui
texuregallerymodel.h
texuregallerymodel.cpp
${CMAKE_SOURCE_DIR}/external/ZFontIcon/ZFontIcon/Fonts.qrc
)

Expand Down Expand Up @@ -34,6 +36,7 @@ endif()
target_include_directories(texi PRIVATE
../
${CMAKE_SOURCE_DIR}/external/rollnw/external
${CMAKE_SOURCE_DIR}/external/rollnw/external/minizip/include
${CMAKE_SOURCE_DIR}/external/rollnw/lib
${CMAKE_SOURCE_DIR}/external/ZFontIcon
${CMAKE_SOURCE_DIR}/external/
Expand All @@ -43,6 +46,7 @@ target_link_libraries(texi PRIVATE
arclight-widgets
nw
arclight-external
minizip
Qt6::Widgets
)

Expand Down
41 changes: 3 additions & 38 deletions src/texi/mainwindow.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <nw/formats/Image.hpp>
#include <nw/resources/Erf.hpp>
#include "texuregallerymodel.h"

#include <QFileDialog>
#include <QImage>
Expand All @@ -17,51 +16,17 @@ MainWindow::MainWindow(QWidget* parent)
QObject::connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onActionOpen);
}

void MainWindow::loadIcons()
{
if (!container_) {
return;
}

auto cb = [this](const nw::Resource& resource) {
if (resource.type == nw::ResourceType::plt) {
return;
}
if (nw::ResourceType::check_category(nw::ResourceType::texture, resource.type)) {
images_.emplace_back(container_->demand(resource));
labels_.push_back(resource.filename());
}
};

container_->visit(cb);

for (size_t i = 0; i < images_.size(); ++i) {
if (!images_[i].valid()) {
continue;
}
QImage qi(images_[i].data(), images_[i].width(), images_[i].height(),
images_[i].channels() == 4 ? QImage::Format_RGBA8888 : QImage::Format_RGB888);

ui->imageGallery->addItem(new QListWidgetItem(QIcon(QPixmap::fromImage(qi)),
QString::fromStdString(labels_[i])));
}
}

void MainWindow::open(const QString& path)
{
if (!QFileInfo::exists(path)) {
return;
}
std::string p = path.toStdString();

ui->imageGallery->clear();
container_ = std::make_unique<nw::Erf>(p);
loadIcons();
ui->imageGallery->setModel(new TexureGalleryModel(path));
}

void MainWindow::onActionOpen()
{
QString fn = QFileDialog::getOpenFileName(this, "Open Erf", "", "Erf (*.erf *.mod *.hak *.nwm *.sav)");
QString fn = QFileDialog::getOpenFileName(this, "Open Container", "", "Container (*.erf *.hak *.key *.zip)");
if (fn.isEmpty()) {
return;
}
Expand Down
9 changes: 0 additions & 9 deletions src/texi/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@

#include <string>

struct Payload {
nw::Image image;
QImage qimage;
};

namespace Ui {
class MainWindow;
}
Expand All @@ -25,17 +20,13 @@ class MainWindow : public QMainWindow {
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();

void loadIcons();
void open(const QString& path);

public slots:
void onActionOpen();

private:
Ui::MainWindow* ui;
std::unique_ptr<nw::Container> container_;
std::vector<nw::Image> images_;
std::vector<std::string> labels_;
};

#endif // MAINWINDOW_H
19 changes: 17 additions & 2 deletions src/texi/mainwindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListWidget" name="imageGallery">
<widget class="QListView" name="imageGallery">
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="iconSize">
<size>
<width>256</width>
Expand All @@ -26,15 +32,24 @@
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="layoutMode">
<enum>QListView::SinglePass</enum>
</property>
<property name="spacing">
<number>5</number>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<property name="selectionRectVisible">
<bool>false</bool>
</property>
<property name="itemAlignment">
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
</property>
</widget>
</item>
</layout>
Expand Down
105 changes: 105 additions & 0 deletions src/texi/texuregallerymodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "texuregallerymodel.h"

#include "nw/formats/Image.hpp"
#include "nw/log.hpp"
#include "nw/resources/Directory.hpp"
#include "nw/resources/Erf.hpp"
#include "nw/resources/Key.hpp"
#include "nw/resources/Zip.hpp"
#include "nw/util/platform.hpp"

#include <QIcon>
#include <QImage>

inline std::unique_ptr<nw::Container> load_container(const std::filesystem::path& p)
{
auto ext = nw::path_to_string(p.extension());

if (std::filesystem::is_directory(p)) {
return std::make_unique<nw::Directory>(p);
} else if (nw::string::icmp(ext, ".hak")) {
return std::make_unique<nw::Erf>(p);
} else if (nw::string::icmp(ext, ".erf")) {
return std::make_unique<nw::Erf>(p);
} else if (nw::string::icmp(ext, ".zip")) {
return std::make_unique<nw::Zip>(p);
} else if (nw::string::icmp(ext, ".key")) {
return std::make_unique<nw::Key>(p);
}
return nullptr;
}

TexureGalleryModel::TexureGalleryModel(const QString& path, QObject* parent)
: QAbstractListModel(parent)
{
container_ = load_container(path.toStdString());

auto cb = [this](const nw::Resource& resource) {
if (resource.type == nw::ResourceType::plt) {
return;
}
if (nw::ResourceType::check_category(nw::ResourceType::texture, resource.type)) {
labels_.push_back(resource);
}
};

container_->visit(cb);
std::sort(std::begin(labels_), std::end(labels_));
}

QVariant TexureGalleryModel::headerData(int section, Qt::Orientation orientation, int role) const
{
return {};
}

int TexureGalleryModel::rowCount(const QModelIndex& parent) const
{
return labels_.size();
}

int TexureGalleryModel::columnCount(const QModelIndex& parent) const
{
return 1;
}

QVariant TexureGalleryModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();

if (role == Qt::DecorationRole) {
auto it = cache_.find(labels_[index.row()]);
if (it != std::end(cache_)) {
return it->second;
} else {
nw::Image img{container_->demand(labels_[index.row()])};
if (!img.valid()) {
QVariant();
}

QImage qi(img.data(), img.width(), img.height(),
img.channels() == 4 ? QImage::Format_RGBA8888 : QImage::Format_RGB888);
if (qi.height() > 128 || qi.width() > 128) {
qi = qi.scaled(128, 128, Qt::KeepAspectRatio);
}
if (img.is_bio_dds()) {
qi.mirror();
}
auto it2 = cache_.emplace(labels_[index.row()], QPixmap::fromImage(qi));
return it2.first->second;
}
} else if (role == Qt::SizeHintRole) {
return QSize(128, 150);
} else if (role == Qt::DisplayRole) {
return QString::fromStdString(labels_[index.row()].filename());
} else if (role == Qt::TextAlignmentRole) {
return (Qt::AlignBottom | Qt::AlignHCenter).toInt();
} else if (role == Qt::ToolTipRole) {
auto it = cache_.find(labels_[index.row()]);
if (it == std::end(cache_)) {
return {};
}
return QString::fromStdString(fmt::format("{}x{}", it->second.width(), it->second.height()));
}
return QVariant();
}
34 changes: 34 additions & 0 deletions src/texi/texuregallerymodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef TEXUREGALLERYMODEL_H
#define TEXUREGALLERYMODEL_H

#include "nw/resources/Container.hpp"

#include "absl/container/flat_hash_map.h"

#include <QAbstractListModel>
#include <QPixmap>

#include <string>

class TexureGalleryModel : public QAbstractListModel {
Q_OBJECT

public:
explicit TexureGalleryModel(const QString& path, QObject* parent = nullptr);

// Header:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

// Basic functionality:
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;

private:
std::unique_ptr<nw::Container> container_;
std::vector<nw::Resource> labels_;
mutable absl::flat_hash_map<nw::Resource, QPixmap> cache_;
};

#endif // TEXUREGALLERYMODEL_H

0 comments on commit f2869f8

Please sign in to comment.