Combine attribute shift/mask data
This commit is contained in:
parent
fa2b4d3edb
commit
1641ac00b0
5 changed files with 123 additions and 107 deletions
|
@ -47,27 +47,37 @@ public:
|
||||||
uint32_t unusedAttributes;
|
uint32_t unusedAttributes;
|
||||||
QString label;
|
QString label;
|
||||||
|
|
||||||
|
enum Attr {
|
||||||
|
Behavior,
|
||||||
|
TerrainType,
|
||||||
|
EncounterType,
|
||||||
|
LayerType,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AttrLayout {
|
||||||
|
uint32_t mask;
|
||||||
|
int shift;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const QHash<Metatile::Attr, Metatile::AttrLayout> defaultLayoutFRLG;
|
||||||
|
static const QHash<Metatile::Attr, Metatile::AttrLayout> defaultLayoutRSE;
|
||||||
|
|
||||||
uint32_t getAttributes();
|
uint32_t getAttributes();
|
||||||
void setAttributes(uint32_t data);
|
void setAttributes(uint32_t data);
|
||||||
void setAttributes(uint32_t data, BaseGameVersion version);
|
void convertAttributes(uint32_t data, BaseGameVersion version);
|
||||||
|
|
||||||
static int getIndexInTileset(int);
|
static int getIndexInTileset(int);
|
||||||
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
|
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
|
||||||
static int getAttributesSize(BaseGameVersion version);
|
static int getDefaultAttributesSize(BaseGameVersion version);
|
||||||
static void calculateAttributeLayout();
|
static void setCustomLayout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static uint32_t behaviorMask;
|
static QHash<Metatile::Attr, Metatile::AttrLayout> customLayout;
|
||||||
static uint32_t terrainTypeMask;
|
|
||||||
static uint32_t encounterTypeMask;
|
|
||||||
static uint32_t layerTypeMask;
|
|
||||||
static uint32_t unusedAttrMask;
|
static uint32_t unusedAttrMask;
|
||||||
static int behaviorShift;
|
|
||||||
static int terrainTypeShift;
|
|
||||||
static int encounterTypeShift;
|
|
||||||
static int layerTypeShift;
|
|
||||||
|
|
||||||
static int getShiftValue(uint32_t mask);
|
void setAttributes(uint32_t, const QHash<Metatile::Attr, Metatile::AttrLayout>*);
|
||||||
|
static void setCustomAttributeLayout(Metatile::AttrLayout *, uint32_t, uint32_t, QString);
|
||||||
|
static bool doMasksOverlap(QList<uint32_t>);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool operator==(const Metatile &a, const Metatile &b) {
|
inline bool operator==(const Metatile &a, const Metatile &b) {
|
||||||
|
|
|
@ -563,7 +563,7 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
||||||
int size = getConfigInteger(key, value, 1, 4, 2);
|
int size = getConfigInteger(key, value, 1, 4, 2);
|
||||||
if (size & (size - 1)) {
|
if (size & (size - 1)) {
|
||||||
logWarn(QString("Invalid config value for %1: must be 1, 2, or 4").arg(key));
|
logWarn(QString("Invalid config value for %1: must be 1, 2, or 4").arg(key));
|
||||||
size = 2;
|
return; // Don't set a default now, it will be set later based on the base game version
|
||||||
}
|
}
|
||||||
this->metatileAttributesSize = size;
|
this->metatileAttributesSize = size;
|
||||||
} else if (key == "metatile_behavior_mask") {
|
} else if (key == "metatile_behavior_mask") {
|
||||||
|
@ -624,11 +624,12 @@ void ProjectConfig::setUnreadKeys() {
|
||||||
if (!readKeys.contains("create_map_text_file")) this->createMapTextFile = (this->baseGameVersion != BaseGameVersion::pokeemerald);
|
if (!readKeys.contains("create_map_text_file")) this->createMapTextFile = (this->baseGameVersion != BaseGameVersion::pokeemerald);
|
||||||
if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE;
|
if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE;
|
||||||
if (!readKeys.contains("default_secondary_tileset")) this->defaultSecondaryTileset = isPokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg";
|
if (!readKeys.contains("default_secondary_tileset")) this->defaultSecondaryTileset = isPokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg";
|
||||||
if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = isPokefirered ? 4 : 2;
|
if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = Metatile::getDefaultAttributesSize(this->baseGameVersion);
|
||||||
if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = isPokefirered ? 0x000001FF : 0x00FF;
|
const QHash<Metatile::Attr, Metatile::AttrLayout> layout = isPokefirered ? Metatile::defaultLayoutFRLG : Metatile::defaultLayoutRSE;
|
||||||
if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = isPokefirered ? 0x00003E00 : 0;
|
if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = layout[Metatile::Attr::Behavior].mask;
|
||||||
if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = isPokefirered ? 0x07000000 : 0;
|
if (!readKeys.contains("metatile_terrain_type_mask")) this->metatileTerrainTypeMask = layout[Metatile::Attr::TerrainType].mask;
|
||||||
if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = isPokefirered ? 0x60000000 : 0xF000;
|
if (!readKeys.contains("metatile_encounter_type_mask")) this->metatileEncounterTypeMask = layout[Metatile::Attr::EncounterType].mask;
|
||||||
|
if (!readKeys.contains("metatile_layer_type_mask")) this-> metatileLayerTypeMask = layout[Metatile::Attr::LayerType].mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
||||||
|
|
|
@ -2,16 +2,22 @@
|
||||||
#include "tileset.h"
|
#include "tileset.h"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
|
||||||
uint32_t Metatile::behaviorMask = 0;
|
QHash<Metatile::Attr, Metatile::AttrLayout> Metatile::customLayout = {};
|
||||||
uint32_t Metatile::terrainTypeMask = 0;
|
|
||||||
uint32_t Metatile::encounterTypeMask = 0;
|
|
||||||
uint32_t Metatile::layerTypeMask = 0;
|
|
||||||
uint32_t Metatile::unusedAttrMask = 0;
|
uint32_t Metatile::unusedAttrMask = 0;
|
||||||
|
|
||||||
int Metatile::behaviorShift = 0;
|
const QHash<Metatile::Attr, Metatile::AttrLayout> Metatile::defaultLayoutFRLG = {
|
||||||
int Metatile::terrainTypeShift = 0;
|
{Metatile::Attr::Behavior, { .mask = 0x000001FF, .shift = 0} },
|
||||||
int Metatile::encounterTypeShift = 0;
|
{Metatile::Attr::TerrainType, { .mask = 0x00003E00, .shift = 9} },
|
||||||
int Metatile::layerTypeShift = 0;
|
{Metatile::Attr::EncounterType, { .mask = 0x07000000, .shift = 24} },
|
||||||
|
{Metatile::Attr::LayerType, { .mask = 0x60000000, .shift = 29} },
|
||||||
|
};
|
||||||
|
|
||||||
|
const QHash<Metatile::Attr, Metatile::AttrLayout> Metatile::defaultLayoutRSE = {
|
||||||
|
{Metatile::Attr::Behavior, { .mask = 0x000000FF, .shift = 0} },
|
||||||
|
{Metatile::Attr::TerrainType, { .mask = 0x00000000, .shift = 0} },
|
||||||
|
{Metatile::Attr::EncounterType, { .mask = 0x00000000, .shift = 0} },
|
||||||
|
{Metatile::Attr::LayerType, { .mask = 0x0000F000, .shift = 12} },
|
||||||
|
};
|
||||||
|
|
||||||
Metatile::Metatile() :
|
Metatile::Metatile() :
|
||||||
behavior(0),
|
behavior(0),
|
||||||
|
@ -42,19 +48,37 @@ int Metatile::getIndexInTileset(int metatileId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the vanilla attribute sizes based on version.
|
||||||
|
// Used as a default in the config and for AdvanceMap import.
|
||||||
|
int Metatile::getDefaultAttributesSize(BaseGameVersion version) {
|
||||||
|
return (version == BaseGameVersion::pokefirered) ? 4 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
||||||
int x = static_cast<int>(pixelCoord.x()) / 16;
|
int x = static_cast<int>(pixelCoord.x()) / 16;
|
||||||
int y = static_cast<int>(pixelCoord.y()) / 16;
|
int y = static_cast<int>(pixelCoord.y()) / 16;
|
||||||
return QPoint(x, y);
|
return QPoint(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the position of the rightmost set bit
|
void Metatile::setCustomAttributeLayout(Metatile::AttrLayout * layout, uint32_t mask, uint32_t max, QString errName) {
|
||||||
int Metatile::getShiftValue(uint32_t mask) {
|
if (mask > max) {
|
||||||
return log2(mask & ~(mask - 1));;
|
logWarn(QString("Metatile %1 mask '%2' exceeds maximum size '%3'").arg(errName).arg(mask).arg(max));
|
||||||
|
mask &= max;
|
||||||
|
}
|
||||||
|
layout->mask = mask;
|
||||||
|
layout->shift = log2(mask & ~(mask - 1)); // Get the position of the rightmost set bit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Metatile::doMasksOverlap(QList<uint32_t> masks) {
|
||||||
|
for (int i = 0; i < masks.length(); i++)
|
||||||
|
for (int j = i + 1; j < masks.length(); j++) {
|
||||||
|
if (masks.at(i) & masks.at(j))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Metatile::calculateAttributeLayout() {
|
void Metatile::setCustomLayout() {
|
||||||
// Get the maximum size of any attribute mask
|
// Get the maximum size of any attribute mask
|
||||||
const QHash<int, uint32_t> maxMasks = {
|
const QHash<int, uint32_t> maxMasks = {
|
||||||
{1, 0xFF},
|
{1, 0xFF},
|
||||||
|
@ -63,100 +87,81 @@ void Metatile::calculateAttributeLayout() {
|
||||||
};
|
};
|
||||||
const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
|
const uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0);
|
||||||
|
|
||||||
// Behavior
|
// Set custom attribute masks from the config file
|
||||||
uint32_t mask = projectConfig.getMetatileBehaviorMask();
|
setCustomAttributeLayout(&customLayout[Attr::Behavior], projectConfig.getMetatileBehaviorMask(), maxMask, "behavior");
|
||||||
if (mask > maxMask) {
|
setCustomAttributeLayout(&customLayout[Attr::TerrainType], projectConfig.getMetatileTerrainTypeMask(), maxMask, "terrain type");
|
||||||
logWarn(QString("Metatile behavior mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask));
|
setCustomAttributeLayout(&customLayout[Attr::EncounterType], projectConfig.getMetatileEncounterTypeMask(), maxMask, "encounter type");
|
||||||
mask &= maxMask;
|
setCustomAttributeLayout(&customLayout[Attr::LayerType], projectConfig.getMetatileLayerTypeMask(), maxMask, "layer type");
|
||||||
}
|
|
||||||
Metatile::behaviorMask = mask;
|
|
||||||
Metatile::behaviorShift = getShiftValue(mask);
|
|
||||||
|
|
||||||
// Terrain Type
|
// Set mask for preserving any attribute bits not used by Porymap
|
||||||
mask = projectConfig.getMetatileTerrainTypeMask();
|
Metatile::unusedAttrMask = ~(customLayout[Attr::Behavior].mask
|
||||||
if (mask > maxMask) {
|
| customLayout[Attr::TerrainType].mask
|
||||||
logWarn(QString("Metatile terrain type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask));
|
| customLayout[Attr::EncounterType].mask
|
||||||
mask &= maxMask;
|
| customLayout[Attr::LayerType].mask);
|
||||||
}
|
|
||||||
Metatile::terrainTypeMask = mask;
|
|
||||||
Metatile::terrainTypeShift = getShiftValue(mask);
|
|
||||||
|
|
||||||
// Encounter Type
|
|
||||||
mask = projectConfig.getMetatileEncounterTypeMask();
|
|
||||||
if (mask > maxMask) {
|
|
||||||
logWarn(QString("Metatile encounter type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask));
|
|
||||||
mask &= maxMask;
|
|
||||||
}
|
|
||||||
Metatile::encounterTypeMask = mask;
|
|
||||||
Metatile::encounterTypeShift = getShiftValue(mask);
|
|
||||||
|
|
||||||
// Layer Type
|
|
||||||
mask = projectConfig.getMetatileLayerTypeMask();
|
|
||||||
if (mask > maxMask) {
|
|
||||||
logWarn(QString("Metatile layer type mask '%1' exceeds maximum size '%2'").arg(mask).arg(maxMask));
|
|
||||||
mask &= maxMask;
|
|
||||||
}
|
|
||||||
Metatile::layerTypeMask = mask;
|
|
||||||
Metatile::layerTypeShift = getShiftValue(mask);
|
|
||||||
|
|
||||||
Metatile::unusedAttrMask = ~(Metatile::behaviorMask | Metatile::terrainTypeMask | Metatile::layerTypeMask | Metatile::encounterTypeMask);
|
|
||||||
Metatile::unusedAttrMask &= maxMask;
|
Metatile::unusedAttrMask &= maxMask;
|
||||||
|
|
||||||
// Warn user if any mask overlaps
|
// Overlapping masks are legal, but probably not intended
|
||||||
if (Metatile::behaviorMask & Metatile::terrainTypeMask
|
if (doMasksOverlap({customLayout[Attr::Behavior].mask,
|
||||||
|| Metatile::behaviorMask & Metatile::encounterTypeMask
|
customLayout[Attr::TerrainType].mask,
|
||||||
|| Metatile::behaviorMask & Metatile::layerTypeMask
|
customLayout[Attr::EncounterType].mask,
|
||||||
|| Metatile::terrainTypeMask & Metatile::encounterTypeMask
|
customLayout[Attr::LayerType].mask})) {
|
||||||
|| Metatile::terrainTypeMask & Metatile::layerTypeMask
|
|
||||||
|| Metatile::encounterTypeMask & Metatile::layerTypeMask) {
|
|
||||||
logWarn("Metatile attribute masks are overlapping.");
|
logWarn("Metatile attribute masks are overlapping.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Metatile::getAttributes() {
|
uint32_t Metatile::getAttributes() {
|
||||||
uint32_t attributes = this->unusedAttributes & Metatile::unusedAttrMask;
|
uint32_t attributes = this->unusedAttributes & Metatile::unusedAttrMask;
|
||||||
attributes |= (behavior << Metatile::behaviorShift) & Metatile::behaviorMask;
|
|
||||||
attributes |= (terrainType << Metatile::terrainTypeShift) & Metatile::terrainTypeMask;
|
// Behavior
|
||||||
attributes |= (encounterType << Metatile::encounterTypeShift) & Metatile::encounterTypeMask;
|
Metatile::AttrLayout attr = Metatile::customLayout[Attr::Behavior];
|
||||||
attributes |= (layerType << Metatile::layerTypeShift) & Metatile::layerTypeMask;
|
attributes |= (this->behavior << attr.shift) & attr.mask;
|
||||||
|
|
||||||
|
// Terrain Type
|
||||||
|
attr = Metatile::customLayout[Attr::TerrainType];
|
||||||
|
attributes |= (this->terrainType << attr.shift) & attr.mask;
|
||||||
|
|
||||||
|
// Encounter Type
|
||||||
|
attr = Metatile::customLayout[Attr::EncounterType];
|
||||||
|
attributes |= (this->encounterType << attr.shift) & attr.mask;
|
||||||
|
|
||||||
|
// Layer Type
|
||||||
|
attr = Metatile::customLayout[Attr::LayerType];
|
||||||
|
attributes |= (this->layerType << attr.shift) & attr.mask;
|
||||||
|
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Metatile::setAttributes(uint32_t data) {
|
void Metatile::setAttributes(uint32_t data, const QHash<Metatile::Attr, Metatile::AttrLayout> * layout) {
|
||||||
this->behavior = (data & Metatile::behaviorMask) >> Metatile::behaviorShift;
|
// Behavior
|
||||||
this->terrainType = (data & Metatile::terrainTypeMask) >> Metatile::terrainTypeShift;
|
Metatile::AttrLayout attr = layout->value(Attr::Behavior);
|
||||||
this->encounterType = (data & Metatile::encounterTypeMask) >> Metatile::encounterTypeShift;
|
this->behavior = (data & attr.mask) >> attr.shift;
|
||||||
this->layerType = (data & Metatile::layerTypeMask) >> Metatile::layerTypeShift;
|
|
||||||
|
// Terrain Type
|
||||||
|
attr = layout->value(Attr::TerrainType);
|
||||||
|
this->terrainType = (data & attr.mask) >> attr.shift;
|
||||||
|
|
||||||
|
// Encounter Type
|
||||||
|
attr = layout->value(Attr::EncounterType);
|
||||||
|
this->encounterType = (data & attr.mask) >> attr.shift;
|
||||||
|
|
||||||
|
// Layer Type
|
||||||
|
attr = layout->value(Attr::LayerType);
|
||||||
|
this->layerType = (data & attr.mask) >> attr.shift;
|
||||||
|
|
||||||
this->unusedAttributes = data & Metatile::unusedAttrMask;
|
this->unusedAttributes = data & Metatile::unusedAttrMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the vanilla attribute sizes based on version. For AdvanceMap import
|
void Metatile::setAttributes(uint32_t data) {
|
||||||
int Metatile::getAttributesSize(BaseGameVersion version) {
|
this->setAttributes(data, &Metatile::customLayout);
|
||||||
return (version == BaseGameVersion::pokefirered) ? 4 : 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the attributes using the vanilla layout based on version. For AdvanceMap import
|
// Read attributes using a vanilla layout, then set them using the user's layout. For AdvanceMap import
|
||||||
void Metatile::setAttributes(uint32_t data, BaseGameVersion version) {
|
void Metatile::convertAttributes(uint32_t data, BaseGameVersion version) {
|
||||||
if (version == BaseGameVersion::pokefirered) {
|
if (version == BaseGameVersion::pokefirered) {
|
||||||
const uint32_t behaviorMask = 0x000001FF;
|
this->setAttributes(data, &Metatile::defaultLayoutFRLG);
|
||||||
const uint32_t terrainTypeMask = 0x00003E00;
|
|
||||||
const uint32_t encounterTypeMask = 0x07000000;
|
|
||||||
const uint32_t layerTypeMask = 0x60000000;
|
|
||||||
|
|
||||||
this->behavior = data & behaviorMask;
|
|
||||||
this->terrainType = (data & terrainTypeMask) >> 9;
|
|
||||||
this->encounterType = (data & encounterTypeMask) >> 24;
|
|
||||||
this->layerType = (data & layerTypeMask) >> 29;
|
|
||||||
this->unusedAttributes = data & ~(behaviorMask | terrainTypeMask | layerTypeMask | encounterTypeMask);
|
|
||||||
} else {
|
} else {
|
||||||
const uint16_t behaviorMask = 0x00FF;
|
this->setAttributes(data, &Metatile::defaultLayoutRSE);
|
||||||
const uint16_t layerTypeMask = 0xF000;
|
|
||||||
|
|
||||||
this->behavior = data & behaviorMask;
|
|
||||||
this->layerType = (data & layerTypeMask) >> 12;
|
|
||||||
this->unusedAttributes = data & ~(behaviorMask | layerTypeMask);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean data to fit the user's custom masks
|
// Clean data to fit the user's custom masks
|
||||||
this->setAttributes(this->getAttributes());
|
this->setAttributes(this->getAttributes());
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ QList<Metatile*> MetatileParser::parse(QString filepath, bool *error, bool prima
|
||||||
return { };
|
return { };
|
||||||
}
|
}
|
||||||
|
|
||||||
int attrSize = Metatile::getAttributesSize(version);
|
int attrSize = Metatile::getDefaultAttributesSize(version);
|
||||||
int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary();
|
int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary();
|
||||||
int numMetatiles = static_cast<unsigned char>(in.at(0)) |
|
int numMetatiles = static_cast<unsigned char>(in.at(0)) |
|
||||||
(static_cast<unsigned char>(in.at(1)) << 8) |
|
(static_cast<unsigned char>(in.at(1)) << 8) |
|
||||||
|
@ -82,7 +82,7 @@ QList<Metatile*> MetatileParser::parse(QString filepath, bool *error, bool prima
|
||||||
uint32_t attributes = 0;
|
uint32_t attributes = 0;
|
||||||
for (int j = 0; j < attrSize; j++)
|
for (int j = 0; j < attrSize; j++)
|
||||||
attributes |= static_cast<unsigned char>(in.at(attrOffset + j)) << (8 * j);
|
attributes |= static_cast<unsigned char>(in.at(attrOffset + j)) << (8 * j);
|
||||||
metatile->setAttributes(attributes, version);
|
metatile->convertAttributes(attributes, version);
|
||||||
metatile->tiles = tiles;
|
metatile->tiles = tiles;
|
||||||
metatiles.append(metatile);
|
metatiles.append(metatile);
|
||||||
}
|
}
|
||||||
|
|
|
@ -528,7 +528,7 @@ bool MainWindow::openProject(QString dir) {
|
||||||
userConfig.load();
|
userConfig.load();
|
||||||
projectConfig.setProjectDir(dir);
|
projectConfig.setProjectDir(dir);
|
||||||
projectConfig.load();
|
projectConfig.load();
|
||||||
Metatile::calculateAttributeLayout();
|
Metatile::setCustomLayout();
|
||||||
|
|
||||||
this->closeSupplementaryWindows();
|
this->closeSupplementaryWindows();
|
||||||
this->setProjectSpecificUIVisibility();
|
this->setProjectSpecificUIVisibility();
|
||||||
|
|
Loading…
Reference in a new issue