Start adding character support, creation actions and more

This commit is contained in:
Joshua Goins 2024-02-21 17:51:17 -05:00
parent aaaa489116
commit 630a373cca
16 changed files with 558 additions and 62 deletions

View file

@ -11,8 +11,9 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(QT_MIN_VERSION 5.15)
set(KF5_MIN_VERSION 5.83)
set(QT_MAJOR_VERSION 6)
set(QT_MIN_VERSION 6.5)
set(KF_MIN_VERSION 5.240)
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
@ -27,12 +28,15 @@ include(ECMPoQmTools)
include(KDEGitCommitHooks)
include(KDEClangFormat)
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE REQUIRED COMPONENTS
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE REQUIRED COMPONENTS
Core
Gui
Widgets
Concurrent)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS CoreAddons XmlGui I18n)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS CoreAddons XmlGui I18n)
find_package(QCoro6 REQUIRED COMPONENTS Core Network DBus)
qcoro_enable_coroutines()
add_executable(Redai)
target_sources(Redai PRIVATE
@ -43,6 +47,10 @@ target_sources(Redai PRIVATE
src/artdetailwindow.h
src/artmodel.cpp
src/artmodel.h
src/characterdetailwindow.cpp
src/characterdetailwindow.h
src/charactermodel.cpp
src/charactermodel.h
src/featuredartmodel.cpp
src/featuredartmodel.h
src/imagelabel.cpp
@ -51,13 +59,16 @@ target_sources(Redai PRIVATE
src/mainwindow.cpp
src/mainwindow.h)
target_link_libraries(Redai
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::Concurrent
KF5::I18n
KF5::CoreAddons
KF5::XmlGui)
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Concurrent
KF6::I18n
KF6::CoreAddons
KF6::XmlGui
QCoro::Core
QCoro::Network)
target_compile_options(Redai PRIVATE -fexceptions)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

View file

@ -13,7 +13,6 @@
#include <QJsonDocument>
#include <QLineEdit>
#include <QPushButton>
#include <QScrollArea>
ArtConfigWindow::ArtConfigWindow(const QString &filename, const QString &definitionDirectory, const QString &assetDirectory, QWidget *parent)
: QDialog(parent)

View file

@ -6,6 +6,8 @@
#include "imagelabel.h"
#include <KLocalizedString>
#include <QCoroNetwork>
#include <QCoroTask>
#include <QDateEdit>
#include <QFile>
#include <QFileInfo>
@ -15,10 +17,11 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QLineEdit>
#include <QNetworkAccessManager>
#include <QPushButton>
#include <QTextEdit>
ArtDetailWindow::ArtDetailWindow(const QString &filename, const QDir &assetDirectory, QWidget *parent)
ArtDetailWindow::ArtDetailWindow(const QString &filename, const QUrl &imagePath, QWidget *parent)
: QDialog(parent)
{
setMinimumWidth(800);
@ -38,8 +41,24 @@ ArtDetailWindow::ArtDetailWindow(const QString &filename, const QDir &assetDirec
formLayoutWidget->setLayout(formLayout);
mainLayout->addWidget(formLayoutWidget);
QImage image;
image.load(assetDirectory.absoluteFilePath(QStringLiteral("%1.webp").arg(withoutExtension)));
// load thumbnail
const auto load_image = [imagePath]() -> QCoro::Task<QImage> {
QImage image;
if (!imagePath.isLocalFile()) {
QNetworkAccessManager nam;
auto reply = co_await nam.get(QNetworkRequest(imagePath));
const auto data = reply->readAll();
reply->deleteLater();
image.loadFromData(data, "image/jpeg");
} else {
image.load(imagePath.toLocalFile());
}
co_return image;
};
auto image = QCoro::waitFor(load_image());
auto previewBox = new QGroupBox(i18nc("@title:group", "Preview"));
mainLayout->addWidget(previewBox);

View file

@ -18,7 +18,7 @@ class ArtDetailWindow : public QDialog
{
Q_OBJECT
public:
ArtDetailWindow(const QString &filename, const QDir &assetDirectory, QWidget *parent = nullptr);
ArtDetailWindow(const QString &filename, const QUrl &imagePath, QWidget *parent = nullptr);
private:
void loadData(const QString &filename);

View file

@ -5,21 +5,24 @@
#include "artmodel.h"
#include <KLocalizedString>
#include <QCoroNetwork>
#include <QCoroTask>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QIcon>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QPixmap>
#include <QtConcurrent>
ArtModel::ArtModel(const QDir &definitionDirectory, const QDir &assetDirectory, QObject *parent)
: QAbstractTableModel(parent)
{
piecesFuture = new QFutureWatcher<ArtPiece>(this);
connect(piecesFuture, &QFutureWatcher<ArtPiece>::resultReadyAt, this, &ArtModel::pieceFinished);
connect(piecesFuture, &QFutureWatcher<ArtPiece>::finished, this, &ArtModel::finished);
piecesFuture = new QFutureWatcher<ArtPiece *>(this);
connect(piecesFuture, &QFutureWatcher<ArtPiece *>::resultReadyAt, this, &ArtModel::pieceFinished);
connect(piecesFuture, &QFutureWatcher<ArtPiece *>::finished, this, &ArtModel::finished);
struct PieceInformation {
QString definition;
@ -36,34 +39,46 @@ ArtModel::ArtModel(const QDir &definitionDirectory, const QDir &assetDirectory,
pieceList.push_back(PieceInformation{definitionDirectory.absoluteFilePath(info.baseName()), assetDirectory.absoluteFilePath(info.baseName())});
beginInsertRows(QModelIndex(), m_artPieces.size(), m_artPieces.size() + 1);
beginInsertRows(QModelIndex(), static_cast<int>(m_artPieces.size()), static_cast<int>(m_artPieces.size() + 1));
m_artPieces.push_back({});
endInsertRows();
}
const std::function<ArtPiece(const PieceInformation &info)> loadPiece = [](const PieceInformation &info) -> ArtPiece {
ArtPiece p(info.definition, info.asset);
const std::function<QCoro::Task<ArtPiece *>(const PieceInformation &info)> loadPiece = [](const PieceInformation &info) -> QCoro::Task<ArtPiece *> {
auto p = new ArtPiece(info.definition, info.asset);
p.image.load(p.filename);
p.thumbnail = QPixmap::fromImage(p.image).scaled(100, 100, Qt::AspectRatioMode::KeepAspectRatio).toImage();
// load thumbnail
if (!p->getThumbnailPath().isLocalFile()) {
QNetworkAccessManager nam;
auto reply = co_await nam.get(QNetworkRequest(p->getThumbnailPath()));
const auto data = reply->readAll();
reply->deleteLater();
return p;
p->thumbnail.loadFromData(data, "image/jpeg");
} else {
p->thumbnail.load(p->getThumbnailPath().toLocalFile());
}
p->thumbnail = p->thumbnail.scaled(100, 100, Qt::AspectRatioMode::KeepAspectRatio, Qt::SmoothTransformation);
co_return p;
};
piecesFuture->setFuture(QtConcurrent::mapped(pieceList, loadPiece));
piecesFuture->setFuture(QtConcurrent::mapped(pieceList, [loadPiece](const PieceInformation &info) -> ArtPiece * {
return QCoro::waitFor(loadPiece(info));
}));
}
int ArtModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_artPieces.size();
Q_UNUSED(parent)
return static_cast<int>(m_artPieces.size());
}
int ArtModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
Q_UNUSED(parent)
return 4;
}
@ -73,30 +88,32 @@ QVariant ArtModel::data(const QModelIndex &index, const int role) const
return {};
}
if (m_artPieces[index.row()] == nullptr) {
return {};
}
if (role == Qt::DisplayRole) {
switch (index.column()) {
case 0: {
const QString filename = m_artPieces[index.row()].filename;
const QString filename = m_artPieces[index.row()]->filename;
return filename.split('/').last();
}
case 1:
return {};
case 2:
return m_artPieces[index.row()].title;
return m_artPieces[index.row()]->title;
case 3:
return m_artPieces[index.row()].hasAltText;
return m_artPieces[index.row()]->hasAltText;
}
} else if (role == Qt::UserRole) {
return m_artPieces[index.row()].object;
return QVariant::fromValue(m_artPieces[index.row()]);
} else if (role == Qt::DecorationRole) {
switch (index.column()) {
case 1:
return m_artPieces[index.row()].thumbnail;
return m_artPieces[index.row()]->thumbnail;
case 3:
return m_artPieces[index.row()].hasAltText ? QIcon::fromTheme(QStringLiteral("emblem-checked")) : QIcon::fromTheme(QStringLiteral("emblem-error"));
return m_artPieces[index.row()]->hasAltText ? QIcon::fromTheme(QStringLiteral("emblem-checked")) : QIcon::fromTheme(QStringLiteral("emblem-error"));
}
} else if (role == Qt::UserRole + 1) {
return m_artPieces[index.row()].jsonFilename;
}
return {};
@ -131,11 +148,11 @@ void ArtModel::pieceFinished(const int row)
void ArtModel::finished()
{
std::sort(m_artPieces.begin(), m_artPieces.end(), [](const ArtPiece &a, const ArtPiece &b) {
return a.date > b.date;
std::sort(m_artPieces.begin(), m_artPieces.end(), [](const ArtPiece *a, const ArtPiece *b) {
return a->date > b->date;
});
Q_EMIT dataChanged(index(0, 0), index(m_artPieces.size(), 3));
Q_EMIT dataChanged(index(0, 0), index(static_cast<int>(m_artPieces.size()), 3));
Q_EMIT loadingFinished();
}
@ -164,3 +181,23 @@ ArtPiece::ArtPiece(const QString &filename, const QString &assetFilename)
hasAltText = true;
}
}
QUrl ArtPiece::getImagePath() const
{
if (date.year() > 2022) {
QFileInfo info(filename);
return QUrl::fromUserInput(QStringLiteral("https://images.redstrate.com/art/%1.jpg").arg(info.fileName()));
} else {
return QUrl::fromLocalFile(QStringLiteral("%1.webp").arg(filename));
}
}
QUrl ArtPiece::getThumbnailPath() const
{
if (date.year() > 2022) {
QFileInfo info(filename);
return QUrl::fromUserInput(QStringLiteral("https://images.redstrate.com/thumb/%1.jpg").arg(info.fileName()));
} else {
return QUrl::fromLocalFile(QStringLiteral("%1.webp").arg(filename));
}
}

View file

@ -9,9 +9,11 @@
#include <QFutureWatcher>
#include <QImage>
#include <QJsonObject>
#include <QNetworkAccessManager>
class ArtPiece
class ArtPiece : public QObject
{
Q_OBJECT
public:
ArtPiece() = default;
ArtPiece(const QString &filename, const QString &assetFilename);
@ -21,9 +23,12 @@ public:
QJsonObject object;
QDate date;
QImage image, thumbnail;
QImage thumbnail;
bool hasAltText = false;
QUrl getImagePath() const;
QUrl getThumbnailPath() const;
};
class ArtModel : public QAbstractTableModel
@ -42,11 +47,11 @@ Q_SIGNALS:
void loadingFinished();
protected:
QVector<ArtPiece> m_artPieces;
QVector<ArtPiece *> m_artPieces;
private:
void pieceFinished(int index);
void finished();
QFutureWatcher<ArtPiece> *piecesFuture;
QFutureWatcher<ArtPiece *> *piecesFuture;
};

View file

@ -0,0 +1,148 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "characterdetailwindow.h"
#include "imagelabel.h"
#include <KLocalizedString>
#include <QFile>
#include <QFileInfo>
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QJsonArray>
#include <QJsonDocument>
#include <QLineEdit>
#include <QPushButton>
#include <QTextEdit>
CharacterDetailWindow::CharacterDetailWindow(const QString &filename, QWidget *parent)
: QDialog(parent)
{
setMinimumWidth(800);
setMinimumHeight(600);
setWindowModality(Qt::WindowModality::WindowModal);
setWindowTitle(filename);
QFileInfo info(filename);
const QString withoutExtension = info.completeBaseName();
auto mainLayout = new QHBoxLayout();
setLayout(mainLayout);
auto formLayout = new QFormLayout();
auto formLayoutWidget = new QWidget();
formLayoutWidget->setMaximumWidth(450);
formLayoutWidget->setLayout(formLayout);
mainLayout->addWidget(formLayoutWidget);
auto previewBox = new QGroupBox(i18nc("@title:group", "Preview"));
mainLayout->addWidget(previewBox);
auto previewLayout = new QVBoxLayout();
previewBox->setLayout(previewLayout);
auto imageView = new ImageLabel();
previewLayout->addWidget(imageView);
m_nameEdit = new QLineEdit();
formLayout->addRow(i18nc("@label:textbox", "Title"), m_nameEdit);
m_pronounsEdit = new QLineEdit();
formLayout->addRow(i18nc("@label:textbox", "Pronouns"), m_pronounsEdit);
m_descriptionEdit = new QTextEdit();
m_descriptionEdit->setAcceptRichText(false);
formLayout->addRow(i18nc("@label:textbox", "Description"), m_descriptionEdit);
m_ageEdit = new QLineEdit();
formLayout->addRow(i18nc("@label:textbox", "Age"), m_ageEdit);
m_originEdit = new QLineEdit();
formLayout->addRow(i18nc("@label:textbox", "Origin"), m_originEdit);
auto bottomButtonLayout = new QHBoxLayout();
formLayout->addRow(bottomButtonLayout);
auto cancelButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-close")), i18nc("@action:button", "Cancel"));
connect(cancelButton, &QPushButton::clicked, this, &CharacterDetailWindow::close);
bottomButtonLayout->addWidget(cancelButton);
bottomButtonLayout->addStretch(1);
auto saveButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18nc("@action:button", "Save"));
connect(saveButton, &QPushButton::clicked, this, [this, filename] {
saveData(filename);
});
bottomButtonLayout->addWidget(saveButton);
if (QFile::exists(filename)) {
loadData(filename);
}
}
void CharacterDetailWindow::loadData(const QString &filename)
{
qDebug() << "Loading data from" << filename;
QFile artFile(filename);
artFile.open(QFile::ReadOnly);
const QJsonDocument artJson = QJsonDocument::fromJson(artFile.readAll());
if (artJson.object().contains(QStringLiteral("name"))) {
m_nameEdit->setText(artJson[QStringLiteral("name")].toString());
}
if (artJson.object().contains(QStringLiteral("pronouns"))) {
m_pronounsEdit->setText(artJson[QStringLiteral("pronouns")].toString());
}
if (artJson.object().contains(QStringLiteral("age"))) {
m_ageEdit->setText(artJson[QStringLiteral("age")].toString());
}
if (artJson.object().contains(QStringLiteral("description"))) {
m_descriptionEdit->setText(artJson[QStringLiteral("description")].toString());
}
if (artJson.object().contains(QStringLiteral("origin"))) {
m_originEdit->setText(artJson[QStringLiteral("origin")].toString());
}
}
void CharacterDetailWindow::saveData(const QString &filename)
{
qDebug() << "Saving data to" << filename;
QJsonObject object;
if (!m_nameEdit->text().isEmpty()) {
object[QStringLiteral("name")] = m_nameEdit->text();
}
if (!m_pronounsEdit->text().isEmpty()) {
object[QStringLiteral("pronouns")] = m_pronounsEdit->text();
}
if (!m_ageEdit->text().isEmpty()) {
object[QStringLiteral("age")] = m_ageEdit->text();
}
if (!m_descriptionEdit->document()->toPlainText().isEmpty()) {
object[QStringLiteral("description")] = m_descriptionEdit->document()->toPlainText();
}
if (!m_originEdit->text().isEmpty()) {
object[QStringLiteral("origin")] = m_originEdit->text();
}
const QJsonDocument jsonDoc(object);
QFile file(filename);
file.open(QFile::WriteOnly);
file.write(jsonDoc.toJson());
file.close();
close();
}

View file

@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QCheckBox>
#include <QDateEdit>
#include <QDialog>
#include <QDir>
#include <QJsonObject>
#include <QLineEdit>
#include <QListWidget>
#include <QStringListModel>
#include <QTextEdit>
class CharacterDetailWindow : public QDialog
{
Q_OBJECT
public:
explicit CharacterDetailWindow(const QString &filename, QWidget *parent = nullptr);
private:
void loadData(const QString &filename);
void saveData(const QString &filename);
QLineEdit *m_nameEdit;
QLineEdit *m_pronounsEdit;
QTextEdit *m_descriptionEdit;
QLineEdit *m_ageEdit;
QLineEdit *m_originEdit;
};

135
src/charactermodel.cpp Normal file
View file

@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "charactermodel.h"
#include <KLocalizedString>
#include <QCoroTask>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QPixmap>
#include <QtConcurrent>
CharacterModel::CharacterModel(const QDir &definitionDirectory, QObject *parent)
: QAbstractTableModel(parent)
{
charactersFuture = new QFutureWatcher<Character *>(this);
connect(charactersFuture, &QFutureWatcher<Character *>::resultReadyAt, this, &CharacterModel::characterFinished);
connect(charactersFuture, &QFutureWatcher<Character *>::finished, this, &CharacterModel::finished);
struct CharacterInformation {
QString definition;
};
QVector<CharacterInformation> characterList;
QDirIterator it(definitionDirectory);
while (it.hasNext()) {
QFileInfo info(it.next());
if (!info.isFile()) {
continue;
}
characterList.push_back(CharacterInformation{definitionDirectory.absoluteFilePath(info.baseName())});
beginInsertRows(QModelIndex(), static_cast<int>(m_characters.size()), static_cast<int>(m_characters.size() + 1));
m_characters.push_back({});
endInsertRows();
}
const std::function<QCoro::Task<Character *>(const CharacterInformation &info)> loadPiece =
[](const CharacterInformation &info) -> QCoro::Task<Character *> {
auto p = new Character(info.definition);
co_return p;
};
charactersFuture->setFuture(QtConcurrent::mapped(characterList, [loadPiece](const CharacterInformation &info) -> Character * {
return QCoro::waitFor(loadPiece(info));
}));
}
int CharacterModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return static_cast<int>(m_characters.size());
}
int CharacterModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
}
QVariant CharacterModel::data(const QModelIndex &index, const int role) const
{
if (!index.isValid()) {
return {};
}
if (m_characters[index.row()] == nullptr) {
return {};
}
if (role == Qt::DisplayRole) {
switch (index.column()) {
case 0: {
return m_characters[index.row()]->name;
}
}
} else if (role == Qt::UserRole) {
return QVariant::fromValue(m_characters[index.row()]);
}
return {};
}
QVariant CharacterModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Orientation::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case 0:
return i18nc("@title:column", "Name");
default:
Q_UNREACHABLE();
}
}
return QAbstractTableModel::headerData(section, orientation, role);
}
void CharacterModel::characterFinished(const int row)
{
m_characters[row] = charactersFuture->resultAt(row);
Q_EMIT dataChanged(index(row, 0), index(row + 1, 0));
}
void CharacterModel::finished()
{
std::sort(m_characters.begin(), m_characters.end(), [](const Character *a, const Character *b) {
return a->name > b->name;
});
Q_EMIT dataChanged(index(0, 0), index(static_cast<int>(m_characters.size()), 3));
Q_EMIT loadingFinished();
}
Character::Character(const QString &filename)
{
this->filename = filename + ".json";
QFile artFile(this->filename);
artFile.open(QFile::ReadOnly);
const QJsonDocument artJson = QJsonDocument::fromJson(artFile.readAll());
if (artJson.object().contains(QStringLiteral("name"))) {
name = artJson.object()[QStringLiteral("name")].toString();
}
}

48
src/charactermodel.h Normal file
View file

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QAbstractTableModel>
#include <QDir>
#include <QFutureWatcher>
#include <QImage>
#include <QJsonObject>
#include <QNetworkAccessManager>
class Character : public QObject
{
Q_OBJECT
public:
Character() = default;
Character(const QString &filename);
QString filename;
QString name;
};
class CharacterModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit CharacterModel(const QDir &definitionDirectory, QObject *parent = nullptr);
[[nodiscard]] int rowCount(const QModelIndex &parent) const override;
[[nodiscard]] int columnCount(const QModelIndex &parent) const override;
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
Q_SIGNALS:
void loadingFinished();
protected:
QVector<Character *> m_characters;
private:
void characterFinished(int index);
void finished();
QFutureWatcher<Character *> *charactersFuture;
};

View file

@ -14,7 +14,7 @@ FeaturedArtModel::FeaturedArtModel(const QString &definitionDirectory, const QSt
QVariant FeaturedArtModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
return {};
if (role == Qt::DisplayRole) {
if (index.column() == 0) {
@ -57,12 +57,12 @@ bool FeaturedArtModel::setData(const QModelIndex &index, const QVariant &value,
return true;
}
void FeaturedArtModel::setFeaturedItems(QStringList featured)
void FeaturedArtModel::setFeaturedItems(const QStringList &featured)
{
for (const auto &id : featured) {
for (int i = 0; i < m_artPieces.size(); i++) {
ArtPiece &piece = m_artPieces[i];
QFileInfo fileInfo(piece.jsonFilename);
auto &piece = m_artPieces[i];
QFileInfo fileInfo(piece->jsonFilename);
if (fileInfo.baseName() == id) {
setData(index(i, 0), Qt::Checked, Qt::CheckStateRole);
}

View file

@ -17,7 +17,7 @@ public:
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
void setFeaturedItems(QStringList featured);
void setFeaturedItems(const QStringList &featured);
private:
QSet<QPersistentModelIndex> checkedItems;

View file

@ -35,7 +35,7 @@ QPixmap ImageLabel::scaledPixmap() const
void ImageLabel::resizeEvent(QResizeEvent *e)
{
Q_UNUSED(e);
Q_UNUSED(e)
if (!pix.isNull()) {
QLabel::setPixmap(scaledPixmap());
}

View file

@ -12,9 +12,6 @@
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QApplication app(argc, argv);
KLocalizedString::setApplicationDomain("redai");
@ -24,7 +21,7 @@ int main(int argc, char *argv[])
QStringLiteral("1.0"),
i18n("Website gallery manager"),
KAboutLicense::GPL_V3,
i18n("© 2023 Joshua Goins"));
i18n("© 2024 Joshua Goins"));
about.addAuthor(i18n("Joshua Goins"), i18n("Maintainer"), QStringLiteral("josh@redstrate.com"), QStringLiteral("https://redstrate.com/"));
about.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails"));
@ -45,8 +42,9 @@ int main(int argc, char *argv[])
const QDir assetPath = sitePath.absoluteFilePath(QStringLiteral("assets"));
const QString artAssetPath = assetPath.absoluteFilePath(QStringLiteral("art"));
const QString dataPath = sitePath.absoluteFilePath(QStringLiteral("data"));
const QString charaDefPath = sitePath.absoluteFilePath(QStringLiteral("characters"));
MainWindow window(defPath, artAssetPath, dataPath);
MainWindow window(defPath, artAssetPath, dataPath, charaDefPath);
window.show();
return QApplication::exec();

View file

@ -10,24 +10,59 @@
#include <QApplication>
#include <QDesktopServices>
#include <QHeaderView>
#include <QInputDialog>
#include <QMenuBar>
#include <QPointer>
#include <QTableView>
#include <QToolBar>
#include "artconfigwindow.h"
#include "artdetailwindow.h"
#include "artmodel.h"
#include "characterdetailwindow.h"
#include "charactermodel.h"
MainWindow::MainWindow(const QDir &definitionDirectory, const QDir &assetDirectory, const QDir &dataDirectory, QWidget *parent)
MainWindow::MainWindow(const QDir &definitionDirectory,
const QDir &assetDirectory,
const QDir &dataDirectory,
const QDir &charactersDefinitionDirectory,
QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle(i18nc("@title:window", "Redai"));
setMinimumSize(1280, 720);
auto toolbar = new QToolBar();
toolbar->setToolButtonStyle(Qt::ToolButtonStyle::ToolButtonTextBesideIcon);
addToolBar(toolbar);
auto newArtworkAction = toolbar->addAction(i18nc("@action", "Add Artwork"));
newArtworkAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
connect(newArtworkAction, &QAction::triggered, this, [this, definitionDirectory] {
const QString text = QInputDialog::getText(this, i18n("Identifier"), i18n("Identifier:"));
if (!text.isEmpty()) {
auto window = new ArtDetailWindow(QStringLiteral("%1/%2.json").arg(definitionDirectory.absolutePath(), text), QStringLiteral(""), this);
window->show();
}
});
auto newCharacterAction = toolbar->addAction(i18nc("@action", "Add Character"));
newCharacterAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
connect(newCharacterAction, &QAction::triggered, this, [this, charactersDefinitionDirectory] {
const QString text = QInputDialog::getText(this, i18n("Identifier"), i18n("Identifier:"));
if (!text.isEmpty()) {
auto window = new CharacterDetailWindow(QStringLiteral("%1/%2.json").arg(charactersDefinitionDirectory.absolutePath(), text), this);
window->show();
}
});
auto menuBar = new QMenuBar();
setMenuBar(menuBar);
auto fileMenu = menuBar->addMenu(i18nc("@title:menu", "File"));
fileMenu->addAction(newArtworkAction);
fileMenu->addAction(newCharacterAction);
fileMenu->addSeparator();
auto quitAction = fileMenu->addAction(i18nc("@action:inmenu", "Quit"));
quitAction->setIcon(QIcon::fromTheme(QStringLiteral("gtk-quit")));
@ -57,7 +92,7 @@ MainWindow::MainWindow(const QDir &definitionDirectory, const QDir &assetDirecto
auto aboutNovusAction = helpMenu->addAction(i18nc("@action:inmenu", "About Redai"));
aboutNovusAction->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
connect(aboutNovusAction, &QAction::triggered, this, [this] {
connect(aboutNovusAction, &QAction::triggered, this, [] {
static QPointer<QDialog> dialog;
if (!dialog) {
dialog = new KAboutApplicationDialog(KAboutData::applicationData(), nullptr);
@ -70,6 +105,9 @@ MainWindow::MainWindow(const QDir &definitionDirectory, const QDir &assetDirecto
aboutQtAction->setIcon(QIcon(QStringLiteral(":/qt-project.org/qmessagebox/images/qtlogo-64.png")));
connect(aboutQtAction, &QAction::triggered, QApplication::instance(), &QApplication::aboutQt);
auto tabWidget = new QTabWidget();
setCentralWidget(tabWidget);
auto model = new ArtModel(definitionDirectory, assetDirectory);
auto pieceListView = new QTableView();
@ -84,12 +122,33 @@ MainWindow::MainWindow(const QDir &definitionDirectory, const QDir &assetDirecto
horizontalHeader->setSectionResizeMode(QHeaderView::Stretch);
connect(pieceListView, &QListView::clicked, this, [this, assetDirectory](const QModelIndex index) {
const QString filename = index.data(Qt::UserRole + 1).toString();
const QJsonObject object = index.data(Qt::UserRole).toJsonObject();
const auto &piece = index.data(Qt::UserRole).value<ArtPiece *>();
auto window = new ArtDetailWindow(filename, assetDirectory, this);
auto window = new ArtDetailWindow(piece->jsonFilename, piece->getImagePath(), this);
window->show();
});
setCentralWidget(pieceListView);
tabWidget->addTab(pieceListView, QStringLiteral("Pieces"));
auto charModel = new CharacterModel(charactersDefinitionDirectory);
auto charView = new QTableView();
charView->setModel(charModel);
charView->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
charView->setSelectionMode(QAbstractItemView::SingleSelection);
verticalHeader = charView->verticalHeader();
verticalHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
horizontalHeader = charView->horizontalHeader();
horizontalHeader->setSectionResizeMode(QHeaderView::Stretch);
connect(charView, &QListView::clicked, this, [this, assetDirectory](const QModelIndex index) {
const auto &piece = index.data(Qt::UserRole).value<Character *>();
auto window = new CharacterDetailWindow(piece->filename, this);
window->show();
});
tabWidget->addTab(charView, QStringLiteral("Characters"));
}

View file

@ -11,5 +11,9 @@ class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(const QDir &definitionDirectory, const QDir &assetDirectory, const QDir &dataDirectory, QWidget *parent = nullptr);
explicit MainWindow(const QDir &definitionDirectory,
const QDir &assetDirectory,
const QDir &dataDirectory,
const QDir &charactersDefinitionDirectory,
QWidget *parent = nullptr);
};