Skip to content

Commit

Permalink
Object Editor Events (#153)
Browse files Browse the repository at this point in the history
* Adds a dialog for setting event arguments when adding or changing an object event.
* Adds a display proxy model for the events to have icons, tooltips, and translations.
* Adds a model representing types of events that can be selected and added.
* Adds a sort proxy model for alphanumerically stable sorting the event types by group and name.
* Check if RepeatedModel::headerData section is also less than 0 like official Qt models do, including QStandardItemModel.
* Explicitly return invalid QModelIndex in RepeatedModel::headerData when invalid section requested.
* Fix a comment typo in RepeatedModel header.
* Add a TypeCase user role to the tree model for filtering asset selection.
* Put events and properties of the object editor into tabs with events selected by default.
* Inline the code editor to the object editor as a master detail view like the timeline editor.
* Separate the properties tabs and the code editor using a horizontal splitter so the properties can be collapsed.
* Rename some event icons to match event name scheme.
* Put the main window event data into a static unique pointer so it doesn't leak.
* Add CMakeLists.txt to DISTFILES in the pro so you can select it from "Other FIles" in Qt Creator. While this is poorly documented because it says DISTFILES is Unix only, this is what Qt Creator does when adding the external file through Qt Creator, it does not add the file to the build, only makes it easier to edit.
* Updated the ENIGMA submodule so the new events can be used, including fixes to the API.
  • Loading branch information
fundies authored Sep 2, 2020
1 parent 33a6d74 commit 97df14f
Show file tree
Hide file tree
Showing 24 changed files with 1,004 additions and 504 deletions.
12 changes: 10 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ set(RGM_SOURCES
Plugins/RGMPlugin.cpp
main.cpp
MainWindow.cpp
Dialogs/EventArgumentsDialog.cpp
Dialogs/PreferencesDialog.cpp
Dialogs/TimelineChangeMoment.cpp
Components/ArtManager.cpp
Expand All @@ -68,6 +69,9 @@ set(RGM_SOURCES
Models/ImmediateMapper.cpp
Models/ResourceModelMap.cpp
Models/TreeModel.cpp
Models/EventsListModel.cpp
Models/EventTypesListModel.cpp
Models/EventTypesListSortFilterProxyModel.cpp
Editors/ObjectEditor.cpp
Editors/PathEditor.cpp
Editors/CodeEditor.cpp
Expand Down Expand Up @@ -98,6 +102,7 @@ set(RGM_HEADERS
Plugins/RGMPlugin.h
MainWindow.h
main.h
Dialogs/EventArgumentsDialog.h
Dialogs/PreferencesDialog.h
Dialogs/PreferencesKeys.h
Dialogs/TimelineChangeMoment.h
Expand All @@ -118,6 +123,9 @@ set(RGM_HEADERS
Models/RepeatedMessageModel.h
Models/ProtoModel.h
Models/RepeatedModel.h
Models/EventsListModel.h
Models/EventTypesListModel.h
Models/EventTypesListSortFilterProxyModel.h
Editors/FontEditor.h
Editors/PathEditor.h
Editors/SpriteEditor.h
Expand Down Expand Up @@ -206,7 +214,7 @@ set(CompilerFlags
CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_RELWITHDEBINFO
)

foreach(CompilerFlag ${CompilerFlags})
message(STATUS " '${CompilerFlag}': ${${CompilerFlag}}")
endforeach()
Expand Down Expand Up @@ -319,7 +327,7 @@ file(TO_CMAKE_PATH ${VCPKG_ROOT} VCPKG_ROOT)
set(SEARCH_PATHS "${VCPKG_ROOT}/installed/x64-windows/bin/")
endif()
else()
set(LIBS "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}EGM${CMAKE_SHARED_LIBRARY_SUFFIX}"
set(LIBS "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}EGM${CMAKE_SHARED_LIBRARY_SUFFIX}"
"${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}Protocols${CMAKE_SHARED_LIBRARY_SUFFIX}"
"${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}ENIGMAShared${CMAKE_SHARED_LIBRARY_SUFFIX}")
endif()
Expand Down
105 changes: 105 additions & 0 deletions Dialogs/EventArgumentsDialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "EventArgumentsDialog.h"
#include "Components/QMenuView.h"
#include "MainWindow.h"
#include "Models/TreeSortFilterProxyModel.h"

#include <QComboBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMetaProperty>
#include <QPushButton>
#include <QSpinBox>
#include <QToolButton>

EventArgumentsDialog::EventArgumentsDialog(QWidget *parent, const QStringList &arguments) : QDialog(parent) {
QGridLayout *layout = new QGridLayout(this);

int row = 0;
for (const auto &arg : arguments) {
QLabel *name = new QLabel(this);
name->setText(arg);

QWidget *value;

if (arg == "integer") {
QSpinBox *integer = new QSpinBox(this);
integer->setMinimum(0);
value = integer;
layout->addWidget(value, row, 1);
} else if (arg == "string") {
QLineEdit *lineEdit = new QLineEdit(this);
lineEdit->setText(tr("MyCustomEvent"));
value = lineEdit;
layout->addWidget(value, row, 1);
} else if (arg == "object") {
QHBoxLayout *objLayout = new QHBoxLayout();
QToolButton *objButton = new QToolButton(this);

QMenuView *objMenu = new QMenuView(this);
TreeSortFilterProxyModel *treeProxy = new TreeSortFilterProxyModel(this);
treeProxy->SetFilterType(TreeNode::TypeCase::kObject);
treeProxy->setSourceModel(MainWindow::treeModel.get());
objMenu->setModel(treeProxy);
objButton->setMenu(objMenu);
objButton->setPopupMode(QToolButton::MenuButtonPopup);

QLineEdit *lineEdit = new QLineEdit(this);
lineEdit->setReadOnly(true);
QModelIndex firstObjIdx = treeProxy
->match(treeProxy->index(0, 0), TreeModel::UserRoles::TypeCaseRole,
TypeCase::kObject, 1, Qt::MatchRecursive)
.first();
QString firstObj = firstObjIdx.data(Qt::DisplayRole).toString();
objButton->setIcon(firstObjIdx.data(Qt::DecorationRole).value<QIcon>());
lineEdit->setText(firstObj);

connect(objMenu, &QMenuView::triggered, [=](const QModelIndex &index) {
lineEdit->setText(treeProxy->data(index, Qt::DisplayRole).toString());
objButton->setIcon(treeProxy->data(index, Qt::DecorationRole).value<QIcon>());
});

objLayout->addWidget(lineEdit);
objLayout->addWidget(objButton);

layout->addItem(objLayout, row, 1);

value = lineEdit;
} else {
QComboBox *combo = new QComboBox(this);
auto argList = MainWindow::GetEventData()->value_names_for_type(name->text().toStdString());
for (auto a : argList) {
combo->addItem(QString::fromStdString(a.first));
}
value = combo;
layout->addWidget(value, row, 1);
}

widgets_.append(value);

layout->addWidget(name, row, 0);
row++;
}

QDialogButtonBox *btn = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addWidget(btn, row, 1);
connect(btn, SIGNAL(accepted()), this, SLOT(accept()));
connect(btn, SIGNAL(rejected()), this, SLOT(reject()));

setWindowTitle(tr("Event arguments"));
}

const QStringList &EventArgumentsDialog::GetArguments() const { return arguments_; }

void EventArgumentsDialog::done(int r) {
for (const QWidget *w : widgets_) {
QVariant argument = w->metaObject()->userProperty().read(w);
QString argstr = "";
if (argument.isValid())
argstr = argument.toString();
arguments_.append(argstr);
}

QDialog::done(r);
}
21 changes: 21 additions & 0 deletions Dialogs/EventArgumentsDialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef EVENTARGUMENTSDIALOG_H
#define EVENTARGUMENTSDIALOG_H

#include "event_reader/event_parser.h"

#include <QDialog>

class EventArgumentsDialog : public QDialog
{
Q_OBJECT
public:
EventArgumentsDialog(QWidget* parent, const QStringList& arguments);
const QStringList& GetArguments() const;
void done(int r) override;

private:
QStringList arguments_;
QVector<QWidget*> widgets_;
};

#endif // EVENTARGUMENTSDIALOG_H
186 changes: 185 additions & 1 deletion Editors/ObjectEditor.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,194 @@
#include "ObjectEditor.h"
#include "Components/QMenuView.h"
#include "Dialogs/EventArgumentsDialog.h"
#include "MainWindow.h"
#include "Models/EventTypesListModel.h"
#include "Models/RepeatedMessageModel.h"
#include "Models/RepeatedStringModel.h"

#include "ui_ObjectEditor.h"

ObjectEditor::ObjectEditor(MessageModel* model, QWidget* parent) : BaseEditor(model, parent), _ui(new Ui::ObjectEditor) {
#include <QSplitter>

void ObjectEditor::BindEventMenu(QToolButton *btn, bool add) {
QMenuView *eventsMenu = new QMenuView(this);
eventsMenu->setModel(_eventsTypesModel);
eventsMenu->setToolTipsVisible(true);
btn->setMenu(eventsMenu);
connect(eventsMenu, &QMenuView::triggered, [=](const QModelIndex &index) { AddChangeFromMenuEvent(index, add); });
}

ObjectEditor::ObjectEditor(MessageModel *model, QWidget *parent)
: BaseEditor(model, parent), _ui(new Ui::ObjectEditor) {
_ui->setupUi(this);

_nodeMapper->addMapping(_ui->nameEdit, TreeNode::kNameFieldNumber);

connect(_ui->saveButton, &QAbstractButton::pressed, this, &BaseEditor::OnSave);

_eventsModel = new EventsListModel(MainWindow::GetEventData(), this);

EventTypesListModel *m = new EventTypesListModel(MainWindow::GetEventData(), this);
_eventsTypesModel = new EventTypesListSortFilterProxyModel(m);
_eventsTypesModel->setSourceModel(m);
_eventsTypesModel->sort(0);

BindEventMenu(_ui->addEventButton, true);
BindEventMenu(_ui->changeEventButton, false);

RebindSubModels();
}

ObjectEditor::~ObjectEditor() { delete _ui; }

void ObjectEditor::AddChangeEventHelper(const Object::EgmEvent &event, bool add) {
if (add) {
AddEvent(event);
} else {
int existingIdx = MapRowFrom(IndexOf(event));
int selectedIdx = MapRowTo(_ui->eventsList->selectionModel()->currentIndex().row());
if (existingIdx == -1 && selectedIdx != -1) {
ChangeEvent(selectedIdx, event, false);
} else
qDebug() << "Change event failed";
}
}

void ObjectEditor::AddChangeFromMenuEvent(const QModelIndex &index, bool add) {
QStringList args = _eventsTypesModel->data(index, EventTypesListModel::UserRoles::EventArgumentsRole).toStringList();
EventArgumentsDialog *dialog = nullptr;
if (args.size() > 0) {
dialog = new EventArgumentsDialog(this, args);
dialog->open();

connect(dialog, &QDialog::accepted, [=]() {
if (dialog->result() == QDialog::Accepted) {
Object::EgmEvent event;
event.set_id(
_eventsTypesModel->data(index, EventTypesListModel::UserRoles::EventBareIDRole).toString().toStdString());
for (const QString &arg : dialog->GetArguments()) {
std::string *s = event.add_arguments();
s->assign(arg.toStdString());
}

AddChangeEventHelper(event, add);
}
});
} else {
Object::EgmEvent event;
event.set_id(
_eventsTypesModel->data(index, EventTypesListModel::UserRoles::EventBareIDRole).toString().toStdString());
AddChangeEventHelper(event, add);
}
}

void ObjectEditor::RebindSubModels() {
_objectModel = _model->GetSubModel<MessageModel *>(TreeNode::kObjectFieldNumber);
_eventsModel->setSourceModel(_objectModel->GetSubModel<RepeatedMessageModel *>(Object::kEgmEventsFieldNumber));

_sortedEvents = new QSortFilterProxyModel(_eventsModel);
_sortedEvents->setSourceModel(_eventsModel);
_sortedEvents->sort(0);
_sortedEvents->setDynamicSortFilter(true);
_ui->eventsList->setModel(_sortedEvents);

connect(_ui->eventsList, &QAbstractItemView::clicked,
[=](const QModelIndex &index) { SetCurrentEditor(MapRowTo(index.row())); });

connect(_ui->deleteEventButton, &QToolButton::pressed, [=]() {
QModelIndex selection = _ui->eventsList->selectionModel()->currentIndex();
if (selection.row() != -1) RemoveEvent(MapRowTo(selection.row()));
});

for (int event = 0; event < _eventsModel->rowCount(); ++event) {
BindEventEditor(event);
}

SetCurrentEditor(MapRowFrom(0));
CheckDisableButtons();

BaseEditor::RebindSubModels();
}

void ObjectEditor::CheckDisableButtons() {
bool hasEvents = _sortedEvents->rowCount() > 0;
_ui->changeEventButton->setDisabled(!hasEvents);
_ui->deleteEventButton->setDisabled(!hasEvents);
_ui->codeEditor->setDisabled(!hasEvents);
if (!hasEvents) _ui->eventLineEdit->setText("");
}

void ObjectEditor::AddEvent(Object::EgmEvent event) {
RepeatedMessageModel *eventsModel = _objectModel->GetSubModel<RepeatedMessageModel *>(Object::kEgmEventsFieldNumber);
int idx = eventsModel->rowCount();

if (IndexOf(event) == -1) {
bool insert = eventsModel->insertRow(idx);
if (insert) {
BindEventEditor(idx);
ChangeEvent(idx, event);
}
} else
qDebug() << "Event already exists";
}

void ObjectEditor::ChangeEvent(int idx, Object::EgmEvent event, bool changeCode) {
RepeatedMessageModel *eventsModel = _objectModel->GetSubModel<RepeatedMessageModel *>(Object::kEgmEventsFieldNumber);

eventsModel->SetData(QString::fromStdString(event.id()), idx, Object::EgmEvent::kIdFieldNumber);

if (changeCode) eventsModel->SetData(QString::fromStdString(event.code()), idx, Object::EgmEvent::kCodeFieldNumber);

RepeatedStringModel *argsModel = eventsModel->GetSubModel<MessageModel *>(idx)->GetSubModel<RepeatedStringModel *>(
Object::EgmEvent::kArgumentsFieldNumber);

size_t argc = 0;
argsModel->removeRows(0, argsModel->rowCount()); // clear old arguments
if (event.arguments_size() > 0) {
argsModel->insertRows(argsModel->rowCount(), event.arguments_size());
for (const auto &arg : event.arguments()) {
argsModel->SetData(QString::fromStdString(arg), argc++);
}
}

SetCurrentEditor(idx);
CheckDisableButtons();
}

void ObjectEditor::RemoveEvent(int idx) {
RepeatedMessageModel *eventsModel = _objectModel->GetSubModel<RepeatedMessageModel *>(Object::kEgmEventsFieldNumber);
eventsModel->removeRow(idx);
_ui->codeEditor->RemoveCodeWidget(idx);
SetCurrentEditor(MapRowFrom(0));
CheckDisableButtons();
}

int ObjectEditor::IndexOf(Object::EgmEvent event) {
std::vector<std::string> args(event.arguments().begin(), event.arguments().end());
Event e = MainWindow::GetEventData()->get_event(event.id(), args);
for (int i = 0; i < _eventsModel->rowCount(); ++i) {
if (_eventsModel->data(_eventsModel->index(i, 0)).toString() == QString::fromStdString(e.HumanName())) return i;
}
return -1;
}

void ObjectEditor::BindEventEditor(int idx) {
RepeatedMessageModel *eventsModel = _objectModel->GetSubModel<RepeatedMessageModel *>(Object::kEgmEventsFieldNumber);
CodeWidget *codeWidget = _ui->codeEditor->AddCodeWidget();
ModelMapper *mapper(new ModelMapper(eventsModel->GetSubModel<MessageModel *>(idx), this));
mapper->addMapping(codeWidget, Object::EgmEvent::kCodeFieldNumber);
mapper->toFirst();
}

void ObjectEditor::SetCurrentEditor(int idx) {
if (idx < _sortedEvents->rowCount()) {
_ui->codeEditor->SetCurrentIndex(idx);
_ui->eventLineEdit->setText(_eventsModel->data(_eventsModel->index(idx, 0)).toString());
_ui->eventsList->selectionModel()->select(_sortedEvents->index(MapRowFrom(idx), 0),
QItemSelectionModel::QItemSelectionModel::ClearAndSelect);
}
}

int ObjectEditor::MapRowTo(int row) { return _sortedEvents->mapToSource(_sortedEvents->index(row, 0)).row(); }

int ObjectEditor::MapRowFrom(int row) { return _sortedEvents->mapFromSource(_eventsModel->index(row, 0)).row(); }
Loading

0 comments on commit 97df14f

Please sign in to comment.