Support setting custom attributes as defaults

This commit is contained in:
GriffinR 2024-01-10 15:59:27 -05:00
parent eb6a56bd60
commit e421a84542
12 changed files with 300 additions and 76 deletions

View file

@ -29,6 +29,7 @@ public:
void save(); void save();
void load(); void load();
void setSaveDisabled(bool disabled); void setSaveDisabled(bool disabled);
void logInvalidKey(const QString &key);
virtual ~KeyValueConfigBase(); virtual ~KeyValueConfigBase();
virtual void reset() = 0; virtual void reset() = 0;
protected: protected:
@ -319,6 +320,8 @@ public:
this->blockCollisionMask = 0x0C00; this->blockCollisionMask = 0x0C00;
this->blockElevationMask = 0xF000; this->blockElevationMask = 0xF000;
this->identifiers.clear(); this->identifiers.clear();
this->defaultEventCustomAttributes.clear();
this->defaultMapCustomAttributes.clear();
this->readKeys.clear(); this->readKeys.clear();
} }
static const QMap<ProjectIdentifier, QPair<QString, QString>> defaultIdentifiers; static const QMap<ProjectIdentifier, QPair<QString, QString>> defaultIdentifiers;
@ -417,6 +420,12 @@ public:
int getCollisionSheetHeight(); int getCollisionSheetHeight();
void setWarpBehaviors(const QSet<uint32_t> &behaviors); void setWarpBehaviors(const QSet<uint32_t> &behaviors);
QSet<uint32_t> getWarpBehaviors(); QSet<uint32_t> getWarpBehaviors();
void insertDefaultEventCustomAttribute(Event::Type eventType, const QString &key, QJsonValue value);
void insertDefaultMapCustomAttribute(const QString &key, QJsonValue value);
void removeDefaultEventCustomAttribute(Event::Type eventType, const QString &key);
void removeDefaultMapCustomAttribute(const QString &key);
QMap<QString, QJsonValue> getDefaultEventCustomAttributes(Event::Type eventType);
QMap<QString, QJsonValue> getDefaultMapCustomAttributes();
protected: protected:
virtual QString getConfigFilepath() override; virtual QString getConfigFilepath() override;
@ -425,6 +434,9 @@ protected:
virtual void onNewConfigFileCreated() override; virtual void onNewConfigFileCreated() override;
virtual void setUnreadKeys() override; virtual void setUnreadKeys() override;
private: private:
void parseCustomAttributes(const QString &key, const QString &value);
QString customAttributesToString(const QMap<QString, QJsonValue> attributes);
BaseGameVersion baseGameVersion; BaseGameVersion baseGameVersion;
QString projectDir; QString projectDir;
QMap<ProjectIdentifier, QString> identifiers; QMap<ProjectIdentifier, QString> identifiers;
@ -466,6 +478,8 @@ private:
int collisionSheetWidth; int collisionSheetWidth;
int collisionSheetHeight; int collisionSheetHeight;
QSet<uint32_t> warpBehaviors; QSet<uint32_t> warpBehaviors;
QMap<Event::Type, QMap<QString, QJsonValue>> defaultEventCustomAttributes;
QMap<QString, QJsonValue> defaultMapCustomAttributes;
}; };
extern ProjectConfig projectConfig; extern ProjectConfig projectConfig;

View file

@ -158,10 +158,11 @@ public:
virtual void setDefaultValues(Project *project); virtual void setDefaultValues(Project *project);
virtual QSet<QString> getExpectedFields() = 0; virtual QSet<QString> getExpectedFields() = 0;
void readCustomValues(QJsonObject values); void readCustomAttributes(QJsonObject values);
void addCustomValuesTo(OrderedJson::object *obj); void addCustomAttributesTo(OrderedJson::object *obj);
const QMap<QString, QJsonValue> getCustomValues() { return this->customValues; } const QMap<QString, QJsonValue> getCustomAttributes() { return this->customAttributes; }
void setCustomValues(const QMap<QString, QJsonValue> newCustomValues) { this->customValues = newCustomValues; } void setCustomAttributes(const QMap<QString, QJsonValue> newCustomAttributes) { this->customAttributes = newCustomAttributes; }
void setDefaultCustomAttributes();
virtual void loadPixmap(Project *project); virtual void loadPixmap(Project *project);
@ -203,7 +204,7 @@ protected:
int spriteHeight = 16; int spriteHeight = 16;
bool usingSprite = false; bool usingSprite = false;
QMap<QString, QJsonValue> customValues; QMap<QString, QJsonValue> customAttributes;
QPixmap pixmap; QPixmap pixmap;
DraggablePixmapItem *pixmapItem = nullptr; DraggablePixmapItem *pixmapItem = nullptr;

View file

@ -379,7 +379,7 @@ private:
Event::Group getEventGroupFromTabWidget(QWidget *tab); Event::Group getEventGroupFromTabWidget(QWidget *tab);
void closeSupplementaryWindows(); void closeSupplementaryWindows();
void setWindowDisabled(bool); void setWindowDisabled(bool);
void resetMapCustomAttributesTable();
void initTilesetEditor(); void initTilesetEditor();
bool initRegionMapEditor(bool silent = false); bool initRegionMapEditor(bool silent = false);
void initShortcutsEditor(); void initShortcutsEditor();

View file

@ -23,7 +23,8 @@ private:
CustomAttributesTable *const table; CustomAttributesTable *const table;
void addNewAttribute(); void addNewAttribute();
bool verifyName(); bool verifyInput();
QVariant getValue() const;
void setNameEditHighlight(bool highlight); void setNameEditHighlight(bool highlight);
}; };

View file

@ -16,27 +16,31 @@ public:
QMap<QString, QJsonValue> getAttributes() const; QMap<QString, QJsonValue> getAttributes() const;
void setAttributes(const QMap<QString, QJsonValue> attributes); void setAttributes(const QMap<QString, QJsonValue> attributes);
void addNewAttribute(const QString &key, QJsonValue value); void addNewAttribute(const QString &key, QJsonValue value, bool setAsDefault);
void setDefaultAttribute(const QString &key, QJsonValue value);
void unsetDefaultAttribute(const QString &key);
QSet<QString> keys() const; QSet<QString> keys() const;
QSet<QString> defaultKeys() const;
QSet<QString> restrictedKeys() const; QSet<QString> restrictedKeys() const;
void setDefaultKeys(const QSet<QString> keys);
void setRestrictedKeys(const QSet<QString> keys); void setRestrictedKeys(const QSet<QString> keys);
signals: signals:
void edited(); void edited();
void defaultSet(QString key, QJsonValue value);
void defaultRemoved(QString key);
private: private:
QTableWidget *table; QTableWidget *table;
QSet<QString> m_keys; QSet<QString> m_keys; // All keys currently in the table
QSet<QString> m_restrictedKeys; QSet<QString> m_defaultKeys; // All keys that are in this table by default (whether or not they're present)
QSet<QString> m_restrictedKeys; // All keys not allowed in the table
int addAttribute(const QString &key, QJsonValue value); int addAttribute(const QString &key, QJsonValue value);
void removeAttribute(const QString &key); void removeAttribute(const QString &key);
bool deleteSelectedAttributes(); bool deleteSelectedAttributes();
void setDefaultAttribute(const QString &key, QJsonValue value);
void unsetDefaultAttribute(const QString &key);
void resizeVertically(); void resizeVertically();
}; };

View file

@ -292,6 +292,10 @@ void KeyValueConfigBase::setSaveDisabled(bool disabled) {
this->saveDisabled = disabled; this->saveDisabled = disabled;
} }
void KeyValueConfigBase::logInvalidKey(const QString &key) {
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
}
const QMap<MapSortOrder, QString> mapSortOrderMap = { const QMap<MapSortOrder, QString> mapSortOrderMap = {
{MapSortOrder::Group, "group"}, {MapSortOrder::Group, "group"},
{MapSortOrder::Layout, "layout"}, {MapSortOrder::Layout, "layout"},
@ -400,7 +404,7 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
} else if (key == "warp_behavior_warning_disabled") { } else if (key == "warp_behavior_warning_disabled") {
this->warpBehaviorWarningDisabled = getConfigBool(key, value); this->warpBehaviorWarningDisabled = getConfigBool(key, value);
} else { } else {
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); logInvalidKey(key);
} }
} }
@ -872,14 +876,14 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
if (k != static_cast<ProjectFilePath>(-1)) { if (k != static_cast<ProjectFilePath>(-1)) {
this->setFilePath(k, value); this->setFilePath(k, value);
} else { } else {
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); logInvalidKey(key);
} }
} else if (key.startsWith("ident/")) { } else if (key.startsWith("ident/")) {
auto identifierId = reverseDefaultIdentifier(key.mid(6)); auto identifierId = reverseDefaultIdentifier(key.mid(6));
if (identifierId != static_cast<ProjectIdentifier>(-1)) { if (identifierId != static_cast<ProjectIdentifier>(-1)) {
this->setIdentifier(identifierId, value); this->setIdentifier(identifierId, value);
} else { } else {
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); logInvalidKey(key);
} }
} else if (key == "prefabs_filepath") { } else if (key == "prefabs_filepath") {
this->prefabFilepath = value; this->prefabFilepath = value;
@ -913,8 +917,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
QStringList behaviorList = value.split(",", Qt::SkipEmptyParts); QStringList behaviorList = value.split(",", Qt::SkipEmptyParts);
for (auto s : behaviorList) for (auto s : behaviorList)
this->warpBehaviors.insert(getConfigUint32(key, s)); this->warpBehaviors.insert(getConfigUint32(key, s));
} else if (key.startsWith("custom_attributes/")) {
this->parseCustomAttributes(key, value);
} else { } else {
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); logInvalidKey(key);
} }
readKeys.append(key); readKeys.append(key);
} }
@ -1004,6 +1010,12 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
for (auto value : this->warpBehaviors) for (auto value : this->warpBehaviors)
warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper());
map.insert("warp_behaviors", warpBehaviorStrs.join(",")); map.insert("warp_behaviors", warpBehaviorStrs.join(","));
if (!this->defaultMapCustomAttributes.isEmpty())
map.insert("custom_attributes/header", this->customAttributesToString(this->defaultMapCustomAttributes));
for (auto i = this->defaultEventCustomAttributes.cbegin(), end = this->defaultEventCustomAttributes.cend(); i != end; i++) {
if (!i.value().isEmpty())
map.insert("custom_attributes/" + Event::eventTypeToString(i.key()), this->customAttributesToString(i.value()));
}
return map; return map;
} }
@ -1466,6 +1478,123 @@ QSet<uint32_t> ProjectConfig::getWarpBehaviors() {
return this->warpBehaviors; return this->warpBehaviors;
} }
void ProjectConfig::insertDefaultEventCustomAttribute(Event::Type eventType, const QString &key, QJsonValue value) {
this->defaultEventCustomAttributes[eventType].insert(key, value);
this->save();
}
void ProjectConfig::insertDefaultMapCustomAttribute(const QString &key, QJsonValue value) {
this->defaultMapCustomAttributes.insert(key, value);
this->save();
}
void ProjectConfig::removeDefaultEventCustomAttribute(Event::Type eventType, const QString &key) {
this->defaultEventCustomAttributes[eventType].remove(key);
this->save();
}
void ProjectConfig::removeDefaultMapCustomAttribute(const QString &key) {
this->defaultMapCustomAttributes.remove(key);
this->save();
}
QMap<QString, QJsonValue> ProjectConfig::getDefaultEventCustomAttributes(Event::Type eventType) {
return this->defaultEventCustomAttributes.value(eventType);
}
QMap<QString, QJsonValue> ProjectConfig::getDefaultMapCustomAttributes() {
return this->defaultMapCustomAttributes;
}
void ProjectConfig::parseCustomAttributes(const QString &key, const QString &value) {
static const QRegularExpression regex("custom_attributes/(?<identifier>\\w+)");
auto match = regex.match(key);
if (!match.hasMatch()){
logInvalidKey(key);
return;
}
// Value should be a comma-separated list of sequences of the form 'key:type:value'.
// Some day if this config file is formatted as JSON data we wouldn't need to store 'type' (among other simplifications).
QMap<QString, QJsonValue> map;
const QStringList attributeSequences = value.split(",", Qt::SkipEmptyParts);
if (attributeSequences.isEmpty())
return;
for (auto sequence : attributeSequences) {
// Parse each 'type:key:value' sequence
const QStringList attributeData = sequence.split(":");
if (attributeData.length() != 3) {
logWarn(QString("Invalid value '%1' for custom attribute in '%2'").arg(sequence).arg(key));
continue;
}
const QString attrKey = attributeData.at(0);
const QString attrType = attributeData.at(1);
const QString attrValue = attributeData.at(2);
QJsonValue value;
if (attrType == "string") {
value = QJsonValue(attrValue);
} else if (attrType == "number") {
bool ok;
int num = attrValue.toInt(&ok, 0);
if (!ok)
logWarn(QString("Invalid value '%1' for custom attribute '%2' in '%3'").arg(attrValue).arg(attrKey).arg(key));
value = QJsonValue(num);
} else if (attrType == "bool") {
bool ok;
int num = attrValue.toInt(&ok, 0);
if (!ok || (num != 0 && num != 1))
logWarn(QString("Invalid value '%1' for custom attribute '%2' in '%3'").arg(attrValue).arg(attrKey).arg(key));
value = QJsonValue(num == 1);
} else {
logWarn(QString("Invalid value type '%1' for custom attribute '%2' in '%3'").arg(attrType).arg(attrKey).arg(key));
continue;
}
// Successfully parsed a 'type:key:value' sequence
map.insert(attrKey, value);
}
// Determine who the custom attribute map belongs to (either the map header or some Event type)
const QString identifier = match.captured("identifier");
if (identifier == "header") {
this->defaultMapCustomAttributes = map;
return;
}
Event::Type eventType = Event::eventTypeFromString(identifier);
if (eventType != Event::Type::None) {
this->defaultEventCustomAttributes[eventType] = map;
return;
}
logWarn(QString("Invalid custom attributes identifier '%1' in '%2'").arg(identifier).arg(key));
}
// Assemble comma-separated list of sequences of the form 'key:type:value'.
QString ProjectConfig::customAttributesToString(const QMap<QString, QJsonValue> attributes) {
QStringList output;
for (auto i = attributes.cbegin(), end = attributes.cend(); i != end; i++) {
QString value;
QString typeStr;
QJsonValue::Type type = i.value().type();
if (type == QJsonValue::Type::String) {
typeStr = "string";
value = i.value().toString();
} else if (type == QJsonValue::Type::Double) {
typeStr = "number";
value = QString::number(i.value().toInt());
} else if (type == QJsonValue::Type::Bool) {
typeStr = "bool";
value = QString::number(i.value().toBool());
} else {
continue;
}
output.append(QString("%1:%2:%3").arg(i.key()).arg(typeStr).arg(value));
}
return output.join(",");
}
UserConfig userConfig; UserConfig userConfig;
@ -1482,7 +1611,7 @@ void UserConfig::parseConfigKeyValue(QString key, QString value) {
} else if (key == "custom_scripts") { } else if (key == "custom_scripts") {
this->parseCustomScripts(value); this->parseCustomScripts(value);
} else { } else {
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); logInvalidKey(key);
} }
readKeys.append(key); readKeys.append(key);
} }

View file

@ -36,22 +36,27 @@ void Event::setDefaultValues(Project *) {
this->setX(0); this->setX(0);
this->setY(0); this->setY(0);
this->setElevation(projectConfig.getDefaultElevation()); this->setElevation(projectConfig.getDefaultElevation());
this->setDefaultCustomAttributes();
} }
void Event::readCustomValues(QJsonObject values) { void Event::setDefaultCustomAttributes() {
this->customValues.clear(); this->setCustomAttributes(projectConfig.getDefaultEventCustomAttributes(this->getEventType()));
}
void Event::readCustomAttributes(QJsonObject values) {
this->customAttributes.clear();
QSet<QString> expectedFields = this->getExpectedFields(); QSet<QString> expectedFields = this->getExpectedFields();
for (QString key : values.keys()) { for (QString key : values.keys()) {
if (!expectedFields.contains(key)) { if (!expectedFields.contains(key)) {
this->customValues[key] = values[key]; this->customAttributes[key] = values[key];
} }
} }
} }
void Event::addCustomValuesTo(OrderedJson::object *obj) { void Event::addCustomAttributesTo(OrderedJson::object *obj) {
for (QString key : this->customValues.keys()) { for (QString key : this->customAttributes.keys()) {
if (!obj->contains(key)) { if (!obj->contains(key)) {
(*obj)[key] = OrderedJson::fromQJsonValue(this->customValues[key]); (*obj)[key] = OrderedJson::fromQJsonValue(this->customAttributes[key]);
} }
} }
} }
@ -179,7 +184,7 @@ Event *ObjectEvent::duplicate() {
copy->setSightRadiusBerryTreeID(this->getSightRadiusBerryTreeID()); copy->setSightRadiusBerryTreeID(this->getSightRadiusBerryTreeID());
copy->setScript(this->getScript()); copy->setScript(this->getScript());
copy->setFlag(this->getFlag()); copy->setFlag(this->getFlag());
copy->setCustomValues(this->getCustomValues()); copy->setCustomAttributes(this->getCustomAttributes());
return copy; return copy;
} }
@ -209,7 +214,7 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) {
objectJson["trainer_sight_or_berry_tree_id"] = this->getSightRadiusBerryTreeID(); objectJson["trainer_sight_or_berry_tree_id"] = this->getSightRadiusBerryTreeID();
objectJson["script"] = this->getScript(); objectJson["script"] = this->getScript();
objectJson["flag"] = this->getFlag(); objectJson["flag"] = this->getFlag();
this->addCustomValuesTo(&objectJson); this->addCustomAttributesTo(&objectJson);
return objectJson; return objectJson;
} }
@ -227,7 +232,7 @@ bool ObjectEvent::loadFromJson(QJsonObject json, Project *) {
this->setScript(ParseUtil::jsonToQString(json["script"])); this->setScript(ParseUtil::jsonToQString(json["script"]));
this->setFlag(ParseUtil::jsonToQString(json["flag"])); this->setFlag(ParseUtil::jsonToQString(json["flag"]));
this->readCustomValues(json); this->readCustomAttributes(json);
return true; return true;
} }
@ -242,6 +247,7 @@ void ObjectEvent::setDefaultValues(Project *project) {
this->setRadiusY(0); this->setRadiusY(0);
this->setSightRadiusBerryTreeID("0"); this->setSightRadiusBerryTreeID("0");
this->setFrameFromMovement(project->facingDirections.value(this->getMovement())); this->setFrameFromMovement(project->facingDirections.value(this->getMovement()));
this->setDefaultCustomAttributes();
} }
const QSet<QString> expectedObjectFields = { const QSet<QString> expectedObjectFields = {
@ -353,7 +359,7 @@ Event *CloneObjectEvent::duplicate() {
copy->setGfx(this->getGfx()); copy->setGfx(this->getGfx());
copy->setTargetID(this->getTargetID()); copy->setTargetID(this->getTargetID());
copy->setTargetMap(this->getTargetMap()); copy->setTargetMap(this->getTargetMap());
copy->setCustomValues(this->getCustomValues()); copy->setCustomAttributes(this->getCustomAttributes());
return copy; return copy;
} }
@ -375,7 +381,7 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) {
cloneJson["y"] = this->getY(); cloneJson["y"] = this->getY();
cloneJson["target_local_id"] = this->getTargetID(); cloneJson["target_local_id"] = this->getTargetID();
cloneJson["target_map"] = project->mapNamesToMapConstants.value(this->getTargetMap()); cloneJson["target_map"] = project->mapNamesToMapConstants.value(this->getTargetMap());
this->addCustomValuesTo(&cloneJson); this->addCustomAttributesTo(&cloneJson);
return cloneJson; return cloneJson;
} }
@ -398,7 +404,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) {
this->setTargetMap(DYNAMIC_MAP_NAME); this->setTargetMap(DYNAMIC_MAP_NAME);
} }
this->readCustomValues(json); this->readCustomAttributes(json);
return true; return true;
} }
@ -407,6 +413,7 @@ void CloneObjectEvent::setDefaultValues(Project *project) {
this->setGfx(project->gfxDefines.keys().first()); this->setGfx(project->gfxDefines.keys().first());
this->setTargetID(1); this->setTargetID(1);
if (this->getMap()) this->setTargetMap(this->getMap()->name); if (this->getMap()) this->setTargetMap(this->getMap()->name);
this->setDefaultCustomAttributes();
} }
const QSet<QString> expectedCloneObjectFields = { const QSet<QString> expectedCloneObjectFields = {
@ -465,7 +472,7 @@ Event *WarpEvent::duplicate() {
copy->setDestinationMap(this->getDestinationMap()); copy->setDestinationMap(this->getDestinationMap());
copy->setDestinationWarpID(this->getDestinationWarpID()); copy->setDestinationWarpID(this->getDestinationWarpID());
copy->setCustomValues(this->getCustomValues()); copy->setCustomAttributes(this->getCustomAttributes());
return copy; return copy;
} }
@ -487,7 +494,7 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) {
warpJson["dest_map"] = project->mapNamesToMapConstants.value(this->getDestinationMap()); warpJson["dest_map"] = project->mapNamesToMapConstants.value(this->getDestinationMap());
warpJson["dest_warp_id"] = this->getDestinationWarpID(); warpJson["dest_warp_id"] = this->getDestinationWarpID();
this->addCustomValuesTo(&warpJson); this->addCustomAttributesTo(&warpJson);
return warpJson; return warpJson;
} }
@ -510,7 +517,7 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) {
this->setDestinationMap(DYNAMIC_MAP_NAME); this->setDestinationMap(DYNAMIC_MAP_NAME);
} }
this->readCustomValues(json); this->readCustomAttributes(json);
return true; return true;
} }
@ -519,6 +526,7 @@ void WarpEvent::setDefaultValues(Project *) {
if (this->getMap()) this->setDestinationMap(this->getMap()->name); if (this->getMap()) this->setDestinationMap(this->getMap()->name);
this->setDestinationWarpID("0"); this->setDestinationWarpID("0");
this->setElevation(0); this->setElevation(0);
this->setDefaultCustomAttributes();
} }
const QSet<QString> expectedWarpFields = { const QSet<QString> expectedWarpFields = {
@ -552,7 +560,7 @@ Event *TriggerEvent::duplicate() {
copy->setScriptVarValue(this->getScriptVarValue()); copy->setScriptVarValue(this->getScriptVarValue());
copy->setScriptLabel(this->getScriptLabel()); copy->setScriptLabel(this->getScriptLabel());
copy->setCustomValues(this->getCustomValues()); copy->setCustomAttributes(this->getCustomAttributes());
return copy; return copy;
} }
@ -576,7 +584,7 @@ OrderedJson::object TriggerEvent::buildEventJson(Project *) {
triggerJson["var_value"] = this->getScriptVarValue(); triggerJson["var_value"] = this->getScriptVarValue();
triggerJson["script"] = this->getScriptLabel(); triggerJson["script"] = this->getScriptLabel();
this->addCustomValuesTo(&triggerJson); this->addCustomAttributesTo(&triggerJson);
return triggerJson; return triggerJson;
} }
@ -589,7 +597,7 @@ bool TriggerEvent::loadFromJson(QJsonObject json, Project *) {
this->setScriptVarValue(ParseUtil::jsonToQString(json["var_value"])); this->setScriptVarValue(ParseUtil::jsonToQString(json["var_value"]));
this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); this->setScriptLabel(ParseUtil::jsonToQString(json["script"]));
this->readCustomValues(json); this->readCustomAttributes(json);
return true; return true;
} }
@ -599,6 +607,7 @@ void TriggerEvent::setDefaultValues(Project *project) {
this->setScriptVar(project->varNames.first()); this->setScriptVar(project->varNames.first());
this->setScriptVarValue("0"); this->setScriptVarValue("0");
this->setElevation(0); this->setElevation(0);
this->setDefaultCustomAttributes();
} }
const QSet<QString> expectedTriggerFields = { const QSet<QString> expectedTriggerFields = {
@ -626,7 +635,7 @@ Event *WeatherTriggerEvent::duplicate() {
copy->setElevation(this->getElevation()); copy->setElevation(this->getElevation());
copy->setWeather(this->getWeather()); copy->setWeather(this->getWeather());
copy->setCustomValues(this->getCustomValues()); copy->setCustomAttributes(this->getCustomAttributes());
return copy; return copy;
} }
@ -648,7 +657,7 @@ OrderedJson::object WeatherTriggerEvent::buildEventJson(Project *) {
weatherJson["elevation"] = this->getElevation(); weatherJson["elevation"] = this->getElevation();
weatherJson["weather"] = this->getWeather(); weatherJson["weather"] = this->getWeather();
this->addCustomValuesTo(&weatherJson); this->addCustomAttributesTo(&weatherJson);
return weatherJson; return weatherJson;
} }
@ -659,7 +668,7 @@ bool WeatherTriggerEvent::loadFromJson(QJsonObject json, Project *) {
this->setElevation(ParseUtil::jsonToInt(json["elevation"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"]));
this->setWeather(ParseUtil::jsonToQString(json["weather"])); this->setWeather(ParseUtil::jsonToQString(json["weather"]));
this->readCustomValues(json); this->readCustomAttributes(json);
return true; return true;
} }
@ -667,6 +676,7 @@ bool WeatherTriggerEvent::loadFromJson(QJsonObject json, Project *) {
void WeatherTriggerEvent::setDefaultValues(Project *project) { void WeatherTriggerEvent::setDefaultValues(Project *project) {
this->setWeather(project->coordEventWeatherNames.first()); this->setWeather(project->coordEventWeatherNames.first());
this->setElevation(0); this->setElevation(0);
this->setDefaultCustomAttributes();
} }
const QSet<QString> expectedWeatherTriggerFields = { const QSet<QString> expectedWeatherTriggerFields = {
@ -693,7 +703,7 @@ Event *SignEvent::duplicate() {
copy->setFacingDirection(this->getFacingDirection()); copy->setFacingDirection(this->getFacingDirection());
copy->setScriptLabel(this->getScriptLabel()); copy->setScriptLabel(this->getScriptLabel());
copy->setCustomValues(this->getCustomValues()); copy->setCustomAttributes(this->getCustomAttributes());
return copy; return copy;
} }
@ -716,7 +726,7 @@ OrderedJson::object SignEvent::buildEventJson(Project *) {
signJson["player_facing_dir"] = this->getFacingDirection(); signJson["player_facing_dir"] = this->getFacingDirection();
signJson["script"] = this->getScriptLabel(); signJson["script"] = this->getScriptLabel();
this->addCustomValuesTo(&signJson); this->addCustomAttributesTo(&signJson);
return signJson; return signJson;
} }
@ -728,7 +738,7 @@ bool SignEvent::loadFromJson(QJsonObject json, Project *) {
this->setFacingDirection(ParseUtil::jsonToQString(json["player_facing_dir"])); this->setFacingDirection(ParseUtil::jsonToQString(json["player_facing_dir"]));
this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); this->setScriptLabel(ParseUtil::jsonToQString(json["script"]));
this->readCustomValues(json); this->readCustomAttributes(json);
return true; return true;
} }
@ -737,6 +747,7 @@ void SignEvent::setDefaultValues(Project *project) {
this->setFacingDirection(project->bgEventFacingDirections.first()); this->setFacingDirection(project->bgEventFacingDirections.first());
this->setScriptLabel("NULL"); this->setScriptLabel("NULL");
this->setElevation(0); this->setElevation(0);
this->setDefaultCustomAttributes();
} }
const QSet<QString> expectedSignFields = { const QSet<QString> expectedSignFields = {
@ -766,7 +777,7 @@ Event *HiddenItemEvent::duplicate() {
copy->setQuantity(this->getQuantity()); copy->setQuantity(this->getQuantity());
copy->setQuantity(this->getQuantity()); copy->setQuantity(this->getQuantity());
copy->setCustomValues(this->getCustomValues()); copy->setCustomAttributes(this->getCustomAttributes());
return copy; return copy;
} }
@ -795,7 +806,7 @@ OrderedJson::object HiddenItemEvent::buildEventJson(Project *) {
hiddenItemJson["underfoot"] = this->getUnderfoot(); hiddenItemJson["underfoot"] = this->getUnderfoot();
} }
this->addCustomValuesTo(&hiddenItemJson); this->addCustomAttributesTo(&hiddenItemJson);
return hiddenItemJson; return hiddenItemJson;
} }
@ -813,7 +824,7 @@ bool HiddenItemEvent::loadFromJson(QJsonObject json, Project *) {
this->setUnderfoot(ParseUtil::jsonToBool(json["underfoot"])); this->setUnderfoot(ParseUtil::jsonToBool(json["underfoot"]));
} }
this->readCustomValues(json); this->readCustomAttributes(json);
return true; return true;
} }
@ -827,6 +838,7 @@ void HiddenItemEvent::setDefaultValues(Project *project) {
if (projectConfig.getHiddenItemRequiresItemfinderEnabled()) { if (projectConfig.getHiddenItemRequiresItemfinderEnabled()) {
this->setUnderfoot(false); this->setUnderfoot(false);
} }
this->setDefaultCustomAttributes();
} }
const QSet<QString> expectedHiddenItemFields = { const QSet<QString> expectedHiddenItemFields = {
@ -859,7 +871,7 @@ Event *SecretBaseEvent::duplicate() {
copy->setElevation(this->getElevation()); copy->setElevation(this->getElevation());
copy->setBaseID(this->getBaseID()); copy->setBaseID(this->getBaseID());
copy->setCustomValues(this->getCustomValues()); copy->setCustomAttributes(this->getCustomAttributes());
return copy; return copy;
} }
@ -881,7 +893,7 @@ OrderedJson::object SecretBaseEvent::buildEventJson(Project *) {
secretBaseJson["elevation"] = this->getElevation(); secretBaseJson["elevation"] = this->getElevation();
secretBaseJson["secret_base_id"] = this->getBaseID(); secretBaseJson["secret_base_id"] = this->getBaseID();
this->addCustomValuesTo(&secretBaseJson); this->addCustomAttributesTo(&secretBaseJson);
return secretBaseJson; return secretBaseJson;
} }
@ -892,7 +904,7 @@ bool SecretBaseEvent::loadFromJson(QJsonObject json, Project *) {
this->setElevation(ParseUtil::jsonToInt(json["elevation"])); this->setElevation(ParseUtil::jsonToInt(json["elevation"]));
this->setBaseID(ParseUtil::jsonToQString(json["secret_base_id"])); this->setBaseID(ParseUtil::jsonToQString(json["secret_base_id"]));
this->readCustomValues(json); this->readCustomAttributes(json);
return true; return true;
} }
@ -900,6 +912,7 @@ bool SecretBaseEvent::loadFromJson(QJsonObject json, Project *) {
void SecretBaseEvent::setDefaultValues(Project *project) { void SecretBaseEvent::setDefaultValues(Project *project) {
this->setBaseID(project->secretBaseIds.first()); this->setBaseID(project->secretBaseIds.first());
this->setElevation(0); this->setElevation(0);
this->setDefaultCustomAttributes();
} }
const QSet<QString> expectedSecretBaseFields = { const QSet<QString> expectedSecretBaseFields = {
@ -931,6 +944,7 @@ OrderedJson::object HealLocationEvent::buildEventJson(Project *) {
void HealLocationEvent::setDefaultValues(Project *) { void HealLocationEvent::setDefaultValues(Project *) {
this->setElevation(projectConfig.getDefaultElevation()); this->setElevation(projectConfig.getDefaultElevation());
this->setDefaultCustomAttributes();
if (!this->getMap()) if (!this->getMap())
return; return;
bool respawnEnabled = projectConfig.getHealLocationRespawnDataEnabled(); bool respawnEnabled = projectConfig.getHealLocationRespawnDataEnabled();

View file

@ -302,10 +302,23 @@ void MainWindow::initEditor() {
this->editor->selectedEventIndexChanged(value, Event::Group::Heal); this->editor->selectedEventIndexChanged(value, Event::Group::Heal);
}); });
// Connect the Custom Attributes table on the Header tab
connect(ui->mapCustomAttributesTable, &CustomAttributesTable::edited, [this]() { connect(ui->mapCustomAttributesTable, &CustomAttributesTable::edited, [this]() {
this->markMapEdited(); this->markMapEdited();
this->editor->updateCustomMapHeaderValues(); this->editor->updateCustomMapHeaderValues();
}); });
connect(ui->mapCustomAttributesTable, &CustomAttributesTable::defaultSet, [](QString key, QJsonValue value) {
projectConfig.insertDefaultMapCustomAttribute(key, value);
});
connect(ui->mapCustomAttributesTable, &CustomAttributesTable::defaultRemoved, [](QString key) {
projectConfig.removeDefaultMapCustomAttribute(key);
});
}
void MainWindow::resetMapCustomAttributesTable() {
QStringList keys = projectConfig.getDefaultMapCustomAttributes().keys();
ui->mapCustomAttributesTable->setDefaultKeys(QSet<QString>(keys.begin(), keys.end()));
ui->mapCustomAttributesTable->setRestrictedKeys(Project::getTopLevelMapFields());
} }
void MainWindow::initMiscHeapObjects() { void MainWindow::initMiscHeapObjects() {
@ -512,8 +525,8 @@ bool MainWindow::openProject(const QString &dir, bool initial) {
projectConfig.load(); projectConfig.load();
this->closeSupplementaryWindows(); this->closeSupplementaryWindows();
this->resetMapCustomAttributesTable();
this->newMapDefaultsSet = false; this->newMapDefaultsSet = false;
ui->mapCustomAttributesTable->setRestrictedKeys(Project::getTopLevelMapFields());
if (isProjectOpen()) if (isProjectOpen())
Scripting::cb_ProjectClosed(editor->project->root); Scripting::cb_ProjectClosed(editor->project->root);

View file

@ -31,16 +31,23 @@ CustomAttributesDialog::CustomAttributesDialog(CustomAttributesTable *parent) :
this->setNameEditHighlight(false); this->setNameEditHighlight(false);
}); });
// Exclude delimiters used in the config
static const QRegularExpression expression("[^:,=/]*");
ui->lineEdit_Name->setValidator(new QRegularExpressionValidator(expression));
// Button box // Button box
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton *button) { connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton *button) {
auto buttonRole = ui->buttonBox->buttonRole(button); auto buttonRole = ui->buttonBox->buttonRole(button);
if (buttonRole == QDialogButtonBox::AcceptRole && this->verifyName()) { if (buttonRole == QDialogButtonBox::AcceptRole && this->verifyInput()) {
this->addNewAttribute(); this->addNewAttribute();
this->done(QDialog::Accepted); this->done(QDialog::Accepted);
} else if (buttonRole == QDialogButtonBox::RejectRole) { } else if (buttonRole == QDialogButtonBox::RejectRole) {
this->done(QDialog::Rejected); this->done(QDialog::Rejected);
} }
}); });
ui->spinBox_Value->setMinimum(INT_MIN);
ui->spinBox_Value->setMaximum(INT_MAX);
} }
CustomAttributesDialog::~CustomAttributesDialog() { CustomAttributesDialog::~CustomAttributesDialog() {
@ -51,7 +58,7 @@ void CustomAttributesDialog::setNameEditHighlight(bool highlight) {
ui->lineEdit_Name->setStyleSheet(highlight ? "QLineEdit { background-color: rgba(255, 0, 0, 25%) }" : ""); ui->lineEdit_Name->setStyleSheet(highlight ? "QLineEdit { background-color: rgba(255, 0, 0, 25%) }" : "");
} }
bool CustomAttributesDialog::verifyName() { bool CustomAttributesDialog::verifyInput() {
const QString name = ui->lineEdit_Name->text(); const QString name = ui->lineEdit_Name->text();
if (name.isEmpty()) { if (name.isEmpty()) {
@ -67,8 +74,16 @@ bool CustomAttributesDialog::verifyName() {
return false; return false;
} }
// Warn user if changing the default value of a "Default" custom attribute
if (this->table->defaultKeys().contains(name) && ui->checkBox_Default->isChecked()) {
const QString msg = QString("'%1' is already a default attribute. Replace its default value with '%2'?")
.arg(name)
.arg(this->getValue().toString());
if (QMessageBox::warning(this, "Warning", msg, QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel)
return false;
}
// Warn user if key name would overwrite an existing custom attribute // Warn user if key name would overwrite an existing custom attribute
if (this->table->keys().contains(name)) { else if (this->table->keys().contains(name)) {
const QString msg = QString("Overwrite value for existing attribute '%1'?").arg(name); const QString msg = QString("Overwrite value for existing attribute '%1'?").arg(name);
if (QMessageBox::warning(this, "Warning", msg, QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) if (QMessageBox::warning(this, "Warning", msg, QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel)
return false; return false;
@ -77,20 +92,19 @@ bool CustomAttributesDialog::verifyName() {
return true; return true;
} }
void CustomAttributesDialog::addNewAttribute() { QVariant CustomAttributesDialog::getValue() const {
QJsonValue value; QVariant value;
const QString type = ui->comboBox_Type->currentText(); const QString type = ui->comboBox_Type->currentText();
if (type == "String") { if (type == "String") {
value = QJsonValue(ui->lineEdit_Value->text()); value = QVariant(ui->lineEdit_Value->text());
} else if (type == "Number") { } else if (type == "Number") {
value = QJsonValue(ui->spinBox_Value->value()); value = QVariant(ui->spinBox_Value->value());
} else if (type == "Boolean") { } else if (type == "Boolean") {
value = QJsonValue(ui->checkBox_Value->isChecked()); value = QVariant(ui->checkBox_Value->isChecked());
} }
return value;
const QString key = ui->lineEdit_Name->text(); }
this->table->addNewAttribute(key, value);
void CustomAttributesDialog::addNewAttribute() {
if (ui->checkBox_Default->isChecked()) this->table->addNewAttribute(ui->lineEdit_Name->text(), QJsonValue::fromVariant(this->getValue()), ui->checkBox_Default->isChecked());
this->table->setDefaultAttribute(key, value);
} }

View file

@ -15,14 +15,11 @@ enum Column {
Count Count
}; };
// TODO: Tooltip-- "Custom fields will be added to the map.json file for the current map."?
// TODO: Fix squishing when first element is added
// TODO: Fix Header table size on first open
// TODO: Take control of Delete key?
// TODO: Edit history?
CustomAttributesTable::CustomAttributesTable(QWidget *parent) : CustomAttributesTable::CustomAttributesTable(QWidget *parent) :
QFrame(parent) QFrame(parent)
{ {
this->setAttribute(Qt::WA_DeleteOnClose);
QVBoxLayout *layout = new QVBoxLayout(this); QVBoxLayout *layout = new QVBoxLayout(this);
QLabel *label = new QLabel("Custom Attributes"); QLabel *label = new QLabel("Custom Attributes");
layout->addWidget(label); layout->addWidget(label);
@ -72,9 +69,19 @@ CustomAttributesTable::CustomAttributesTable(QWidget *parent) :
// Adding the "Selectable" flag to the Key cell changes its appearance to match the Value cell, which // Adding the "Selectable" flag to the Key cell changes its appearance to match the Value cell, which
// makes it confusing that you can't edit the Key cell. To keep the uneditable appearance and allow // makes it confusing that you can't edit the Key cell. To keep the uneditable appearance and allow
// deleting rows by selecting Key cells, we select the full row when a Key cell is selected. // deleting rows by selecting Key cells, we select the full row when a Key cell is selected.
// Double clicking the Key cell will begin editing the value cell, as it would for double-clicking the value cell.
connect(this->table, &QTableWidget::cellClicked, [this](int row, int column) { connect(this->table, &QTableWidget::cellClicked, [this](int row, int column) {
if (column == Column::Key) this->table->selectRow(row); if (column == Column::Key) {
this->table->selectRow(row);
}
}); });
connect(this->table, &QTableWidget::cellDoubleClicked, [this](int row, int column) {
if (column == Column::Key) {
auto index = this->table->model()->index(row, Column::Value);
this->table->edit(index);
}
});
// TODO: Right-click for context menu to set a default?
} }
CustomAttributesTable::~CustomAttributesTable() CustomAttributesTable::~CustomAttributesTable()
@ -142,7 +149,7 @@ int CustomAttributesTable::addAttribute(const QString &key, QJsonValue value) {
return -1; return -1;
// Overwrite existing key (if present) // Overwrite existing key (if present)
if (this->m_keys.remove(key)) if (this->m_keys.contains(key))
this->removeAttribute(key); this->removeAttribute(key);
// Add new row // Add new row
@ -170,6 +177,7 @@ int CustomAttributesTable::addAttribute(const QString &key, QJsonValue value) {
spinBox->setMinimum(INT_MIN); spinBox->setMinimum(INT_MIN);
spinBox->setMaximum(INT_MAX); spinBox->setMaximum(INT_MAX);
spinBox->setValue(ParseUtil::jsonToInt(value)); spinBox->setValue(ParseUtil::jsonToInt(value));
// This connection will be handled by QTableWidget::cellChanged for other cell types
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), [this]() { emit this->edited(); }); connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), [this]() { emit this->edited(); });
this->table->setCellWidget(rowIndex, Column::Value, spinBox); this->table->setCellWidget(rowIndex, Column::Value, spinBox);
break; break;
@ -194,9 +202,10 @@ int CustomAttributesTable::addAttribute(const QString &key, QJsonValue value) {
} }
// For the user adding an attribute by interacting with the table // For the user adding an attribute by interacting with the table
void CustomAttributesTable::addNewAttribute(const QString &key, QJsonValue value) { void CustomAttributesTable::addNewAttribute(const QString &key, QJsonValue value, bool setAsDefault) {
int row = this->addAttribute(key, value); int row = this->addAttribute(key, value);
if (row < 0) return; if (row < 0) return;
if (setAsDefault) this->setDefaultAttribute(key, value);
this->table->selectRow(row); this->table->selectRow(row);
this->resizeVertically(); this->resizeVertically();
} }
@ -210,21 +219,25 @@ void CustomAttributesTable::setAttributes(const QMap<QString, QJsonValue> attrib
} }
void CustomAttributesTable::setDefaultAttribute(const QString &key, QJsonValue value) { void CustomAttributesTable::setDefaultAttribute(const QString &key, QJsonValue value) {
// TODO m_defaultKeys.insert(key);
emit this->defaultSet(key, value);
} }
void CustomAttributesTable::unsetDefaultAttribute(const QString &key) { void CustomAttributesTable::unsetDefaultAttribute(const QString &key) {
// TODO if (m_defaultKeys.remove(key))
emit this->defaultRemoved(key);
} }
void CustomAttributesTable::removeAttribute(const QString &key) { void CustomAttributesTable::removeAttribute(const QString &key) {
for (int row = 0; row < this->table->rowCount(); row++) { for (int row = 0; row < this->table->rowCount(); row++) {
auto keyItem = this->table->item(row, Column::Key); auto keyItem = this->table->item(row, Column::Key);
if (keyItem && keyItem->text() == key) { if (keyItem && keyItem->text() == key) {
this->m_keys.remove(key);
this->table->removeRow(row); this->table->removeRow(row);
break; break;
} }
} }
// No need to adjust size because this is (at the moment) only used for replacement
} }
bool CustomAttributesTable::deleteSelectedAttributes() { bool CustomAttributesTable::deleteSelectedAttributes() {
@ -243,7 +256,10 @@ bool CustomAttributesTable::deleteSelectedAttributes() {
return false; return false;
for (QPersistentModelIndex index : persistentIndexes) { for (QPersistentModelIndex index : persistentIndexes) {
this->table->removeRow(index.row()); auto row = index.row();
auto item = this->table->item(row, Column::Key);
if (item) this->m_keys.remove(item->text());
this->table->removeRow(row);
} }
if (this->table->rowCount() > 0) { if (this->table->rowCount() > 0) {
@ -257,10 +273,18 @@ QSet<QString> CustomAttributesTable::keys() const {
return this->m_keys; return this->m_keys;
} }
QSet<QString> CustomAttributesTable::defaultKeys() const {
return this->m_defaultKeys;
}
QSet<QString> CustomAttributesTable::restrictedKeys() const { QSet<QString> CustomAttributesTable::restrictedKeys() const {
return this->m_restrictedKeys; return this->m_restrictedKeys;
} }
void CustomAttributesTable::setDefaultKeys(const QSet<QString> keys) {
this->m_defaultKeys = keys;
}
void CustomAttributesTable::setRestrictedKeys(const QSet<QString> keys) { void CustomAttributesTable::setRestrictedKeys(const QSet<QString> keys) {
this->m_restrictedKeys = keys; this->m_restrictedKeys = keys;
} }

View file

@ -103,8 +103,9 @@ void EventFrame::setup() {
void EventFrame::initCustomAttributesTable() { void EventFrame::initCustomAttributesTable() {
this->custom_attributes = new CustomAttributesTable(this); this->custom_attributes = new CustomAttributesTable(this);
QStringList keys = projectConfig.getDefaultEventCustomAttributes(this->event->getEventType()).keys();
this->custom_attributes->setDefaultKeys(QSet<QString>(keys.begin(), keys.end()));
this->custom_attributes->setRestrictedKeys(this->event->getExpectedFields()); this->custom_attributes->setRestrictedKeys(this->event->getExpectedFields());
this->custom_attributes->setAttributes(this->event->getCustomValues());
this->layout_contents->addWidget(this->custom_attributes); this->layout_contents->addWidget(this->custom_attributes);
} }
@ -138,9 +139,15 @@ void EventFrame::connectSignals(MainWindow *) {
this->custom_attributes->disconnect(); this->custom_attributes->disconnect();
connect(this->custom_attributes, &CustomAttributesTable::edited, [this]() { connect(this->custom_attributes, &CustomAttributesTable::edited, [this]() {
this->event->setCustomValues(this->custom_attributes->getAttributes()); this->event->setCustomAttributes(this->custom_attributes->getAttributes());
this->event->modify(); this->event->modify();
}); });
connect(this->custom_attributes, &CustomAttributesTable::defaultSet, [this](QString key, QJsonValue value) {
projectConfig.insertDefaultEventCustomAttribute(this->event->getEventType(), key, value);
});
connect(this->custom_attributes, &CustomAttributesTable::defaultRemoved, [this](QString key) {
projectConfig.removeDefaultEventCustomAttribute(this->event->getEventType(), key);
});
} }
void EventFrame::initialize() { void EventFrame::initialize() {
@ -152,6 +159,8 @@ void EventFrame::initialize() {
this->spinner_y->setValue(this->event->getY()); this->spinner_y->setValue(this->event->getY());
this->spinner_z->setValue(this->event->getZ()); this->spinner_z->setValue(this->event->getZ());
this->custom_attributes->setAttributes(this->event->getCustomAttributes());
this->label_icon->setPixmap(this->event->getPixmap()); this->label_icon->setPixmap(this->event->getPixmap());
} }

View file

@ -303,6 +303,7 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() {
if (projectConfig.getFloorNumberEnabled()) { if (projectConfig.getFloorNumberEnabled()) {
newMap->floorNumber = this->ui->spinBox_NewMap_Floor_Number->value(); newMap->floorNumber = this->ui->spinBox_NewMap_Floor_Number->value();
} }
newMap->customHeaders = projectConfig.getDefaultMapCustomAttributes();
newMap->layout = layout; newMap->layout = layout;
newMap->layoutId = layout->id; newMap->layoutId = layout->id;