mirror of
https://github.com/huderlem/porymap.git
synced 2026-04-12 12:45:52 -05:00
520 lines
18 KiB
C++
520 lines
18 KiB
C++
#include "maplistmodels.h"
|
|
#include "validator.h"
|
|
#include "project.h"
|
|
#include "filterchildrenproxymodel.h"
|
|
|
|
#include <QMouseEvent>
|
|
#include <QLineEdit>
|
|
|
|
|
|
|
|
void MapTree::removeSelected() {
|
|
while (!this->selectedIndexes().isEmpty()) {
|
|
QModelIndex i = this->selectedIndexes().takeLast();
|
|
this->model()->removeRow(i.row(), i.parent());
|
|
}
|
|
}
|
|
|
|
void MapTree::keyPressEvent(QKeyEvent *event) {
|
|
if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) {
|
|
// Delete selected items in the tree
|
|
auto selectionModel = this->selectionModel();
|
|
if (!selectionModel->hasSelection())
|
|
return;
|
|
|
|
auto model = static_cast<FilterChildrenProxyModel*>(this->model());
|
|
auto sourceModel = static_cast<MapListModel*>(model->sourceModel());
|
|
|
|
QModelIndexList selectedIndexes = selectionModel->selectedRows();
|
|
QList<QPersistentModelIndex> persistentIndexes;
|
|
for (const auto &index : selectedIndexes) {
|
|
persistentIndexes.append(model->mapToSource(index));
|
|
}
|
|
for (const auto &index : persistentIndexes) {
|
|
sourceModel->removeItemAt(index);
|
|
}
|
|
} else {
|
|
QWidget::keyPressEvent(event);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
MapListModel::MapListModel(Project *project, QObject *parent) : QStandardItemModel(parent) {
|
|
this->project = project;
|
|
this->root = invisibleRootItem();
|
|
|
|
this->mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico"));
|
|
this->mapIcon = QIcon(QStringLiteral(":/icons/map.ico"));
|
|
this->mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico"));
|
|
this->mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico"));
|
|
|
|
this->mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off);
|
|
this->mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On);
|
|
|
|
this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off);
|
|
this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On);
|
|
}
|
|
|
|
QStandardItem *MapListModel::itemAt(const QModelIndex &index) const {
|
|
if (index.isValid()) {
|
|
QStandardItem *item = static_cast<QStandardItem*>(index.internalPointer());
|
|
if (item)
|
|
return item;
|
|
}
|
|
return this->root;
|
|
}
|
|
|
|
QStandardItem *MapListModel::itemAt(const QString &itemName) const {
|
|
QModelIndex index = this->indexOf(itemName);
|
|
if (!index.isValid())
|
|
return nullptr;
|
|
return this->itemAt(index)->child(index.row(), index.column());
|
|
}
|
|
|
|
QModelIndex MapListModel::indexOf(const QString &itemName) const {
|
|
if (this->mapItems.contains(itemName))
|
|
return this->mapItems.value(itemName)->index();
|
|
|
|
if (this->mapFolderItems.contains(itemName))
|
|
return this->mapFolderItems.value(itemName)->index();
|
|
|
|
return QModelIndex();
|
|
}
|
|
|
|
void MapListModel::removeItemAt(const QModelIndex &index) {
|
|
QStandardItem *item = this->itemAt(index)->child(index.row(), index.column());
|
|
if (!item)
|
|
return;
|
|
|
|
const QString type = item->data(MapListUserRoles::TypeRole).toString();
|
|
if (type == "map_name") {
|
|
// TODO: No support for deleting maps
|
|
} else {
|
|
// TODO: Because there's no support for deleting maps we can only delete empty folders
|
|
if (!item->hasChildren()) {
|
|
removeItem(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem *map) {
|
|
if (!map) map = new QStandardItem;
|
|
map->setText(mapName);
|
|
map->setData(mapName, MapListUserRoles::NameRole);
|
|
map->setData("map_name", MapListUserRoles::TypeRole);
|
|
map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren);
|
|
map->setToolTip(this->project->mapNamesToMapConstants.value(mapName));
|
|
this->mapItems.insert(mapName, map);
|
|
return map;
|
|
}
|
|
|
|
QStandardItem *MapListModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) {
|
|
if (!folder) folder = new QStandardItem;
|
|
folder->setText(folderName);
|
|
folder->setData(folderName, MapListUserRoles::NameRole);
|
|
folder->setData(this->folderTypeName, MapListUserRoles::TypeRole);
|
|
folder->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);
|
|
folder->setEditable(this->editable); // Will override flags if necessary
|
|
this->mapFolderItems.insert(folderName, folder);
|
|
return folder;
|
|
}
|
|
|
|
QStandardItem *MapListModel::insertMapItem(const QString &mapName, const QString &folderName) {
|
|
if (mapName.isEmpty() || mapName == this->project->getDynamicMapName()) // Disallow adding MAP_DYNAMIC to the map list.
|
|
return nullptr;
|
|
|
|
QStandardItem *map = createMapItem(mapName);
|
|
|
|
QStandardItem *folder = this->mapFolderItems[folderName];
|
|
if (!folder) {
|
|
// Folder doesn't exist yet, add it.
|
|
folder = insertMapFolderItem(folderName);
|
|
}
|
|
// If folder is still nullptr here it's because we failed to create it.
|
|
if (folder) {
|
|
folder->appendRow(map);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
QStandardItem *MapListModel::insertMapFolderItem(const QString &folderName) {
|
|
if (folderName.isEmpty())
|
|
return nullptr;
|
|
|
|
QStandardItem *item = createMapFolderItem(folderName);
|
|
this->root->appendRow(item);
|
|
return item;
|
|
}
|
|
|
|
QVariant MapListModel::data(const QModelIndex &index, int role) const {
|
|
if (!index.isValid())
|
|
return QVariant();
|
|
|
|
int row = index.row();
|
|
int col = index.column();
|
|
|
|
const QStandardItem *item = this->itemAt(index)->child(row, col);
|
|
const QString type = item->data(MapListUserRoles::TypeRole).toString();
|
|
const QString name = item->data(MapListUserRoles::NameRole).toString();
|
|
|
|
if (role == Qt::DecorationRole) {
|
|
if (type == "map_name") {
|
|
// Decorating map in the map list
|
|
if (name == this->activeItemName)
|
|
return this->mapOpenedIcon;
|
|
|
|
const Map* map = this->project->mapCache.value(name);
|
|
if (!map)
|
|
return this->mapGrayIcon;
|
|
return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon;
|
|
} else if (type == this->folderTypeName) {
|
|
// Decorating map folder in the map list
|
|
return item->hasChildren() ? this->mapFolderIcon : this->emptyMapFolderIcon;
|
|
}
|
|
}
|
|
return QStandardItemModel::data(index, role);
|
|
}
|
|
|
|
|
|
|
|
QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const {
|
|
QLineEdit *editor = new QLineEdit(parent);
|
|
editor->setPlaceholderText(Project::getMapGroupPrefix());
|
|
editor->setValidator(new IdentifierValidator(parent));
|
|
editor->setFrame(false);
|
|
return editor;
|
|
}
|
|
|
|
void GroupNameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
|
|
QString groupName = index.data(MapListUserRoles::NameRole).toString();
|
|
QLineEdit *le = static_cast<QLineEdit *>(editor);
|
|
le->setText(groupName);
|
|
}
|
|
|
|
void GroupNameDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
|
|
QLineEdit *le = static_cast<QLineEdit *>(editor);
|
|
QString groupName = le->text();
|
|
model->setData(index, groupName, MapListUserRoles::NameRole);
|
|
}
|
|
|
|
void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const {
|
|
editor->setGeometry(option.rect);
|
|
}
|
|
|
|
|
|
|
|
MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(project, parent) {
|
|
this->folderTypeName = "map_group";
|
|
this->editable = true;
|
|
|
|
for (const auto &groupName : this->project->groupNames) {
|
|
insertMapFolderItem(groupName);
|
|
for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) {
|
|
insertMapItem(mapName, groupName);
|
|
}
|
|
}
|
|
}
|
|
|
|
Qt::DropActions MapGroupModel::supportedDropActions() const {
|
|
return Qt::MoveAction;
|
|
}
|
|
|
|
QStringList MapGroupModel::mimeTypes() const {
|
|
QStringList types;
|
|
types << "application/porymap.mapgroupmodel.map"
|
|
<< "application/porymap.mapgroupmodel.group"
|
|
<< "application/porymap.mapgroupmodel.source.row"
|
|
<< "application/porymap.mapgroupmodel.source.column";
|
|
return types;
|
|
}
|
|
|
|
QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const {
|
|
QMimeData *mimeData = QStandardItemModel::mimeData(indexes);
|
|
QByteArray encodedData;
|
|
|
|
QDataStream stream(&encodedData, QIODevice::WriteOnly);
|
|
|
|
bool droppingGroups = false;
|
|
|
|
// if dropping a selection containing both group(s) and map(s), only copy groups
|
|
for (const QModelIndex &index : indexes) {
|
|
if (index.isValid() && data(index, MapListUserRoles::TypeRole).toString() == "map_group") {
|
|
QString groupName = data(index, MapListUserRoles::NameRole).toString();
|
|
stream << groupName;
|
|
stream << index.row();
|
|
droppingGroups = true;
|
|
}
|
|
}
|
|
|
|
if (droppingGroups) {
|
|
mimeData->setData("application/porymap.mapgroupmodel.group", encodedData);
|
|
return mimeData;
|
|
}
|
|
|
|
for (const QModelIndex &index : indexes) {
|
|
if (index.isValid()) {
|
|
QString mapName = data(index, MapListUserRoles::NameRole).toString();
|
|
stream << mapName;
|
|
}
|
|
}
|
|
|
|
mimeData->setData("application/porymap.mapgroupmodel.map", encodedData);
|
|
return mimeData;
|
|
}
|
|
|
|
bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int, const QModelIndex &parentIndex) {
|
|
if (action == Qt::IgnoreAction)
|
|
return true;
|
|
|
|
if (!parentIndex.isValid() && !data->hasFormat("application/porymap.mapgroupmodel.group"))
|
|
return false;
|
|
|
|
int firstRow = 0;
|
|
|
|
if (row != -1) {
|
|
firstRow = row;
|
|
}
|
|
else if (parentIndex.isValid()) {
|
|
firstRow = rowCount(parentIndex);
|
|
}
|
|
|
|
if (data->hasFormat("application/porymap.mapgroupmodel.group")) {
|
|
if (parentIndex.row() != -1 || parentIndex.column() != -1) {
|
|
return false;
|
|
}
|
|
|
|
if (row < 0) {
|
|
row = this->rowCount(parentIndex);
|
|
}
|
|
|
|
QByteArray encodedData = data->data("application/porymap.mapgroupmodel.group");
|
|
QDataStream stream(&encodedData, QIODevice::ReadOnly);
|
|
QStringList droppedGroups;
|
|
QList<int> droppedGroupIndexes;
|
|
int rowCount = 0;
|
|
|
|
while (!stream.atEnd()) {
|
|
QString groupName;
|
|
stream >> groupName;
|
|
int groupIndex;
|
|
stream >> groupIndex;
|
|
droppedGroups << groupName;
|
|
droppedGroupIndexes << groupIndex;
|
|
rowCount++;
|
|
}
|
|
|
|
for (int r = 0; r < rowCount; r++) {
|
|
QString groupName = droppedGroups[r];
|
|
// copy children to new node
|
|
int sourceRow = droppedGroupIndexes[r];
|
|
QModelIndex originIndex = this->index(sourceRow, 0);
|
|
QModelIndexList children;
|
|
QStringList mapsToMove;
|
|
for (int i = 0; i < this->rowCount(originIndex); ++i ) {
|
|
children << this->index(i, 0, originIndex);
|
|
mapsToMove << this->index(i, 0 , originIndex).data(MapListUserRoles::NameRole).toString();
|
|
}
|
|
|
|
this->insertRow(row + r, parentIndex);
|
|
QModelIndex groupIndex = index(row + r, 0, parentIndex);
|
|
QStandardItem *groupItem = this->itemFromIndex(groupIndex);
|
|
createMapFolderItem(groupName, groupItem);
|
|
|
|
for (QString mapName : mapsToMove) {
|
|
QStandardItem *mapItem = createMapItem(mapName);
|
|
groupItem->appendRow(mapItem);
|
|
}
|
|
}
|
|
}
|
|
else if (data->hasFormat("application/porymap.mapgroupmodel.map")) {
|
|
QByteArray encodedData = data->data("application/porymap.mapgroupmodel.map");
|
|
QDataStream stream(&encodedData, QIODevice::ReadOnly);
|
|
QStringList droppedMaps;
|
|
int rowCount = 0;
|
|
|
|
while (!stream.atEnd()) {
|
|
QString mapName;
|
|
stream >> mapName;
|
|
droppedMaps << mapName;
|
|
rowCount++;
|
|
}
|
|
|
|
QStandardItem *groupItem = this->itemFromIndex(parentIndex);
|
|
if (groupItem->hasChildren()) {
|
|
this->insertRows(firstRow, rowCount, parentIndex);
|
|
for (QString mapName : droppedMaps) {
|
|
QModelIndex mapIndex = index(firstRow, 0, parentIndex);
|
|
QStandardItem *mapItem = this->itemFromIndex(mapIndex);
|
|
createMapItem(mapName, mapItem);
|
|
firstRow++;
|
|
}
|
|
}
|
|
// for whatever reason insertRows doesn't work as I expected with childless items
|
|
// so just append all the new maps instead
|
|
else {
|
|
for (QString mapName : droppedMaps) {
|
|
QStandardItem *mapItem = createMapItem(mapName);
|
|
groupItem->appendRow(mapItem);
|
|
firstRow++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
emit dragMoveCompleted();
|
|
updateProject();
|
|
|
|
return true;
|
|
}
|
|
|
|
void MapGroupModel::updateProject() {
|
|
if (!this->project) return;
|
|
|
|
// Temporary objects in case of failure, so we won't modify the project unless it succeeds.
|
|
QStringList mapNames;
|
|
QStringList groupNames;
|
|
QMap<QString, QStringList> groupNameToMapNames;
|
|
|
|
for (int g = 0; g < this->root->rowCount(); g++) {
|
|
const QStandardItem *groupItem = this->item(g);
|
|
QString groupName = groupItem->data(MapListUserRoles::NameRole).toString();
|
|
groupNames.append(groupName);
|
|
for (int m = 0; m < groupItem->rowCount(); m++) {
|
|
const QStandardItem *mapItem = groupItem->child(m);
|
|
if (!mapItem) {
|
|
logError("An error occured while trying to apply updates to map group structure.");
|
|
return;
|
|
}
|
|
QString mapName = mapItem->data(MapListUserRoles::NameRole).toString();
|
|
groupNameToMapNames[groupName].append(mapName);
|
|
mapNames.append(mapName);
|
|
}
|
|
}
|
|
|
|
this->project->mapNames = mapNames;
|
|
this->project->groupNames = groupNames;
|
|
this->project->groupNameToMapNames = groupNameToMapNames;
|
|
this->project->hasUnsavedDataChanges = true;
|
|
}
|
|
|
|
void MapGroupModel::removeItem(QStandardItem *item) {
|
|
this->removeRow(item->row());
|
|
this->updateProject();
|
|
}
|
|
|
|
QVariant MapGroupModel::data(const QModelIndex &index, int role) const {
|
|
if (!index.isValid())
|
|
return QVariant();
|
|
|
|
int row = index.row();
|
|
int col = index.column();
|
|
|
|
const QStandardItem *item = this->itemAt(index)->child(row, col);
|
|
const QString type = item->data(MapListUserRoles::TypeRole).toString();
|
|
const QString name = item->data(MapListUserRoles::NameRole).toString();
|
|
|
|
if (role == Qt::DisplayRole) {
|
|
if (type == "map_name") {
|
|
return QString("[%1.%2] ").arg(this->itemAt(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + name;
|
|
}
|
|
else if (type == this->folderTypeName) {
|
|
return name;
|
|
}
|
|
}
|
|
return MapListModel::data(index, role);
|
|
}
|
|
|
|
bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) {
|
|
if (role == MapListUserRoles::NameRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") {
|
|
if (!this->project->isIdentifierUnique(value.toString())) {
|
|
return false;
|
|
}
|
|
}
|
|
if (QStandardItemModel::setData(index, value, role)) {
|
|
this->updateProject();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
MapLocationModel::MapLocationModel(Project *project, QObject *parent) : MapListModel(project, parent) {
|
|
this->folderTypeName = "map_section";
|
|
|
|
for (const auto &idName : this->project->mapSectionIdNames) {
|
|
insertMapFolderItem(idName);
|
|
}
|
|
for (const auto &mapName : this->project->mapNames) {
|
|
insertMapItem(mapName, this->project->mapNameToMapSectionName.value(mapName));
|
|
}
|
|
}
|
|
|
|
void MapLocationModel::removeItem(QStandardItem *item) {
|
|
this->project->removeMapsec(item->data(MapListUserRoles::NameRole).toString());
|
|
this->removeRow(item->row());
|
|
}
|
|
|
|
QStandardItem *MapLocationModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) {
|
|
folder = MapListModel::createMapFolderItem(folderName, folder);
|
|
folder->setToolTip(this->project->getMapsecDisplayName(folderName));
|
|
return folder;
|
|
}
|
|
|
|
|
|
|
|
LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(project, parent) {
|
|
this->folderTypeName = "map_layout";
|
|
|
|
for (const auto &layoutId : this->project->layoutIds) {
|
|
insertMapFolderItem(layoutId);
|
|
}
|
|
for (const auto &mapName : this->project->mapNames) {
|
|
insertMapItem(mapName, this->project->mapNameToLayoutId.value(mapName));
|
|
}
|
|
}
|
|
|
|
void LayoutTreeModel::removeItem(QStandardItem *) {
|
|
// TODO: Deleting layouts not supported
|
|
}
|
|
|
|
QStandardItem *LayoutTreeModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) {
|
|
folder = MapListModel::createMapFolderItem(folderName, folder);
|
|
|
|
// Despite using layout IDs internally, the Layouts map list shows layouts using their file path name.
|
|
// We could handle this with Qt::DisplayRole in LayoutTreeModel::data, but then it would be sorted using the ID instead of the name.
|
|
const Layout* layout = this->project->mapLayouts.value(folderName);
|
|
if (layout) folder->setText(layout->name);
|
|
|
|
// The layout ID will instead be shown as a tool tip.
|
|
folder->setToolTip(folderName);
|
|
|
|
return folder;
|
|
}
|
|
|
|
QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const {
|
|
if (!index.isValid())
|
|
return QVariant();
|
|
|
|
int row = index.row();
|
|
int col = index.column();
|
|
|
|
const QStandardItem *item = this->itemAt(index)->child(row, col);
|
|
const QString type = item->data(MapListUserRoles::TypeRole).toString();
|
|
const QString name = item->data(MapListUserRoles::NameRole).toString();
|
|
|
|
if (type == this->folderTypeName) {
|
|
if (role == Qt::DecorationRole) {
|
|
// Map layouts are used as folders, but we display them with the same icons as maps.
|
|
if (name == this->activeItemName)
|
|
return this->mapOpenedIcon;
|
|
|
|
const Layout* layout = this->project->mapLayouts.value(name);
|
|
if (!layout || !layout->loaded)
|
|
return this->mapGrayIcon;
|
|
return layout->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon;
|
|
}
|
|
}
|
|
return MapListModel::data(index, role);
|
|
}
|