Undates for 2.6.0

* added UI event buffering to the anaylsis results, making it much more performant with many seeds (#122)
* added custom separator option for csv export (#122)
* added DejaVuSans monospace font for a more consistent look (#107)
* added filter for biome center locations with scale 1:256 for versions up to 1.17 (#63)
* changed biome statistics UI to display seeds as rows (#122)
* changed matching seed list and some anaylsis results to be tristate sortable
* changed zoom limits for the goto dialog, allowing a larger manual zoom range (#162)
* changed abandoned village and end ship modifiers to be a tristate with exclude option (#168)
* fixed incorrect progress display for anaylses (#165)
* fixed stronghold filter so it doesn't skip the last inner ring stronghold (#171)
* fixed slightly inaccurate biome check location for some villages and bastions (#168)
* + few more minor fixes and tweaks
This commit is contained in:
Cubitect 2022-11-13 15:48:38 +01:00
parent 3be941f0a9
commit a2bfd58002
41 changed files with 1347 additions and 589 deletions

@ -1 +1 @@
Subproject commit 0bf8cb0cce10b75b7996fcd72792fc2a6a4014c2
Subproject commit 10e297d17da679ecb70a6b91b46d619c59aa24e4

BIN
rc/fonts/DejaVuSansMono.ttf Normal file

Binary file not shown.

View File

@ -29,9 +29,9 @@
<file>icons/map.png</file>
<file>icons/quad.png</file>
<file>icons/tempcat.png</file>
<file>icons/check0.png</file>
<file>icons/check1.png</file>
<file>icons/check2.png</file>
<file>icons/check_unchecked.png</file>
<file>icons/check_include.png</file>
<file>icons/check_exclude.png</file>
<file>icons/origin.png</file>
<file>icons/treasure.png</file>
<file>icons/treasure_d.png</file>
@ -69,5 +69,7 @@
<file>icons/portal_lit.png</file>
<file>icons/portal_giant.png</file>
<file>icons/end_ship.png</file>
<file>icons/check_exclude_d.png</file>
<file>icons/check_include_d.png</file>
</qresource>
</RCC>

BIN
rc/icons/check_exclude.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

BIN
rc/icons/check_include.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

View File

@ -211,5 +211,6 @@
<file>dark.qss</file>
<file>fonts/DejaVuSans.ttf</file>
<file>fonts/DejaVuSans-Bold.ttf</file>
<file>fonts/DejaVuSansMono.ttf</file>
</qresource>
</RCC>

View File

@ -5,8 +5,8 @@
#include <QString>
#define VERS_MAJOR 2
#define VERS_MINOR 5
#define VERS_PATCH 1 // negative patch number designates a development version
#define VERS_MINOR 6
#define VERS_PATCH 0 // negative patch number designates a development version
// returns +1 if newer, -1 if older and 0 if equal
inline int cmpVers(int major, int minor, int patch)

View File

@ -55,9 +55,7 @@ ConditionDialog::ConditionDialog(FormConditions *parent, Config *config, int mcv
QString mcs = tr("MC %1", "Minecraft version").arg(p_mcs ? p_mcs : "?");
ui->labelMC->setText(mcs);
QFont mono = QFont("Monospace", 10);
mono.setStyleHint(QFont::TypeWriter);
ui->lineSummary->setFont(mono);
ui->lineSummary->setFont(g_font_mono);
// prevent bold font of group box title getting inherited
QFont dfont = font();
@ -159,11 +157,14 @@ ConditionDialog::ConditionDialog(FormConditions *parent, Config *config, int mcv
}
QString tristyle =
"QCheckBox::indicator:unchecked { image: url(:/icons/check0.png); }\n"
"QCheckBox::indicator:indeterminate { image: url(:/icons/check1.png); }\n"
"QCheckBox::indicator:checked { image: url(:/icons/check2.png); }\n";
"QCheckBox::indicator:indeterminate { image: url(:/icons/check_include.png); }\n"
"QCheckBox::indicator:checked { image: url(:/icons/check_exclude.png); }\n"
"QCheckBox::indicator:indeterminate:disabled { image: url(:/icons/check_include_d.png); }\n"
"QCheckBox::indicator:checked:disabled { image: url(:/icons/check_exclude_d.png); }\n";
ui->scrollBiomes->setStyleSheet(tristyle);
ui->scrollNoise->setStyleSheet(tristyle);
ui->checkAbandoned->setStyleSheet(tristyle);
ui->checkEndShip->setStyleSheet(tristyle);
memset(climaterange, 0, sizeof(climaterange));
memset(climatecomplete, 0, sizeof(climatecomplete));
@ -210,16 +211,9 @@ ConditionDialog::ConditionDialog(FormConditions *parent, Config *config, int mcv
ids.push_back(id);
IdCmp cmp(IdCmp::SORT_LEX, mc, DIM_UNDEF);
std::sort(ids.begin(), ids.end(), cmp);
QStringList allowed_matches;
for (int id : ids)
{
if (isOverworld(mc, id))
{
QString s = biome2str(mc, id);
ui->comboMatchBiome->addItem(getBiomeIcon(id), s, QVariant::fromValue(id));
allowed_matches.append(s);
}
const int *lim = getBiomeParaLimits(mc, id);
if (!lim)
continue;
@ -288,6 +282,9 @@ ConditionDialog::ConditionDialog(FormConditions *parent, Config *config, int mcv
ui->comboBoxRelative->setCurrentIndex(initindex);
on_comboBoxRelative_activated(initindex);
ui->comboMatchBiome->insertItem(0, biome2str(mc, cond.biomeId), QVariant::fromValue(cond.biomeId));
ui->comboMatchBiome->setCurrentIndex(0);
updateMode();
ui->spinBox->setValue(cond.count);
@ -352,25 +349,16 @@ ConditionDialog::ConditionDialog(FormConditions *parent, Config *config, int mcv
}
}
int idx = ui->comboMatchBiome->findData(QVariant::fromValue(cond.biomeId));
if (idx >= 0)
{
ui->comboMatchBiome->setCurrentIndex(idx);
}
else
{
QString bstr = biome2str(mc, cond.biomeId);
ui->comboMatchBiome->insertItem(0, QIcon(":/icons/check2.png"), bstr, QVariant::fromValue(cond.biomeId));
ui->comboMatchBiome->setCurrentIndex(0);
allowed_matches.append(bstr);
}
ui->lineBiomeSize->setText(QString::number(cond.biomeSize));
ui->lineTollerance->setText(QString::number(cond.tol));
auto totristate = [](uint16_t st, uint16_t msk) {
return (st & msk) ? (st & Condition::VAR_NOT) ? Qt::Checked : Qt::PartiallyChecked : Qt::Unchecked;
};
ui->checkStartPieces->setChecked(cond.varflags & Condition::VAR_WITH_START);
ui->checkAbandoned->setChecked(cond.varflags & Condition::VAR_ABANODONED);
ui->checkEndShip->setChecked(cond.varflags & Condition::VAR_ENDSHIP);
ui->checkDenseBB->setChecked(cond.varflags & Condition::VAR_DENSE_BB);
ui->checkAbandoned->setCheckState(totristate(cond.varflags, Condition::VAR_ABANODONED));
ui->checkEndShip->setCheckState(totristate(cond.varflags, Condition::VAR_ENDSHIP));
for (VariantCheckBox *cb : qAsConst(variantboxes))
{
int idx = cb->sp - g_start_pieces;
@ -390,12 +378,6 @@ ConditionDialog::ConditionDialog(FormConditions *parent, Config *config, int mcv
setClimateLimits(climaterange[1], cond.limex, false);
}
QRegularExpressionValidator *reval = new QRegularExpressionValidator(
QRegularExpression("(" + allowed_matches.join("|") + ")"), this
);
ui->comboMatchBiome->lineEdit()->setValidator(reval);
on_lineSquare_editingFinished();
onClimateLimitChanged();
@ -480,7 +462,7 @@ void ConditionDialog::updateMode()
{
ui->stackedWidget->setCurrentWidget(ui->pageClimates);
}
else if (filterindex == F_BIOME_CENTER)
else if (filterindex == F_BIOME_CENTER || filterindex == F_BIOME_CENTER_256)
{
ui->stackedWidget->setCurrentWidget(ui->pageBiomeCenter);
}
@ -637,41 +619,80 @@ void ConditionDialog::updateBiomeSelection()
}
}
// separate available biomes
QLayoutItem *sep = ui->gridLayoutBiomes->takeAt(ui->gridLayoutBiomes->indexOf(separator));
std::vector<int> unavailable;
std::map<int, QLayoutItem*> items;
for (const auto& it : biomecboxes)
{
int id = it.first;
QCheckBox *cb = it.second;
int idx = ui->gridLayoutBiomes->indexOf(cb);
items[id] = ui->gridLayoutBiomes->takeAt(idx);
if (std::find(available.begin(), available.end(), id) == available.end())
unavailable.push_back(id);
}
IdCmp cmp = {IdCmp::SORT_LEX, mc, DIM_UNDEF};
std::sort(available.begin(), available.end(), cmp);
std::sort(unavailable.begin(), unavailable.end(), cmp);
int row = 0;
for (int i = 0, len = available.size(), mod = (len+1)/2; i < len; i++)
if (ui->stackedWidget->currentWidget() == ui->pageBiomes)
{
int id = available[i];
biomecboxes[id]->setEnabled(true);
QLayoutItem *item = items[id];
ui->gridLayoutBiomes->addItem(item, row+i%mod, i/mod);
// separate available biomes
QLayoutItem *sep = ui->gridLayoutBiomes->takeAt(ui->gridLayoutBiomes->indexOf(separator));
std::vector<int> unavailable;
std::map<int, QLayoutItem*> items;
for (const auto& it : biomecboxes)
{
int id = it.first;
QCheckBox *cb = it.second;
int idx = ui->gridLayoutBiomes->indexOf(cb);
items[id] = ui->gridLayoutBiomes->takeAt(idx);
if (std::find(available.begin(), available.end(), id) == available.end())
unavailable.push_back(id);
}
std::sort(unavailable.begin(), unavailable.end(), cmp);
int row = 0;
for (int i = 0, len = available.size(), mod = (len+1)/2; i < len; i++)
{
int id = available[i];
biomecboxes[id]->setEnabled(true);
QLayoutItem *item = items[id];
ui->gridLayoutBiomes->addItem(item, row+i%mod, i/mod);
}
row = (available.size() + 1) / 2;
ui->gridLayoutBiomes->addItem(sep, row, 0, 1, 2);
row++;
for (int i = 0, len = unavailable.size(), mod = (len+1)/2; i < len; i++)
{
int id = unavailable[i];
biomecboxes[id]->setEnabled(false);
QLayoutItem *item = items[id];
ui->gridLayoutBiomes->addItem(item, row+i%mod, i/mod);
}
}
row = (available.size() + 1) / 2;
ui->gridLayoutBiomes->addItem(sep, row, 0, 1, 2);
row++;
for (int i = 0, len = unavailable.size(), mod = (len+1)/2; i < len; i++)
if (ui->stackedWidget->currentWidget() == ui->pageBiomeCenter)
{
int id = unavailable[i];
biomecboxes[id]->setEnabled(false);
QLayoutItem *item = items[id];
ui->gridLayoutBiomes->addItem(item, row+i%mod, i/mod);
QStringList allowed_matches;
QVariant curid = ui->comboMatchBiome->currentData();
ui->comboMatchBiome->clear();
for (int id: available)
{
QString s = biome2str(mc, id);
ui->comboMatchBiome->addItem(getBiomeIcon(id), s, QVariant::fromValue(id));
allowed_matches.append(s);
}
if (curid.isValid())
{
int idx = ui->comboMatchBiome->findData(curid);
if (idx >= 0)
{
ui->comboMatchBiome->setCurrentIndex(idx);
}
else
{
QString s = QString("%1 %2").arg(WARNING_CHAR).arg(biome2str(mc, curid.toInt()));
ui->comboMatchBiome->insertItem(0, getBiomeIcon(curid.toInt(), true), s, curid);
ui->comboMatchBiome->setCurrentIndex(0);
allowed_matches.append(s);
}
}
QRegularExpressionValidator *reval = new QRegularExpressionValidator(
QRegularExpression("(" + allowed_matches.join("|") + ")"), this
);
ui->comboMatchBiome->lineEdit()->setValidator(reval);
on_lineBiomeSize_textChanged("");
}
}
@ -703,7 +724,7 @@ int ConditionDialog::warnIfBad(Condition cond)
}
}
}
else if (cond.type == F_BIOME_CENTER)
else if (cond.type == F_BIOME_CENTER || cond.type == F_BIOME_CENTER_256)
{
int w = cond.x2 - cond.x1 + 1;
int h = cond.z2 - cond.z1 + 1;
@ -918,10 +939,23 @@ void ConditionDialog::on_buttonOk_clicked()
c.flags |= MATCH_ANY;
c.varflags = c.varstart = 0;
c.varflags |= ui->checkStartPieces->isChecked() * Condition::VAR_WITH_START;
c.varflags |= ui->checkAbandoned->isChecked() * Condition::VAR_ABANODONED;
c.varflags |= ui->checkEndShip->isChecked() * Condition::VAR_ENDSHIP;
c.varflags |= ui->checkDenseBB->isChecked() * Condition::VAR_DENSE_BB;
if (ui->checkStartPieces->isChecked())
c.varflags |= Condition::VAR_WITH_START;
if (ui->checkDenseBB->isChecked())
c.varflags |= Condition::VAR_DENSE_BB;
if (ui->checkAbandoned->checkState() != Qt::Unchecked)
{
c.varflags |= Condition::VAR_ABANODONED;
if (ui->checkAbandoned->checkState() == Qt::Checked)
c.varflags |= Condition::VAR_NOT;
}
if (ui->checkEndShip->checkState() != Qt::Unchecked)
{
c.varflags |= Condition::VAR_ENDSHIP;
if (ui->checkAbandoned->checkState() == Qt::Checked)
c.varflags |= Condition::VAR_NOT;
}
for (VariantCheckBox *cb : qAsConst(variantboxes))
{
if (!cb->isChecked())
@ -1090,9 +1124,15 @@ void ConditionDialog::onClimateLimitChanged()
}
}
void ConditionDialog::on_lineBiomeSize_textChanged(const QString &text)
void ConditionDialog::on_lineBiomeSize_textChanged(const QString &)
{
double area = text.toInt();
ui->labelBiomeSize->setText(QString::asprintf("(%g sq. chunks)", area / 16));
int filterindex = ui->comboBoxType->currentData().toInt();
double area = ui->lineBiomeSize->text().toInt();
QString s;
if (filterindex == F_BIOME_CENTER_256)
s = QString::asprintf("(~%g sq. chunks)", area * 256);
else
s = QString::asprintf("(%g sq. chunks)", area / 16.0);
ui->labelBiomeSize->setText(s);
}

View File

@ -489,7 +489,7 @@
<number>0</number>
</property>
<property name="currentIndex">
<number>2</number>
<number>0</number>
</property>
<widget class="QWidget" name="pageNone">
<property name="enabled">
@ -701,8 +701,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>60</width>
<height>20</height>
<width>634</width>
<height>300</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
@ -1013,8 +1013,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>60</width>
<height>20</height>
<width>634</width>
<height>253</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
@ -1068,6 +1068,9 @@
<property name="text">
<string>Abandoned</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
@ -1080,8 +1083,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>60</width>
<height>20</height>
<width>634</width>
<height>335</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3_1">
@ -1137,8 +1140,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>60</width>
<height>20</height>
<width>634</width>
<height>335</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3_2">
@ -1242,8 +1245,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>60</width>
<height>20</height>
<width>634</width>
<height>335</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3_3">
@ -1284,6 +1287,9 @@
<property name="text">
<string>End ship</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">

View File

@ -28,9 +28,7 @@ ConfigDialog::ConfigDialog(QWidget *parent, Config *config)
}
#endif
QFont mono = QFont("Monospace", 9);
mono.setStyleHint(QFont::TypeWriter);
ui->buttonBiomeColor->setFont(mono);
ui->buttonBiomeColor->setFont(g_font_mono);
ui->lineMatching->setValidator(new QIntValidator(1, 99999999, ui->lineMatching));
@ -56,6 +54,9 @@ void ConfigDialog::initSettings(Config *config)
ui->lineMatching->setText(QString::number(config->maxMatching));
ui->lineGridSpacing->setText(config->gridSpacing ? QString::number(config->gridSpacing) : "");
ui->spinCacheSize->setValue(config->mapCacheSize);
ui->lineSep->setText(config->separator);
int idx = config->quote == "\'" ? 1 : config->quote== "\"" ? 2 : 0;
ui->comboQuote->setCurrentIndex(idx);
setBiomeColorPath(config->biomeColorPath);
}
@ -72,6 +73,9 @@ Config ConfigDialog::getSettings()
conf.maxMatching = ui->lineMatching->text().toInt();
conf.gridSpacing = ui->lineGridSpacing->text().toInt();
conf.mapCacheSize = ui->spinCacheSize->value();
conf.separator = ui->lineSep->text();
int idx = ui->comboQuote->currentIndex();
conf.quote = idx == 1 ? "\'" : idx == 2 ? "\"" : "";
if (!conf.maxMatching) conf.maxMatching = 65536;

View File

@ -10,13 +10,13 @@
<normaloff>:/icons/logo.png</normaloff>:/icons/logo.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0" colspan="2">
<item row="2" column="0">
<widget class="QGroupBox" name="groupSession">
<property name="title">
<string>Session</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0" rowspan="2" colspan="3">
<item row="0" column="0" rowspan="2" colspan="2">
<widget class="QCheckBox" name="checkRestore">
<property name="text">
<string>Restore previous session at launch</string>
@ -30,7 +30,7 @@
</property>
</widget>
</item>
<item row="2" column="2">
<item row="2" column="1">
<widget class="QSpinBox" name="spinAutosave">
<property name="suffix">
<string> min</string>
@ -49,7 +49,52 @@
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="3" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Export</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="1" column="1">
<widget class="QLineEdit" name="lineSep"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>CSV cell quotation:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>CSV column separator:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="comboQuote">
<item>
<property name="text">
<string>Where necessary</string>
</property>
</item>
<item>
<property name="text">
<string>Single quotes (')</string>
</property>
</item>
<item>
<property name="text">
<string>Double quotes (&quot;)</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupSearch">
<property name="title">
<string>Search</string>
@ -68,7 +113,17 @@
</layout>
</widget>
</item>
<item row="11" column="0" colspan="2">
<item row="5" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QGroupBox" name="groupMisc">
<property name="title">
<string>Miscellaneous</string>
@ -84,17 +139,7 @@
</layout>
</widget>
</item>
<item row="12" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="2" colspan="2">
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupInterface">
<property name="title">
<string>Interface</string>
@ -119,13 +164,6 @@
</property>
</widget>
</item>
<item row="8" column="0" colspan="4">
<widget class="QCheckBox" name="checkBBoxes">
<property name="text">
<string>Outline known bounding boxes</string>
</property>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QPushButton" name="buttonBiomeColor">
<property name="text">
@ -187,16 +225,6 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="4">
<widget class="QCheckBox" name="checkSmooth">
<property name="toolTip">
<string>Simulate innertia for the map view</string>
</property>
<property name="text">
<string>Smooth map motion</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_4">
<property name="text">
@ -259,13 +287,30 @@ Leave blank for the default behaviour</string>
<item row="4" column="2" colspan="3">
<widget class="QLineEdit" name="lineGridSpacing"/>
</item>
<item row="6" column="0" colspan="4">
<item row="6" column="0" colspan="5">
<widget class="QCheckBox" name="checkDockable">
<property name="text">
<string>Undockable map</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="5">
<widget class="QCheckBox" name="checkSmooth">
<property name="toolTip">
<string>Simulate innertia for the map view</string>
</property>
<property name="text">
<string>Smooth map motion</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="5">
<widget class="QCheckBox" name="checkBBoxes">
<property name="text">
<string>Outline known bounding boxes</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -159,16 +159,18 @@ inline int str2seed(const QString &str, uint64_t *out)
return S_TEXT;
}
struct IdCmp
{
enum
{
SORT_ID,
SORT_LEX,
SORT_DIM,
};
IdCmp(int mode, int mc, int dim) : mode(mode),mc(mc),dim(dim) {}
IdCmp(int mode, int mc, int dim) : mode(mode),mc(mc),dim(dim)
{
}
int mode;
int mc;
@ -190,6 +192,13 @@ struct IdCmp
}
if (v1 ^ v2)
return v1;
if (mode == SORT_DIM)
{
int d1 = getDimension(id1);
int d2 = getDimension(id2);
if (d1 != d2)
return (d1==0 ? 0 : d1==-1 ? 1 : 2) < (d2==0 ? 0 : d2==-1 ? 1 : 2);
}
const char *s1 = biome2str(mc, id1);
const char *s2 = biome2str(mc, id2);
if (!s1 && !s2) return id1 < id2;

View File

@ -40,9 +40,7 @@ FormConditions::FormConditions(QWidget *parent)
qRegisterMetaType< Condition >("Condition");
qRegisterMetaTypeStreamOperators< Condition >("Condition");
QFont mono = QFont("Monospace", 9);
mono.setStyleHint(QFont::TypeWriter);
ui->listConditionsFull->setFont(mono);
ui->listConditionsFull->setFont(g_font_mono);
}
FormConditions::~FormConditions()

View File

@ -79,9 +79,7 @@ FormGen48::FormGen48(MainWindow *parent)
connect(ui->lineSalt, SIGNAL(editingFinished()), SLOT(onChange()));
connect(ui->lineListSalt, SIGNAL(editingFinished()), SLOT(onChange()));
QFont mono = QFont("Monospace", 9);
mono.setStyleHint(QFont::TypeWriter);
ui->lineList48->setFont(mono);
ui->lineList48->setFont(g_font_mono);
cond.type = 0;
Gen48Settings defaults;

View File

@ -12,12 +12,91 @@
#include <QClipboard>
#include <QFileDialog>
#include <QMessageBox>
#include <QFontMetrics>
QVariant SeedTableModel::data(const QModelIndex& index, int role) const
{
if (role == Qt::DisplayRole)
{
if (index.column() == COL_SEED)
return seeds[index.row()].txtSeed;
if (index.column() == COL_HEX48)
return seeds[index.row()].txtHex48;
if (index.column() == COL_TOP16)
return seeds[index.row()].txtTop16;
}
else if (role == Qt::UserRole)
{
if (index.column() == COL_HEX48)
return seeds[index.row()].varHex48;
if (index.column() == COL_TOP16)
return seeds[index.row()].varTop16;
return seeds[index.row()].varSeed;
}
return QVariant::Invalid;
}
QVariant SeedTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (section < 0)
return QVariant::Invalid;
if (role == Qt::InitialSortOrderRole)
return QVariant::fromValue(Qt::DescendingOrder);
if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
{
if (section == COL_SEED)
return QVariant::fromValue(tr("seed"));
if (section == COL_TOP16)
return QVariant::fromValue(tr("top 16"));
if (section == COL_HEX48)
return QVariant::fromValue(tr("lower 48 bit"));
}
if (role == Qt::DisplayRole && orientation == Qt::Vertical)
return QVariant::fromValue(section + 1);
return QVariant::Invalid;
}
int SeedTableModel::insertSeeds(QVector<uint64_t> newseeds)
{
int row = seeds.size();
beginInsertRows(QModelIndex(), row, row + newseeds.size()-1);
for (uint64_t seed : qAsConst(newseeds))
{
Seed s;
s.seed = seed;
s.varSeed = QVariant::fromValue(seed);
s.varTop16 = QVariant::fromValue((quint64)(seed>>48) & 0xFFFF);
s.varHex48 = QVariant::fromValue((quint64)(seed & MASK48));
s.txtSeed = QVariant::fromValue(QString::asprintf("%" PRId64, seed));
s.txtTop16 = QVariant::fromValue(QString::asprintf("%04llx", (quint64)(seed>>48) & 0xFFFF));
s.txtHex48 = QVariant::fromValue(QString::asprintf("%012llx", (quint64)(seed & MASK48)));
seeds.append(s);
}
endInsertRows();
return row;
}
void SeedTableModel::removeRow(int row)
{
beginRemoveRows(QModelIndex(), row, row);
seeds.removeAt(row);
endRemoveRows();
}
void SeedTableModel::reset()
{
beginRemoveRows(QModelIndex(), 0, seeds.size());
seeds.clear();
endRemoveRows();
}
FormSearchControl::FormSearchControl(MainWindow *parent)
: QWidget(parent)
, parent(parent)
, ui(new Ui::FormSearchControl)
, model(new SeedTableModel(this))
, proxy(new SeedSortProxy(this))
, protodialog()
, sthread(this)
, stimer()
@ -28,22 +107,38 @@ FormSearchControl::FormSearchControl(MainWindow *parent)
, slist64()
, smin(0)
, smax(~(uint64_t)0)
, qbuf()
, nextupdate()
{
ui->setupUi(this);
protodialog = new ProtoBaseDialog(this);
QFont mono = QFont("Monospace", 9);
mono.setStyleHint(QFont::TypeWriter);
ui->listResults->setFont(mono);
ui->progressBar->setFont(mono);
ui->labelStatus->setFont(mono);
ui->results->setFont(g_font_mono);
ui->progressBar->setFont(g_font_mono);
ui->labelStatus->setFont(g_font_mono);
ui->listResults->horizontalHeader()->setFont(mono);
proxy->setSourceModel(model);
ui->results->setModel(proxy);
ui->results->horizontalHeader()->setFont(g_font_mono);
ui->results->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
ui->results->verticalHeader()->setDefaultSectionSize(QFontMetrics(g_font_mono).height());
ui->results->setColumnWidth(SeedTableModel::COL_SEED, 200);
ui->results->setColumnWidth(SeedTableModel::COL_TOP16, 60);
ui->results->setColumnWidth(SeedTableModel::COL_HEX48, 120);
connect(ui->results->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this, &FormSearchControl::onSort);
ui->results->sortByColumn(-1, Qt::AscendingOrder);
connect(&sthread, &SearchMaster::searchFinish, this, &FormSearchControl::searchFinish, Qt::QueuedConnection);
connect(&stimer, &QTimer::timeout, this, QOverload<>::of(&FormSearchControl::resultTimeout));
connect(
ui->results->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
SLOT(onSeedSelectionChanged()));
searchProgressReset();
ui->spinThreads->setMaximum(QThread::idealThreadCount());
ui->spinThreads->setValue(QThread::idealThreadCount());
@ -62,11 +157,11 @@ FormSearchControl::~FormSearchControl()
QVector<uint64_t> FormSearchControl::getResults()
{
int n = ui->listResults->rowCount();
int n = proxy->rowCount();
QVector<uint64_t> results = QVector<uint64_t>(n);
for (int i = 0; i < n; i++)
{
results[i] = ui->listResults->item(i, 0)->data(Qt::UserRole).toULongLong();
results[i] = proxy->data(proxy->index(i, SeedTableModel::COL_SEED), Qt::UserRole).toULongLong();
}
return results;
}
@ -210,8 +305,7 @@ void FormSearchControl::closeProtobaseMsg()
void FormSearchControl::on_buttonClear_clicked()
{
ui->listResults->clearContents();
ui->listResults->setRowCount(0);
model->reset();
searchProgressReset();
ui->lineStart->setText("0");
}
@ -266,6 +360,7 @@ void FormSearchControl::on_buttonStart_clicked()
ui->buttonStart->setText(tr("Abort search"));
ui->buttonStart->setIcon(QIcon(":/icons/cancel.png"));
searchLockUi(true);
nextupdate = 0;
sthread.start();
elapsed.start();
stimer.start(250);
@ -310,17 +405,18 @@ void FormSearchControl::on_buttonMore_clicked()
}
}
void FormSearchControl::on_listResults_itemSelectionChanged()
void FormSearchControl::onSeedSelectionChanged()
{
int row = ui->listResults->currentRow();
if (row >= 0 && row < ui->listResults->rowCount())
int row = ui->results->currentIndex().row();
if (row >= 0 && row < ui->results->model()->rowCount())
{
uint64_t s = ui->listResults->item(row, 0)->data(Qt::UserRole).toULongLong();
QModelIndex idx = ui->results->model()->index(row, SeedTableModel::COL_SEED);
uint64_t s = ui->results->model()->data(idx, Qt::UserRole).toULongLong();
emit selectedSeedChanged(s);
}
}
void FormSearchControl::on_listResults_customContextMenuRequested(const QPoint &pos)
void FormSearchControl::on_results_customContextMenuRequested(const QPoint &pos)
{
QMenu menu(this);
@ -330,19 +426,19 @@ void FormSearchControl::on_listResults_customContextMenuRequested(const QPoint &
QAction *actremove = menu.addAction(QIcon::fromTheme("list-remove"),
tr("Remove selected seed"), this,
&FormSearchControl::removeCurrent, QKeySequence::Delete);
actremove->setEnabled(!ui->listResults->selectedItems().empty());
actremove->setEnabled(!ui->results->selectionModel()->hasSelection());
QAction *actcopy = menu.addAction(QIcon::fromTheme("edit-copy"),
tr("Copy list to clipboard"), this,
&FormSearchControl::copyResults, QKeySequence::Copy);
actcopy->setEnabled(ui->listResults->rowCount() > 0);
actcopy->setEnabled(ui->results->model()->rowCount() > 0);
int n = pasteList(true);
QAction *actpaste = menu.addAction(QIcon::fromTheme("edit-paste"),
tr("Paste %n seed(s) from clipboard", "", n), this,
&FormSearchControl::pasteResults, QKeySequence::Paste);
actpaste->setEnabled(n > 0);
menu.exec(ui->listResults->mapToGlobal(pos));
menu.exec(ui->results->mapToGlobal(pos));
}
void FormSearchControl::on_buttonSearchHelp_clicked()
@ -409,11 +505,51 @@ int FormSearchControl::pasteList(bool dummy)
return 0;
}
void FormSearchControl::onSort(int, Qt::SortOrder)
{
// We want to achieve: none -> descending -> ascending -> none
// The headerview flips the indicator with the logic:
// if (same_section)
// new_order = old_order == descending ? ascending : descending
// else
// new_order = InitialSortOrder (else ascending)
QHeaderView *header = ui->results->horizontalHeader();
if (proxy->order == Qt::AscendingOrder && proxy->column != -1)
{
header->setSortIndicatorShown(false);
header->setSortIndicator(-1, Qt::DescendingOrder);
proxy->column = -1;
}
else
{
header->setSortIndicatorShown(true);
}
}
void FormSearchControl::onSearchResult(uint64_t seed)
{
qbuf.push_back(seed);
quint64 ns = elapsed.nsecsElapsed();
if (ns > nextupdate)
{
quint64 buffer_ms = 100; // advanced option
QTimer::singleShot(buffer_ms, this, &FormSearchControl::onBufferTimeout);
nextupdate = ns + buffer_ms * 1e6;
}
}
void FormSearchControl::onBufferTimeout()
{
searchResultsAdd(qbuf, false);
qbuf.clear();
nextupdate = 0;
}
int FormSearchControl::searchResultsAdd(QVector<uint64_t> seeds, bool countonly)
{
const Config& config = parent->config;
int ns = ui->listResults->rowCount();
int ns = model->seeds.size();
int n = ns;
if (n >= config.maxMatching)
return 0;
@ -425,12 +561,9 @@ int FormSearchControl::searchResultsAdd(QVector<uint64_t> seeds, bool countonly)
QSet<uint64_t> current;
current.reserve(n + seeds.size());
for (int i = 0; i < n; i++)
{
uint64_t seed = ui->listResults->item(i, 0)->data(Qt::UserRole).toULongLong();
current.insert(seed);
}
current.insert(model->seeds[i].seed);
ui->listResults->setSortingEnabled(false);
QVector<uint64_t> newseeds;
for (uint64_t s : seeds)
{
if (current.contains(s))
@ -441,19 +574,15 @@ int FormSearchControl::searchResultsAdd(QVector<uint64_t> seeds, bool countonly)
continue;
}
current.insert(s);
QTableWidgetItem* s48item = new QTableWidgetItem();
QTableWidgetItem* seeditem = new QTableWidgetItem();
s48item->setData(Qt::UserRole, QVariant::fromValue(s));
s48item->setText(QString::asprintf("%012llx|%04x",
(qulonglong)(s & MASK48), (uint)(s >> 48) & 0xffff));
seeditem->setData(Qt::DisplayRole, QVariant::fromValue((int64_t)s));
ui->listResults->insertRow(n);
ui->listResults->setItem(n, 0, s48item);
ui->listResults->setItem(n, 1, seeditem);
newseeds.append(s);
n++;
}
ui->listResults->setSortingEnabled(true);
if (!newseeds.empty())
{
ui->results->setSortingEnabled(false);
model->insertSeeds(newseeds);
ui->results->setSortingEnabled(true);
}
if (countonly == false && n >= config.maxMatching)
{
sthread.stop();
@ -640,18 +769,19 @@ void FormSearchControl::resultTimeout()
void FormSearchControl::removeCurrent()
{
int row = ui->listResults->currentRow();
int row = ui->results->selectionModel()->currentIndex().row();
if (row >= 0)
ui->listResults->removeRow(row);
model->removeRow(row);
}
void FormSearchControl::copyResults()
{
QString text;
int n = ui->listResults->rowCount();
int n = ui->results->model()->rowCount();
for (int i = 0; i < n; i++)
{
uint64_t seed = ui->listResults->item(i, 0)->data(Qt::UserRole).toULongLong();
QModelIndex idx = ui->results->model()->index(i, SeedTableModel::COL_SEED);
uint64_t seed = ui->results->model()->data(idx, Qt::UserRole).toULongLong();
text += QString::asprintf("%" PRId64 "\n", seed);
}
@ -661,7 +791,7 @@ void FormSearchControl::copyResults()
void FormSearchControl::keyReleaseEvent(QKeyEvent *event)
{
if (ui->listResults->hasFocus())
if (ui->results->hasFocus())
{
if (event->matches(QKeySequence::Delete))
removeCurrent();

View File

@ -6,6 +6,8 @@
#include <QKeyEvent>
#include <QMessageBox>
#include <QElapsedTimer>
#include <QAbstractTableModel>
#include <QSortFilterProxyModel>
#include <deque>
@ -19,6 +21,72 @@ class FormSearchControl;
class MainWindow;
class SeedTableModel : public QAbstractTableModel
{
public:
explicit SeedTableModel(QObject *parent = nullptr) :
QAbstractTableModel(parent) {}
enum { COL_SEED, COL_TOP16, COL_HEX48, COL_MAX };
virtual int rowCount(const QModelIndex&) const override { return seeds.size(); }
virtual int columnCount(const QModelIndex&) const override { return COL_MAX; }
virtual QVariant data(const QModelIndex& index, int role) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
int insertSeeds(QVector<uint64_t> seeds);
void removeRow(int row);
void reset();
struct Seed
{
uint64_t seed;
QVariant varSeed, varHex48, varTop16;
QVariant txtSeed, txtHex48, txtTop16;
};
QList<Seed> seeds;
};
class SeedSortProxy : public QSortFilterProxyModel
{
Q_OBJECT
public:
SeedSortProxy(QObject *parent = nullptr) : QSortFilterProxyModel(parent),column(),order(Qt::DescendingOrder) {}
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
{
if (orientation == Qt::Vertical && role == Qt::DisplayRole)
return QVariant::fromValue(section + 1);
return QSortFilterProxyModel::headerData(section, orientation, role);
}
virtual bool lessThan(const QModelIndex& a, const QModelIndex& b) const override
{
uint64_t av = sourceModel()->data(a, Qt::UserRole).toULongLong();
uint64_t bv = sourceModel()->data(b, Qt::UserRole).toULongLong();
if (a.column() == SeedTableModel::COL_SEED)
return (int64_t) bv < (int64_t) av;
else
return bv < av;
}
virtual void sort(int column, Qt::SortOrder order) override
{
if (column >= columnCount())
return;
if (this->column == -1)
QSortFilterProxyModel::sort(-1, order);
else
QSortFilterProxyModel::sort(column, order);
this->column = column;
this->order = order;
}
int column;
Qt::SortOrder order;
};
class FormSearchControl : public QWidget
{
@ -53,8 +121,9 @@ public slots:
void on_buttonStart_clicked();
void on_buttonMore_clicked();
void on_listResults_itemSelectionChanged();
void on_listResults_customContextMenuRequested(const QPoint& pos);
void onSort(int column, Qt::SortOrder);
void onSeedSelectionChanged();
void on_results_customContextMenuRequested(const QPoint& pos);
void on_buttonSearchHelp_clicked();
@ -62,6 +131,8 @@ public slots:
void pasteResults();
int pasteList(bool dummy);
void onBufferTimeout();
void onSearchResult(uint64_t seed);
int searchResultsAdd(QVector<uint64_t> seeds, bool countonly);
void searchProgressReset();
void searchProgress(uint64_t last, uint64_t end, int64_t seed);
@ -75,9 +146,12 @@ protected:
public:
struct TProg { uint64_t ns, prog; };
private:
MainWindow *parent;
Ui::FormSearchControl *ui;
SeedTableModel *model;
SeedSortProxy *proxy;
ProtoBaseDialog *protodialog;
SearchMaster sthread;
QTimer stimer;
@ -94,6 +168,10 @@ private:
// min and max seeds values
uint64_t smin, smax;
// found seeds that are waiting to be added to results
QVector<uint64_t> qbuf;
quint64 nextupdate;
};
#endif // FORMSEARCHCONTROL_H

View File

@ -32,68 +32,6 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QTableWidget" name="listResults">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>180</number>
</attribute>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>21</number>
</attribute>
<column>
<property name="text">
<string>Hex low-48 | top-16</string>
</property>
<property name="textAlignment">
<set>AlignLeading|AlignVCenter</set>
</property>
</column>
<column>
<property name="text">
<string>Seed</string>
</property>
<property name="textAlignment">
<set>AlignLeading|AlignVCenter</set>
</property>
</column>
</widget>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="4">
@ -286,6 +224,46 @@
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QTableView" name="results">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources>

View File

@ -6,6 +6,7 @@
#include <QDoubleValidator>
#include <QKeyEvent>
#include <QClipboard>
#include <QMessageBox>
GotoDialog::GotoDialog(MapView *map, qreal x, qreal z, qreal scale)
@ -15,8 +16,8 @@ GotoDialog::GotoDialog(MapView *map, qreal x, qreal z, qreal scale)
{
ui->setupUi(this);
scalemin = 1.0 / 64;
scalemax = 1024;
scalemin = 1.0 / 4096;
scalemax = 65536;
ui->lineX->setValidator(new QDoubleValidator(-3e7, 3e7, 1, ui->lineX));
ui->lineZ->setValidator(new QDoubleValidator(-3e7, 3e7, 1, ui->lineZ));
ui->lineScale->setValidator(new QDoubleValidator(scalemin, scalemax, 16, ui->lineScale));
@ -40,6 +41,14 @@ void GotoDialog::on_buttonBox_clicked(QAbstractButton *button)
qreal x = ui->lineX->text().toDouble();
qreal z = ui->lineZ->text().toDouble();
qreal scale = ui->lineScale->text().toDouble();
if (scale > 4096)
{
int button = QMessageBox::warning(this, tr("Unsafe Scale"),
tr("Setting a very large scale may be unsafe.\n"
"Continue anyway?"), QMessageBox::Abort|QMessageBox::Yes);
if (button == QMessageBox::Abort)
return;
}
if (scale < scalemin) scale = scalemin;
if (scale > scalemax) scale = scalemax;
ui->lineScale->setText(QString::asprintf("%.4f", scale));

View File

@ -15,6 +15,9 @@ unsigned char g_tempsColors[256][3];
ExtGenSettings g_extgen;
QFont g_font_default;
QFont g_font_mono;
extern "C"
int getStructureConfig_override(int stype, int mc, StructureConfig *sconf)
{
@ -36,26 +39,31 @@ int main(int argc, char *argv[])
initBiomeColors(g_biomeColors);
initBiomeTypeColors(g_tempsColors);
QApplication a(argc, argv);
QApplication app(argc, argv);
QCoreApplication::setApplicationName("cubiomes-viewer");
QTranslator translator;
translator.load("en_US", ":/lang");
a.installTranslator(&translator);
app.installTranslator(&translator);
//int fontid = QFontDatabase::addApplicationFont(":/fonts/test.ttf");
int fontid = QFontDatabase::addApplicationFont(":/fonts/DejaVuSans.ttf");
if (fontid >= 0)
{
QFontDatabase::addApplicationFont(":/fonts/DejaVuSans-Bold.ttf");
QFont fontdef = QFontDatabase::applicationFontFamilies(fontid).at(0);
fontdef.setPointSize(10);
a.setFont(fontdef);
int fontid_mono = QFontDatabase::addApplicationFont(":/fonts/DejaVuSansMono.ttf");
g_font_default = QFontDatabase::applicationFontFamilies(fontid).at(0);
g_font_default.setPointSize(10);
g_font_mono = QFontDatabase::applicationFontFamilies(fontid_mono).at(0);
g_font_mono.setPointSize(9);
app.setFont(g_font_default);
}
MainWindow mw;
mw.show();
int ret = a.exec();
int ret = app.exec();
return ret;
}

View File

@ -24,7 +24,6 @@
#include <QtDebug>
#include <QDataStream>
#include <QMenu>
#include <QFont>
#include <QFileDialog>
#include <QTextStream>
#include <QSettings>
@ -354,6 +353,8 @@ void MainWindow::saveSettings()
settings.setValue("config/gridSpacing", config.gridSpacing);
settings.setValue("config/mapCacheSize", config.mapCacheSize);
settings.setValue("config/biomeColorPath", config.biomeColorPath);
settings.setValue("config/separator", config.separator);
settings.setValue("config/quote", config.quote);
settings.setValue("world/estimateTerrain", g_extgen.estimateTerrain);
settings.setValue("world/saltOverride", g_extgen.saltOverride);
@ -412,6 +413,8 @@ void MainWindow::loadSettings()
config.gridSpacing = settings.value("config/gridSpacing", config.gridSpacing).toInt();
config.mapCacheSize = settings.value("config/mapCacheSize", config.mapCacheSize).toInt();
config.biomeColorPath = settings.value("config/biomeColorPath", config.biomeColorPath).toString();
config.separator = settings.value("config/separator", config.separator).toString();
config.quote = settings.value("config/quote", config.quote).toString();
if (!config.biomeColorPath.isEmpty())
onBiomeColorChange();
@ -587,7 +590,7 @@ bool MainWindow::loadProgress(QString fnam, bool keepresults, bool quiet)
int button = QMessageBox::warning(this, tr("Warning"),
tr("File was created with a newer version.\n"
"Progress may be incomplete or broken.\n\n"
"Continue anyway?"),
"Continue loading progress anyway?"),
QMessageBox::Abort|QMessageBox::Yes);
if (button == QMessageBox::Abort)
return false;

View File

@ -52,9 +52,7 @@ MapView::MapView(QWidget *parent)
{
memset(sshow, 0, sizeof(sshow));
QFont mono = QFont("Monospace", 9);
mono.setStyleHint(QFont::TypeWriter);
setFont(mono);
setFont(g_font_mono);
QPalette pal = palette();
pal.setColor(QPalette::Background, Qt::black);
@ -67,7 +65,7 @@ MapView::MapView(QWidget *parent)
overlay = new MapOverlay(this);
overlay->setMouseTracking(true);
overlay->setFont(mono);
overlay->setFont(g_font_mono);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
@ -226,7 +224,7 @@ void MapView::showContextMenu(const QPoint &pos)
menu.addAction(tr("Copy tp: ")+tp, [=](){ this->copyText(tp); });
menu.addAction(tr("Copy block: ")+coords, [=](){ this->copyText(coords); });
menu.addAction(tr("Copy chunk: ")+chunk, [=](){ this->copyText(chunk); });
menu.addAction(tr("Go to coordinates..."), this, &MapView::onGoto);
menu.addAction(tr("Go to coordinates..."), this, &MapView::onGoto, QKeySequence(Qt::CTRL + Qt::Key_G));
menu.exec(mapToGlobal(pos));
}
@ -312,12 +310,14 @@ void MapView::resizeEvent(QResizeEvent *e)
void MapView::wheelEvent(QWheelEvent *e)
{
qreal zoommin = 1.0 / 2048.0, zoommax = 128.0;
const qreal ang = e->angleDelta().y() / 8; // e->delta() / 8;
if (ang < 0 && blocks2pix < zoommin) return;
if (ang > 0 && blocks2pix > zoommax) return;
blocks2pix *= pow(2, ang/100);
qreal scalemin = 128.0, scalemax = 1.0 / 4096.0;
if (blocks2pix > scalemin) blocks2pix = scalemin;
if (blocks2pix < scalemax) blocks2pix = scalemax;
update();//repaint();
if (ang < 0 && blocks2pix < zoommin) blocks2pix = zoommin;
if (ang > 0 && blocks2pix > zoommax) blocks2pix = zoommax;
update();
}
void MapView::mousePressEvent(QMouseEvent *e)

View File

@ -12,7 +12,7 @@ class MapOverlay : public QWidget
public:
explicit MapOverlay(QWidget *parent = nullptr)
: QWidget(parent),pos{},bname() {}
: QWidget(parent),pos{0,0},bname() {}
~MapOverlay() {}
public slots:

View File

@ -61,9 +61,7 @@ PresetDialog::PresetDialog(QWidget *parent, WorldInfo wi, bool showEamples)
connect(ui->buttonOk, &QPushButton::clicked, this, &QDialog::accept);
connect(ui->buttonCancel, &QPushButton::clicked, this, &QDialog::reject);
QFont mono = QFont("Monospace", 9);
mono.setStyleHint(QFont::TypeWriter);
ui->listFilters->setFont(mono);
ui->listFilters->setFont(g_font_mono);
if (showEamples)
ui->tabWidget->setCurrentWidget(ui->tabExamples);

View File

@ -533,7 +533,13 @@ static bool isVariantOk(const Condition *c, WorldGen *g, int stype, int varbiome
{
if (g->mc < MC_1_10) return true;
getVariant(&sv, stype, g->mc, g->seed, pos->x, pos->z, varbiome);
if ((c->varflags & Condition::VAR_ABANODONED) && !sv.abandoned) return false;
if (c->varflags & Condition::VAR_ABANODONED)
{
if ((c->varflags & Condition::VAR_NOT) && sv.abandoned)
return false;
if (!(c->varflags & Condition::VAR_NOT) && !sv.abandoned)
return false;
}
if (!(c->varflags & Condition::VAR_WITH_START) || g->mc < MC_1_14) return true;
}
else if (stype == Bastion)
@ -555,10 +561,11 @@ static bool isVariantOk(const Condition *c, WorldGen *g, int stype, int varbiome
if (!(c->varflags & Condition::VAR_ENDSHIP)) return true;
Piece pieces[END_CITY_PIECES_MAX];
int i, n = getEndCityPieces(pieces, g->seed, pos->x >> 4, pos->z >> 4);
bool withship = !(c->varflags & Condition::VAR_NOT);
for (i = 0; i < n; i++)
if (pieces[i].type == END_SHIP)
return true;
return false;
return withship;
return !withship;
}
else if (stype == Fortress)
{
@ -1261,7 +1268,7 @@ L_qm_any:
else
{ // check if the area is entirely outside the radii ranges in which strongholds can generate
if (rmax < 1408*1408)
return COND_FAILED;
return cond->count == 0 ? COND_OK : COND_FAILED;
rmin = sqrt(rmin);
rmax = sqrt(rmax);
r = (rmax - 1408) / 3072; // maximum relevant ring number
@ -1296,7 +1303,7 @@ L_qm_any:
gen->init4Dim(0);
while (nextStronghold(&sh, &gen->g) > 0)
{
if (*abort || sh.ringnum > r)
if (*abort)
break;
bool inside;
if (rmax)
@ -1333,8 +1340,7 @@ L_qm_any:
icnt++;
}
}
if (sh.ringnum == r && sh.ringidx+1 == sh.ringmax)
if (sh.ringnum > r)
break;
}
if (cond->count == 0)
@ -1510,15 +1516,17 @@ L_noise_biome:
case F_BIOME_CENTER:
case F_BIOME_CENTER_256:
if (pass == PASS_FULL_64)
{
rx1 = ((cond->x1 << 2) + at.x) >> 2;
rz1 = ((cond->z1 << 2) + at.z) >> 2;
rx2 = ((cond->x2 << 2) + at.x) >> 2;
rz2 = ((cond->z2 << 2) + at.z) >> 2;
s = finfo.pow2;
rx1 = ((cond->x1 << s) + at.x) >> s;
rz1 = ((cond->z1 << s) + at.z) >> s;
rx2 = ((cond->x2 << s) + at.x) >> s;
rz2 = ((cond->z2 << s) + at.z) >> s;
int w = rx2 - rx1 + 1;
int h = rz2 - rz1 + 1;
Range r = {4, rx1, rz1, w, h, cond->y >> 2, 1};
Range r = {finfo.step, rx1, rz1, w, h, cond->y >> 2, 1};
gen->init4Dim(0);
if (cond->count == 0)

View File

@ -84,6 +84,7 @@ enum
F_ANCIENT_CITY,
F_LOGIC_NOT,
F_BIOME_CENTER,
F_BIOME_CENTER_256,
// new filters should be added here at the end to keep some downwards compatibility
FILTER_MAX,
};
@ -315,13 +316,6 @@ static const struct FilterList
"at layer OCEAN TEMPERATURE with scale 1:256. "
"This generation layer depends only on the lower 48-bits of the seed.")
};
list[F_BIOME_CENTER] = FilterInfo{
CAT_BIOMES, 1, 1, 1, 0, 0, 0, 4, 2, 1, MC_1_0, MC_NEWEST, 0, 1, disp++,
":icons/map.png",
_("Locate biome center 1:4"),
_("Finds the center position of a given biome. This requires the full "
"generation of those biomes and ")
};
list[F_CLIMATE_NOISE] = FilterInfo{
CAT_BIOMES, 0, 1, 1, 0, 0, 0, 4, 2, 0, MC_1_18, MC_NEWEST, 0, 0, disp++,
":icons/map.png",
@ -329,6 +323,18 @@ static const struct FilterList
_("Custom limits for the required and allowed climate noise parameters that "
"the specified area should cover.")
};
list[F_BIOME_CENTER] = FilterInfo{
CAT_BIOMES, 1, 1, 1, 0, 0, 0, 4, 2, 1, MC_1_0, MC_NEWEST, 0, 1, disp++,
":icons/map.png",
_("Locate biome center 1:4"),
_("Finds the center position of a given biome.")
};
list[F_BIOME_CENTER_256] = FilterInfo{
CAT_BIOMES, 1, 1, 1, 0, 0, 0, 256, 8, 1, MC_1_0, MC_1_17, 0, 1, disp++,
":icons/map.png",
_("Locate biome center 1:256"),
_("Finds the center position of a given biome. Based on the 1:256 biome layer.")
};
list[F_TEMPS] = FilterInfo{
CAT_BIOMES, 1, 1, 1, 0, 0, 0, 1024, 10, 0, MC_1_7, MC_1_17, 0, 0, disp++,
":icons/tempcat.png",
@ -586,6 +592,7 @@ struct /*__attribute__((packed))*/ Condition
VAR_ABANODONED = 0x02, // zombie village
VAR_ENDSHIP = 0x04, // end city ship
VAR_DENSE_BB = 0x08, // fortress with a 2x2 arrangement of start/crossings
VAR_NOT = 0x10, // invert flag (e.g. not abandoned)
};
int16_t type;
uint16_t meta;

View File

@ -121,6 +121,7 @@ bool SearchMaster::set(
}
if (finfo.cat == CAT_BIOMES &&
c.type != F_BIOME_CENTER &&
c.type != F_BIOME_CENTER_256 &&
c.type != F_TEMPS &&
c.type != F_CLIMATE_NOISE)
{
@ -221,7 +222,7 @@ bool SearchMaster::set(
static int check(uint64_t s48, void *data)
{
(void) data;
const StructureConfig sconf = {};
const StructureConfig sconf = {0,0,0,0,0};
return isQuadBaseFeature24(sconf, s48, 7+1, 7+1, 9+1) != 0;
}
@ -487,8 +488,8 @@ void SearchMaster::run()
{
SearchWorker *worker = new SearchWorker(this);
QObject::connect(
worker, &SearchWorker::results,
parent, &FormSearchControl::searchResultsAdd,
worker, &SearchWorker::result,
parent, &FormSearchControl::onSearchResult,
Qt::BlockingQueuedConnection);
QObject::connect(
worker, &SearchWorker::finished,
@ -766,9 +767,8 @@ void SearchWorker::run()
== COND_OK
)
{
QVector<uint64_t> matches = {seed};
if (!*abort)
emit results(matches, false);
emit result(seed);
}
}
//if (ie == len) // done
@ -793,9 +793,8 @@ void SearchWorker::run()
== COND_OK
)
{
QVector<uint64_t> matches = {seed};
if (!*abort)
emit results(matches, false);
emit result(seed);
}
if (++lowidx >= len)
@ -818,9 +817,8 @@ void SearchWorker::run()
== COND_OK
)
{
QVector<uint64_t> matches = {seed};
if (!*abort)
emit results(matches, false);
emit result(seed);
}
if (seed == ~(uint64_t)0)
@ -865,9 +863,8 @@ void SearchWorker::run()
== COND_OK
)
{
QVector<uint64_t> matches = {seed};
if (!*abort)
emit results(matches, false);
emit result(seed);
}
if (++high >= 0x10000)

View File

@ -77,7 +77,7 @@ public:
virtual void run() override;
signals:
int results(QVector<uint64_t> seeds, bool countonly);
void result(uint64_t seed);
public:
SearchMaster * master;

View File

@ -5,6 +5,7 @@
#include <QThread>
#include <QString>
#include <QFont>
#include <vector>
@ -31,6 +32,10 @@ struct ExtGenSettings
// Keep the extended generator settings in global scope.
extern ExtGenSettings g_extgen;
// global references to the default fonts
extern QFont g_font_default;
extern QFont g_font_mono;
struct WorldInfo
{
int mc;
@ -69,6 +74,8 @@ struct Config
int gridSpacing;
int mapCacheSize;
QString biomeColorPath;
QString separator;
QString quote;
Config() { reset(); }
@ -85,6 +92,8 @@ struct Config
gridSpacing = 0;
mapCacheSize = 256;
biomeColorPath = "";
separator = ";";
quote = "";
}
};

View File

@ -10,14 +10,7 @@
<normaloff>:/icons/logo.png</normaloff>:/icons/logo.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QGroupBox" name="groupVis">
<property name="title">
<string>Maximum scale for structure visibility</string>
</property>
</widget>
</item>
<item row="1" column="0">
<item row="1" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -27,6 +20,13 @@
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupVis">
<property name="title">
<string>Maximum scale for structure visibility</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources>

View File

@ -18,22 +18,22 @@ void AnalysisBiomes::run()
Generator g;
setupGenerator(&g, wi.mc, wi.large);
for (int64_t seed : qAsConst(seeds))
for (idx = 0; idx < seeds.size(); idx++)
{
if (stop) break;
wi.seed = seed;
if (locate < 0)
runStatistics(&g);
else
wi.seed = seeds[idx];
if (dat.locate >= 0)
runLocate(&g);
else
runStatistics(&g);
}
}
void AnalysisBiomes::runStatistics(Generator *g)
{
QVector<uint64_t> idcnt(256);
int w = x2 - x1 + 1;
int h = z2 - z1 + 1;
int w = dat.x2 - dat.x1 + 1;
int h = dat.z2 - dat.z1 + 1;
uint64_t n = w * (uint64_t)h;
for (int d = 0; d < 3; d++)
@ -42,16 +42,16 @@ void AnalysisBiomes::runStatistics(Generator *g)
continue;
applySeed(g, dims[d], wi.seed);
if (samples >= n)
if (dat.samples >= n)
{ // full area gen => generate 512x512 areas at a time
const int step = 512;
for (int x = x1; x <= x2 && !stop; x += step)
for (int x = dat.x1; x <= dat.x2 && !stop; x += step)
{
for (int z = z1; z <= z2 && !stop; z += step)
for (int z = dat.z1; z <= dat.z2 && !stop; z += step)
{
int w = x2-x+1 < step ? x2-x+1 : step;
int h = z2-z+1 < step ? z2-z+1 : step;
Range r = {scale, x, z, w, h, wi.y, 1};
int w = dat.x2-x+1 < step ? dat.x2-x+1 : step;
int h = dat.z2-z+1 < step ? dat.z2-z+1 : step;
Range r = {dat.scale, x, z, w, h, wi.y, 1};
int *ids = allocCache(g, r);
genBiomes(g, ids, r);
for (int i = 0; i < w*h; i++)
@ -64,7 +64,7 @@ void AnalysisBiomes::runStatistics(Generator *g)
{
std::vector<uint64_t> order;
if (samples * 2 >= n)
if (dat.samples * 2 >= n)
{ // dense regime => shuffle indeces
order.resize(n);
for (uint64_t i = 0; i < n; i++)
@ -78,14 +78,14 @@ void AnalysisBiomes::runStatistics(Generator *g)
order[i] = order[idx];
order[idx] = t;
}
order.resize(samples);
order.resize(dat.samples);
}
else
{ // sparse regime => fill randomly without reuse
std::unordered_set<uint64_t> used;
order.reserve(samples);
used.reserve(samples);
for (uint64_t i = 0; order.size() < samples; i++)
order.reserve(dat.samples);
used.reserve(dat.samples);
for (uint64_t i = 0; order.size() < dat.samples; i++)
{
if (!(i & 0xffff) && stop)
break;
@ -96,12 +96,12 @@ void AnalysisBiomes::runStatistics(Generator *g)
}
}
for (uint64_t i = 0; i < samples && !stop; i++)
for (uint64_t i = 0; i < dat.samples && !stop; i++)
{
uint64_t idx = order[i];
int x = (int) (idx % w);
int z = (int) (idx / w);
int id = getBiomeAt(g, scale, x1+x, wi.y, z1+z);
int id = getBiomeAt(g, dat.scale, dat.x1+x, wi.y, dat.z1+z);
idcnt[ id & 0xff ]++;
}
}
@ -117,9 +117,9 @@ void AnalysisBiomes::runLocate(Generator *g)
enum { MAX_LOCATE = 4096 };
Pos pos[MAX_LOCATE];
int siz[MAX_LOCATE];
Range r = {4, x1, z1, x2-x1+1, z2-z1+1, wi.y, 1};
Range r = {4, dat.x1, dat.z1, dat.x2-dat.x1+1, dat.z2-dat.z1+1, wi.y, 1};
int n = getBiomeCenters(
pos, siz, MAX_LOCATE, g, r, locate, minsize, tolerance,
pos, siz, MAX_LOCATE, g, r, dat.locate, minsize, tolerance,
(volatile char*)&stop
);
if (n && !stop)
@ -148,64 +148,173 @@ QVariant BiomeTableModel::data(const QModelIndex& index, int role) const
{
if (role != Qt::DisplayRole || index.row() < 0 || index.column() < 0)
return QVariant::Invalid;
int id = ids[index.row()];
int col = index.column();
uint64_t seed = seeds[col];
int id = ids[index.column()];
uint64_t seed = seeds[index.row()];
return cnt[id][seed];
}
QVariant BiomeTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole || section < 0)
if (section < 0)
return QVariant::Invalid;
if (orientation == Qt::Horizontal)
if (role == Qt::InitialSortOrderRole)
return QVariant::fromValue(Qt::AscendingOrder);
if (role == Qt::DisplayRole && orientation == Qt::Vertical)
{
if (section < seeds.size())
return QVariant::fromValue(seeds[section]);
return QVariant::fromValue((int64_t)seeds[section]);
}
else
if (orientation == Qt::Horizontal)
{
if (section < ids.size())
return QVariant::fromValue(QString(biome2str(cmp.mc, ids[section])));
{
if (role == Qt::DisplayRole)
return QVariant::fromValue(QString(biome2str(cmp.mc, ids[section])));
else
return QVariant::fromValue(ids[section]);
}
}
return QVariant::Invalid;
}
int BiomeTableModel::insertId(int id)
void BiomeTableModel::insertIds(QSet<int>& nids)
{
QList<int>::iterator it = std::lower_bound(ids.begin(), ids.end(), id, cmp);
if (it != ids.end() && *it == id)
return -1;
int row = std::distance(ids.begin(), it);
//beginInsertRows(QModelIndex(), row, row);
ids.insert(row, id);
//endInsertRows();
return row;
for (int id : qAsConst(nids))
{
QList<int>::iterator it = std::lower_bound(ids.begin(), ids.end(), id, cmp);
if (it != ids.end() && *it == id)
continue;
int i = std::distance(ids.begin(), it);
beginInsertColumns(QModelIndex(), i, i);
ids.insert(i, id);
endInsertColumns();
}
}
int BiomeTableModel::insertSeed(uint64_t seed)
void BiomeTableModel::insertSeeds(QList<uint64_t>& nseeds)
{
int col = seeds.size();
//beginInsertColumns(QModelIndex(), col, col);
seeds.append(seed);
//endInsertColumns();
return col;
int i = seeds.size();
beginInsertRows(QModelIndex(), i, i+nseeds.size()-1);
seeds.append(nseeds);
endInsertRows();
}
void BiomeTableModel::reset(int mc)
{
beginRemoveRows(QModelIndex(), 0, ids.size());
ids.clear();
endRemoveRows();
beginRemoveColumns(QModelIndex(), 0, seeds.size());
beginResetModel();
seeds.clear();
endRemoveColumns();
ids.clear();
cnt.clear();
cmp.mode = IdCmp::SORT_LEX;
cmp.mode = IdCmp::SORT_DIM;
cmp.dim = DIM_UNDEF;
cmp.mc = mc;
endResetModel();
}
BiomeHeader::BiomeHeader(QWidget *parent)
: QHeaderView(Qt::Horizontal, parent)
, hover(-1)
, pressed(-1)
{
setSectionsClickable(true);
setHighlightSections(true);
connect(this, &QHeaderView::sectionPressed, this, &BiomeHeader::onSectionPress);
}
void BiomeHeader::onSectionPress(int section)
{
pressed = section;
}
bool BiomeHeader::event(QEvent *e)
{
switch (e->type())
{
case QEvent::HoverEnter:
case QEvent::HoverMove:
hover = logicalIndexAt(((QHoverEvent*)e)->pos());
break;
case QEvent::Leave:
case QEvent::HoverLeave:
hover = -1;
break;
default: break;
}
return QHeaderView::event(e);
}
void BiomeHeader::paintSection(QPainter *painter, const QRect& rect, int section) const
{
if (!rect.isValid() || !model())
return;
QStyleOptionHeader opt;
initStyleOption(&opt);
QStyle::State state = QStyle::State_None;
state |= QStyle::State_Enabled;
state |= QStyle::State_Active;
if (section == hover)
state |= QStyle::State_MouseOver;
if (section == pressed)
state |= QStyle::State_Sunken;
QString s = model()->headerData(section, orientation()).toString();
painter->setFont(font());
QFontMetrics fm(font());
int indicator_height = 0;
int margin = 2 * style()->pixelMetric(QStyle::PM_HeaderMargin, 0, this);
QStyleOptionHeader::SortIndicator sortindicator = QStyleOptionHeader::None;
if (isSortIndicatorShown() && sortIndicatorSection() == section)
{
if (sortIndicatorOrder() == Qt::AscendingOrder)
sortindicator = QStyleOptionHeader::SortDown;
else
sortindicator = QStyleOptionHeader::SortUp;
indicator_height = 20;
}
int x = -rect.height() + margin + indicator_height;
int y = rect.left() + (rect.width() + fm.descent()) / 2 + margin;
opt.rect = rect;
opt.section = section;
opt.state = state;
QPointF oldBO = painter->brushOrigin();
painter->save();
painter->setBrushOrigin(opt.rect.topLeft());
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
painter->restore();
painter->rotate(-90);
painter->drawText(x, y, s);
painter->rotate(+90);
if (sortindicator != QStyleOptionHeader::None)
{
opt.sortIndicator = sortindicator;
opt.rect = rect.adjusted(0, rect.bottom()-rect.y()-indicator_height, 0, 0);
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
painter->setBrushOrigin(oldBO);
}
painter->setBrushOrigin(oldBO);
}
QSize BiomeHeader::sectionSizeFromContents(int section) const
{
if (!model())
return QSize();
int margin = 2 * style()->pixelMetric(QStyle::PM_HeaderMargin, 0, this);
QFontMetrics fm(font());
int w = fm.boundingRect(model()->headerData(section, orientation()).toString()).width();
return QSize(fm.height() + 2*margin, w + 2*margin);
}
TabBiomes::TabBiomes(MainWindow *parent)
: QWidget(parent)
@ -214,23 +323,29 @@ TabBiomes::TabBiomes(MainWindow *parent)
, thread()
, model(new BiomeTableModel(this))
, proxy(new BiomeSortProxy(this))
, sortcol(-1)
, elapsed()
, updt(20)
, nextupdate()
{
ui->setupUi(this);
proxy->setSourceModel(model);
ui->table->setModel(proxy);
QHeaderView *header = ui->table->horizontalHeader();
connect(header, &QHeaderView::sortIndicatorChanged, this, &TabBiomes::onSort);
BiomeHeader *header = new BiomeHeader(ui->table);
ui->table->setHorizontalHeader(header);
//QHeaderView *header = ui->table->horizontalHeader();
connect(header, &QHeaderView::sortIndicatorChanged, this, &TabBiomes::onTableSort);
QFont font = QFont("monospace", 9);
ui->table->setFont(font);
ui->table->setFont(g_font_mono);
ui->table->setSortingEnabled(true);
ui->table->sortByColumn(-1, Qt::AscendingOrder);
ui->treeWidget->setColumnWidth(0, 160);
ui->treeWidget->setColumnWidth(1, 120);
ui->treeWidget->sortByColumn(0, Qt::AscendingOrder);
ui->treeLocate->setColumnWidth(0, 160);
ui->treeLocate->setColumnWidth(1, 120);
ui->treeLocate->sortByColumn(-1, Qt::DescendingOrder);
ui->treeLocate->setSortingEnabled(true);
connect(ui->treeLocate->header(), &QHeaderView::sectionClicked, this, &TabBiomes::onLocateHeaderClick);
QIntValidator *intval = new QIntValidator(-60e6, 60e6, this);
ui->lineX1->setValidator(intval);
@ -255,9 +370,7 @@ TabBiomes::TabBiomes(MainWindow *parent)
str2biome[s] = id;
}
QStringList bnames;
for (auto& it : str2biome)
bnames.append(it.first);
const QStringList bnames = str2biome.keys();
QRegularExpressionValidator *reval = new QRegularExpressionValidator(
QRegularExpression("(" + bnames.join("|") + ")"), this
);
@ -266,6 +379,8 @@ TabBiomes::TabBiomes(MainWindow *parent)
TabBiomes::~TabBiomes()
{
thread.stop = true;
thread.wait(500);
delete ui;
}
@ -347,86 +462,22 @@ void TabBiomes::refreshBiomes(int activeid)
}
}
void TabBiomes::onAnalysisSeedDone(uint64_t seed, QVector<uint64_t> idcnt)
void TabBiomes::onLocateHeaderClick()
{
// save state of table UI
int selr = ui->table->selectionModel()->currentIndex().row();
int selc = ui->table->selectionModel()->currentIndex().column();
int posr = ui->table->verticalScrollBar()->value();
int posc = ui->table->horizontalScrollBar()->value();
int ncol = proxy->columnCount();
std::vector<int> colwidth(ncol, 60);
for (int c = 0; c < ncol; c++)
colwidth[c] = ui->table->columnWidth(c);
// create new model
ui->table->setSortingEnabled(false);
ui->table->setModel(nullptr);
BiomeTableModel *m_new = new BiomeTableModel(this);
m_new->ids = model->ids;
m_new->seeds = model->seeds;
m_new->cnt = model->cnt;
if (m_new->insertSeed(seed) < selc)
selc++;
for (int id = 0; id < 256; id++)
int section = ui->treeLocate->header()->sortIndicatorSection();
if (ui->treeLocate->header()->sortIndicatorOrder() == Qt::AscendingOrder && sortcol == section)
{
if (idcnt[id] == 0)
continue;
int r = m_new->insertId(id);
if (r >= 0 && r < selr)
selr++;
m_new->cnt[id][seed] = QVariant::fromValue(idcnt[id]);
ui->treeLocate->sortByColumn(-1, Qt::DescendingOrder);
section = -1;
}
delete model;
model = m_new;
// restore state of table UI for new model
proxy->setSourceModel(model);
ui->table->setModel(proxy);
ui->table->setSortingEnabled(true);
for (int c = 0; c < ncol; c++)
ui->table->setColumnWidth(c, colwidth[c]);
ui->table->resizeColumnToContents(ncol);
int rowheight = QFontMetrics(ui->table->font()).height() + 4;
for (int r = 0, nrow = proxy->rowCount(); r < nrow; r++)
ui->table->setRowHeight(r, rowheight);
ui->table->selectionModel()->setCurrentIndex(proxy->index(selr, selc), QItemSelectionModel::SelectCurrent);
ui->table->verticalScrollBar()->setValue(posr);
ui->table->horizontalScrollBar()->setValue(posc);
QString progress = QString::asprintf(" (%d/%d)", model->seeds.size()+1, thread.seeds.size());
ui->pushStart->setText(tr("Stop") + progress);
sortcol = section;
}
void TabBiomes::onAnalysisSeedItem(QTreeWidgetItem *item)
{
ui->treeWidget->addTopLevelItem(item);
QString progress = QString::asprintf(" (%d/%d)", ui->treeWidget->topLevelItemCount()+1, thread.seeds.size());
ui->pushStart->setText(tr("Stop") + progress);
}
void TabBiomes::onAnalysisFinished()
{
ui->pushExport->setEnabled(!model->ids.empty() || ui->treeWidget->topLevelItemCount());
ui->pushStart->setChecked(false);
ui->pushStart->setText(tr("Analyze"));
}
void TabBiomes::onSort(int column, Qt::SortOrder)
void TabBiomes::onTableSort(int, Qt::SortOrder)
{
QHeaderView *header = ui->table->horizontalHeader();
if (proxy->order == Qt::DescendingOrder && proxy->column == column)
if (proxy->order == Qt::DescendingOrder && proxy->column != -1)
{
header->setSortIndicatorShown(false);
header->setSortIndicator(-1, Qt::AscendingOrder);
@ -438,6 +489,113 @@ void TabBiomes::onSort(int column, Qt::SortOrder)
}
}
void TabBiomes::onAnalysisSeedDone(uint64_t seed, QVector<uint64_t> idcnt)
{
idcnt.push_back(seed);
qbufs.push_back(idcnt);
quint64 ns = elapsed.nsecsElapsed();
if (ns > nextupdate)
{
nextupdate = ns + updt * 1e6;
QTimer::singleShot(updt, this, &TabBiomes::onBufferTimeout);
}
}
void TabBiomes::onAnalysisSeedItem(QTreeWidgetItem *item)
{
qbufl.push_back(item);
quint64 ns = elapsed.nsecsElapsed();
if (ns > nextupdate)
{
nextupdate = ns + updt * 1e6;
QTimer::singleShot(updt, this, &TabBiomes::onBufferTimeout);
}
}
void TabBiomes::onAnalysisFinished()
{
onBufferTimeout();
on_tabWidget_currentChanged(-1);
ui->pushStart->setChecked(false);
ui->pushStart->setText(tr("Analyze"));
}
void TabBiomes::onBufferTimeout()
{
if (qbufs.empty() && qbufl.empty())
return;
uint64_t t = -elapsed.elapsed();
if (!qbufs.empty())
{
ui->table->setSortingEnabled(false);
ui->table->setUpdatesEnabled(false);
QMap<int, int> colwidth;
for (int c = 0, n = model->ids.size(); c < n; c++)
colwidth[model->ids[c]] = ui->table->columnWidth(c);
QList<uint64_t> new_seeds;
QSet<int> new_ids;
QFontMetrics fm(font());
for (int i = 0, n = qbufs.size(); i < n; i++)
{
QVector<uint64_t>& scnt = qbufs[i];
uint64_t seed = scnt.back();
scnt.resize(scnt.size()-1);
new_seeds.push_back(seed);
for (int id = 0, idn = scnt.size(); id < idn; id++)
{
uint64_t cnt = scnt[id];
if (cnt == 0)
continue;
new_ids.insert(id);
model->cnt[id][seed] = QVariant::fromValue(cnt);
int w = fm.boundingRect(QString::number(cnt) + "_").width() + 2;
if (w > colwidth[id])
colwidth[id] = w;
}
}
model->insertIds(new_ids);
model->insertSeeds(new_seeds);
ui->table->setUpdatesEnabled(true);
ui->table->setSortingEnabled(true);
//ui->table->resizeColumnsToContents();
for (int i = 0, n = proxy->columnCount(); i < n; i++)
{
int id = proxy->headerData(i, Qt::Horizontal, Qt::UserRole).toInt();
ui->table->setColumnWidth(i, colwidth[id]);
}
int rowheight = fm.height() + 4;
for (int i = 0, n = proxy->rowCount(); i < n; i++)
ui->table->setRowHeight(i, rowheight);
qbufs.clear();
}
if (!qbufl.empty())
{
ui->treeLocate->setSortingEnabled(false);
ui->treeLocate->setUpdatesEnabled(false);
ui->treeLocate->addTopLevelItems(qbufl);
ui->treeLocate->setUpdatesEnabled(true);
ui->treeLocate->setSortingEnabled(true);
qbufl.clear();
}
QString progress = QString::asprintf(" (%d/%d)", thread.idx.load(), thread.seeds.size());
ui->pushStart->setText(tr("Stop") + progress);
t += elapsed.elapsed();
if (8*t > updt)
updt = 4*t;
nextupdate = elapsed.nsecsElapsed() + 1e6 * updt;
}
void TabBiomes::on_pushStart_clicked()
{
if (thread.isRunning())
@ -446,6 +604,10 @@ void TabBiomes::on_pushStart_clicked()
return;
}
updt = 20;
nextupdate = 0;
elapsed.start();
parent->getSeed(&thread.wi);
thread.seeds.clear();
if (ui->comboSeedSource->currentIndex() == 0)
@ -469,19 +631,23 @@ void TabBiomes::on_pushStart_clicked()
if (ui->radioFullSample->isChecked())
{
thread.samples = ~0ULL;
thread.dat.samples = ~0ULL;
}
else
{
thread.samples = ui->lineSamples->text().toULongLong();
thread.dat.samples = ui->lineSamples->text().toULongLong();
scale = 4;
s = 2;
}
if (ui->tabWidget->currentWidget() == ui->tabLocate)
{
ui->treeWidget->clear();
thread.locate = str2biome[ui->comboBiome->currentText()];
//ui->treeWidget->clear();
ui->treeLocate->setSortingEnabled(false);
while (ui->treeLocate->topLevelItemCount() > 0)
delete ui->treeLocate->takeTopLevelItem(0);
ui->treeLocate->setSortingEnabled(true);
thread.dat.locate = str2biome[ui->comboBiome->currentText()];
thread.minsize = ui->lineBiomeSize->text().toInt();
thread.tolerance = ui->lineTolerance->text().toInt();
if (thread.minsize <= 0)
@ -492,14 +658,19 @@ void TabBiomes::on_pushStart_clicked()
else
{
model->reset(thread.wi.mc);
thread.locate = -1;
thread.dat.locate = -1;
}
thread.scale = scale;
thread.x1 = x1 >> s;
thread.z1 = z1 >> s;
thread.x2 = x2 >> s;
thread.z2 = z2 >> s;
thread.dat.scale = scale;
thread.dat.x1 = x1 >> s;
thread.dat.z1 = z1 >> s;
thread.dat.x2 = x2 >> s;
thread.dat.z2 = z2 >> s;
if (thread.dat.locate < 0)
dats = thread.dat;
else
datl = thread.dat;
ui->pushExport->setEnabled(false);
ui->pushStart->setChecked(true);
@ -507,6 +678,18 @@ void TabBiomes::on_pushStart_clicked()
thread.start();
}
static
void csvline(QTextStream& stream, const QString& qte, const QString& sep, QStringList& cols)
{
if (qte.isEmpty())
{
for (QString& s : cols)
if (s.contains(sep))
s = "\"" + s + "\"";
}
stream << qte << cols.join(sep) << qte << "\n";
}
void TabBiomes::on_pushExport_clicked()
{
QString fnam = QFileDialog::getSaveFileName(
@ -524,41 +707,53 @@ void TabBiomes::on_pushExport_clicked()
return;
}
QString qte = parent->config.quote;
QString sep = parent->config.separator;
QTextStream stream(&file);
stream << "#X1; " << thread.x1 << "; (" << (thread.x1*thread.scale) << ")\n";
stream << "#Z1; " << thread.z1 << "; (" << (thread.z1*thread.scale) << ")\n";
stream << "#X2; " << thread.x2 << "; (" << (thread.x2*thread.scale) << ")\n";
stream << "#Z2; " << thread.z2 << "; (" << (thread.z2*thread.scale) << ")\n";
stream << "#scale; 1:" << thread.scale << "\n";
stream << "Sep=" + sep + "\n";
sep = qte + sep + qte;
if (thread.locate < 0)
if (ui->tabWidget->currentWidget() == ui->tabStats)
{
if (thread.samples != ~0ULL)
stream << "#samples; " << thread.samples << "\n";
stream << qte << "#X1" << sep << dats.x1 << sep << "(" << (dats.x1*dats.scale) << ")" << qte << "\n";
stream << qte << "#Z1" << sep << dats.z1 << sep << "(" << (dats.z1*dats.scale) << ")" << qte << "\n";
stream << qte << "#X2" << sep << dats.x2 << sep << "(" << (dats.x2*dats.scale) << ")" << qte << "\n";
stream << qte << "#Z2" << sep << dats.z2 << sep << "(" << (dats.z2*dats.scale) << ")" << qte << "\n";
stream << qte << "#scale" << sep << "1:" << dats.scale << qte << "\n";
if (dats.samples != ~0ULL)
stream << qte << "#samples" << sep << dats.samples << qte << "\n";
QList<QString> header;
header.append(tr("biome\\seed"));
QStringList header = { tr("seed") };
for (int col = 0, ncol = proxy->columnCount(); col < ncol; col++)
header.append(proxy->headerData(col, Qt::Horizontal).toString());
stream << header.join("; ") << "\n";
csvline(stream, qte, sep, header);
for (int row = 0, nrow = proxy->rowCount(); row < nrow; row++)
{
QList<QString> entries;
entries.append(proxy->headerData(row, Qt::Vertical).toString());
QStringList cols;
cols.append(proxy->headerData(row, Qt::Vertical).toString());
for (int col = 0, ncol = proxy->columnCount(); col < ncol; col++)
{
QString cntstr = proxy->data(proxy->index(row, col)).toString();
entries.append(cntstr == "" ? "0" : cntstr);
cols.append(cntstr == "" ? "0" : cntstr);
}
stream << entries.join("; ") << "\n";
csvline(stream, qte, sep, cols);
}
}
else
else if (ui->tabWidget->currentWidget() == ui->tabLocate)
{
stream << "#biome; " << biome2str(MC_NEWEST, thread.locate) << "\n";
stream << "seed; area; x; z;\n";
QTreeWidgetItemIterator it(ui->treeWidget);
stream << qte << "#X1" << sep << datl.x1 << sep << "(" << (datl.x1*datl.scale) << ")" << qte << "\n";
stream << qte << "#Z1" << sep << datl.z1 << sep << "(" << (datl.z1*datl.scale) << ")" << qte << "\n";
stream << qte << "#X2" << sep << datl.x2 << sep << "(" << (datl.x2*datl.scale) << ")" << qte << "\n";
stream << qte << "#Z2" << sep << datl.z2 << sep << "(" << (datl.z2*datl.scale) << ")" << qte << "\n";
stream << qte << "#scale" << sep << "1:" << datl.scale << qte << "\n";
stream << qte << "#biome" << sep << biome2str(MC_NEWEST, datl.locate) << qte << "\n";
QStringList header = { tr("seed"), tr("area"), tr("x"), tr("z") };
csvline(stream, qte, sep, header);
QTreeWidgetItemIterator it(ui->treeLocate);
QString seed;
for (; *it; ++it)
{
@ -573,7 +768,7 @@ void TabBiomes::on_pushExport_clicked()
cols.append(item->text(1));
cols.append(item->text(2));
cols.append(item->text(3));
stream << cols.join(";") << "\n";
csvline(stream, qte, sep, cols);
}
}
}
@ -616,7 +811,7 @@ void TabBiomes::on_lineBiomeSize_textChanged(const QString &text)
ui->labelBiomeSize->setText(QString::asprintf("(%g sq. chunks)", area / 16));
}
void TabBiomes::on_treeWidget_itemClicked(QTreeWidgetItem *item, int column)
void TabBiomes::on_treeLocate_itemClicked(QTreeWidgetItem *item, int column)
{
(void) column;
QVariant dat;
@ -639,3 +834,15 @@ void TabBiomes::on_treeWidget_itemClicked(QTreeWidgetItem *item, int column)
}
}
void TabBiomes::on_tabWidget_currentChanged(int)
{
bool ok = false;
if (!thread.isRunning())
{
if (ui->tabWidget->currentWidget() == ui->tabStats)
ok = !model->ids.empty();
if (ui->tabWidget->currentWidget() == ui->tabLocate)
ok = ui->treeLocate->topLevelItemCount() > 0;
}
ui->pushExport->setEnabled(ok);
}

View File

@ -21,7 +21,7 @@ class AnalysisBiomes : public QThread
Q_OBJECT
public:
explicit AnalysisBiomes(QObject *parent = nullptr)
: QThread(parent) {}
: QThread(parent),idx() {}
virtual void run() override;
void runStatistics(Generator *g);
@ -35,11 +35,14 @@ public:
QVector<uint64_t> seeds;
WorldInfo wi;
std::atomic_bool stop;
std::atomic_int idx;
int dims[3];
int x1, z1, x2, z2;
int scale;
uint64_t samples;
int locate;
struct Dat {
int x1, z1, x2, z2;
int scale;
int locate;
uint64_t samples;
} dat;
int minsize;
int tolerance;
};
@ -50,14 +53,14 @@ public:
explicit BiomeTableModel(QObject *parent = nullptr) :
QAbstractTableModel(parent), cmp(IdCmp::SORT_LEX, -1, DIM_UNDEF) {}
virtual int rowCount(const QModelIndex&) const override { return ids.size(); }
virtual int columnCount(const QModelIndex&) const override { return seeds.size(); }
virtual int rowCount(const QModelIndex&) const override { return seeds.size(); }
virtual int columnCount(const QModelIndex&) const override { return ids.size(); }
virtual QVariant data(const QModelIndex& index, int role) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
int insertId(int id);
int insertSeed(uint64_t seed);
void insertIds(QSet<int>& ids);
void insertSeeds(QList<uint64_t>& seeds);
void reset(int mc);
QList<int> ids;
@ -71,7 +74,7 @@ class BiomeSortProxy : public QSortFilterProxyModel
Q_OBJECT
public:
BiomeSortProxy(QObject *parent = nullptr) : QSortFilterProxyModel(parent),column(),order() {}
BiomeSortProxy(QObject *parent = nullptr) : QSortFilterProxyModel(parent),column(),order(Qt::AscendingOrder) {}
virtual bool lessThan(const QModelIndex& a, const QModelIndex& b) const override
{
@ -82,6 +85,8 @@ public:
virtual void sort(int column, Qt::SortOrder order) override
{
if (column >= columnCount())
return;
if (this->column == -1)
QSortFilterProxyModel::sort(-1, order);
else
@ -94,6 +99,22 @@ public:
Qt::SortOrder order;
};
class BiomeHeader : public QHeaderView
{
Q_OBJECT
public:
BiomeHeader(QWidget *parent = nullptr);
void onSectionPress(int section);
virtual bool event(QEvent *e) override;
virtual void paintSection(QPainter *painter, const QRect& rect, int section) const override;
virtual QSize sectionSizeFromContents(int section) const override;
int hover;
int pressed;
};
class TabBiomes : public QWidget, public ISaveTab
{
Q_OBJECT
@ -109,23 +130,21 @@ public:
void refreshBiomes(int activeid = -1);
private slots:
void onLocateHeaderClick();
void onTableSort(int column, Qt::SortOrder);
void onAnalysisSeedDone(uint64_t seed, QVector<uint64_t> idcnt);
void onAnalysisSeedItem(QTreeWidgetItem *item);
void onAnalysisFinished();
void onSort(int column, Qt::SortOrder);
void onBufferTimeout();
void on_pushStart_clicked();
void on_pushExport_clicked();
void on_table_doubleClicked(const QModelIndex &index);
void on_buttonFromVisible_clicked();
void on_radioFullSample_toggled(bool checked);
void on_lineBiomeSize_textChanged(const QString &arg1);
void on_treeWidget_itemClicked(QTreeWidgetItem *item, int column);
void on_treeLocate_itemClicked(QTreeWidgetItem *item, int column);
void on_tabWidget_currentChanged(int index);
private:
Ui::TabBiomes *ui;
@ -133,7 +152,15 @@ private:
AnalysisBiomes thread;
BiomeTableModel *model;
BiomeSortProxy *proxy;
std::map<QString, int> str2biome;
QMap<QString, int> str2biome;
AnalysisBiomes::Dat dats, datl;
int sortcol;
QElapsedTimer elapsed;
uint64_t updt;
uint64_t nextupdate;
QVector<QVector<uint64_t>> qbufs;
QList<QTreeWidgetItem*> qbufl;
};
#endif // TABBIOMES_H

View File

@ -238,7 +238,7 @@
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QTreeWidget" name="treeWidget">
<widget class="QTreeWidget" name="treeLocate">
<property name="font">
<font>
<family>Monospace</family>

View File

@ -34,10 +34,10 @@ void AnalysisStructures::run()
Generator g;
setupGenerator(&g, wi.mc, wi.large);
for (int64_t seed : qAsConst(seeds))
for (idx = 0; idx < seeds.size(); idx++)
{
if (stop) break;
wi.seed = seed;
wi.seed = seeds[idx];
if (quad)
runQuads(&g);
else
@ -71,7 +71,7 @@ void AnalysisStructures::runStructs(Generator *g)
sdim = DIM_NETHER;
if (sconf.properties & STRUCT_END)
sdim = DIM_END;
getStructs(&st, sconf, wi, sdim, x1, z1, x2, z2);
getStructs(&st, sconf, wi, sdim, area.x1, area.z1, area.x2, area.z2);
if (st.empty())
continue;
@ -102,7 +102,7 @@ void AnalysisStructures::runStructs(Generator *g)
{
applySeed(g, 0, wi.seed);
Pos pos = getSpawn(g);
if (pos.x >= x1 && pos.x <= x2 && pos.z >= z1 && pos.z <= z2)
if (pos.x >= area.x1 && pos.x <= area.x2 && pos.z >= area.z1 && pos.z <= area.z2)
{
QTreeWidgetItem* item = new QTreeWidgetItem(seeditem);
item->setText(C_SEED, "-");
@ -124,8 +124,8 @@ void AnalysisStructures::runStructs(Generator *g)
applySeed(g, DIM_OVERWORLD, wi.seed);
// get the maximum relevant ring number
int rx1 = abs(x1), rx2 = abs(x2);
int rz1 = abs(z1), rz2 = abs(z2);
int rx1 = abs(area.x1), rx2 = abs(area.x2);
int rz1 = abs(area.z1), rz2 = abs(area.z2);
int xt = (rx1 > rx2 ? rx1 : rx2) + 112+8;
int zt = (rz1 > rz2 ? rz1 : rz2) + 112+8;
int rmax = xt*xt + zt*zt;
@ -136,7 +136,7 @@ void AnalysisStructures::runStructs(Generator *g)
if (stop || sh.ringnum > rmax)
break;
Pos pos = sh.pos;
if (pos.x >= x1 && pos.x <= x2 && pos.z >= z1 && pos.z <= z2)
if (pos.x >= area.x1 && pos.x <= area.x2 && pos.z >= area.z1 && pos.z <= area.z2)
shp.push_back(pos);
}
@ -222,6 +222,10 @@ TabStructures::TabStructures(MainWindow *parent)
, ui(new Ui::TabStructures)
, parent(parent)
, thread(this)
, sortcols(-1)
, sortcolq(-1)
, nextupdate()
, updt(100)
{
ui->setupUi(this);
@ -229,10 +233,12 @@ TabStructures::TabStructures(MainWindow *parent)
ui->treeStructs->setColumnWidth(C_COUNT, 50);
ui->treeStructs->setColumnWidth(C_X, 65);
ui->treeStructs->setColumnWidth(C_Z, 65);
ui->treeStructs->sortByColumn(0, Qt::AscendingOrder);
ui->treeStructs->sortByColumn(-1, Qt::AscendingOrder);
connect(ui->treeStructs->header(), &QHeaderView::sectionClicked, this, [=](){ onHeaderClick(ui->treeStructs); } );
ui->treeQuads->setColumnWidth(0, 160);
ui->treeQuads->sortByColumn(0, Qt::AscendingOrder);
ui->treeQuads->sortByColumn(-1, Qt::AscendingOrder);
connect(ui->treeQuads->header(), &QHeaderView::sectionClicked, this, [=](){ onHeaderClick(ui->treeQuads); } );
connect(&thread, &AnalysisStructures::itemDone, this, &TabStructures::onAnalysisItemDone, Qt::BlockingQueuedConnection);
connect(&thread, &AnalysisStructures::quadDone, this, &TabStructures::onAnalysisQuadDone, Qt::BlockingQueuedConnection);
@ -244,6 +250,8 @@ TabStructures::TabStructures(MainWindow *parent)
TabStructures::~TabStructures()
{
thread.stop = true;
thread.wait(500);
delete ui;
}
@ -285,31 +293,85 @@ void TabStructures::load(QSettings& settings)
ui->radioAll->setChecked(true);
}
void TabStructures::onHeaderClick(QTreeView *tree)
{
int& col = (tree == ui->treeStructs) ? sortcols : sortcolq;
int section = tree->header()->sortIndicatorSection();
if (tree->header()->sortIndicatorOrder() == Qt::AscendingOrder && col == section)
{
tree->sortByColumn(-1, Qt::DescendingOrder);
section = -1;
}
col = section;
}
void TabStructures::onAnalysisItemDone(QTreeWidgetItem *item)
{
ui->treeStructs->addTopLevelItem(item);
ui->treeStructs->resizeColumnToContents(C_DETAIL);
QString progress = QString::asprintf(" (%d/%d)", ui->treeStructs->topLevelItemCount()+1, thread.seeds.size());
ui->pushStart->setText(tr("Stop") + progress);
qbufs.push_back(item);
quint64 ns = elapsed.nsecsElapsed();
if (ns > nextupdate)
{
nextupdate = ns + updt * 1e6;
QTimer::singleShot(updt, this, &TabStructures::onBufferTimeout);
}
}
void TabStructures::onAnalysisQuadDone(QTreeWidgetItem *item)
{
ui->treeQuads->addTopLevelItem(item);
item->setExpanded(true);
QString progress = QString::asprintf(" (%d/%d)", ui->treeQuads->topLevelItemCount()+1, thread.seeds.size());
ui->pushStart->setText(tr("Stop") + progress);
qbufq.push_back(item);
quint64 ns = elapsed.nsecsElapsed();
if (ns > nextupdate)
{
nextupdate = ns + updt * 1e6;
QTimer::singleShot(updt, this, &TabStructures::onBufferTimeout);
}
}
void TabStructures::onAnalysisFinished()
{
ui->pushExport->setEnabled(ui->treeStructs->topLevelItemCount() > 0);
onBufferTimeout();
on_tabWidget_currentChanged(-1);
ui->treeStructs->setSortingEnabled(true);
ui->treeQuads->setSortingEnabled(true);
ui->pushStart->setChecked(false);
ui->pushStart->setText(tr("Analyze"));
}
void TabStructures::onBufferTimeout()
{
if (qbufs.empty() && qbufq.empty())
return;
uint64_t t = -elapsed.elapsed();
if (!qbufs.empty())
{
ui->treeStructs->setSortingEnabled(false);
ui->treeStructs->setUpdatesEnabled(false);
ui->treeStructs->addTopLevelItems(qbufs);
ui->treeStructs->resizeColumnToContents(C_DETAIL);
ui->treeStructs->setUpdatesEnabled(true);
ui->treeStructs->setSortingEnabled(true);
qbufs.clear();
}
if (!qbufq.empty())
{
ui->treeQuads->setSortingEnabled(false);
ui->treeQuads->setUpdatesEnabled(false);
ui->treeQuads->addTopLevelItems(qbufq);
for (QTreeWidgetItem *item: qAsConst(qbufq))
item->setExpanded(true);
ui->treeQuads->setUpdatesEnabled(true);
ui->treeQuads->setSortingEnabled(true);
qbufq.clear();
}
QString progress = QString::asprintf(" (%d/%d)", thread.idx.load(), thread.seeds.size());
ui->pushStart->setText(tr("Stop") + progress);
t += elapsed.elapsed();
if (8*t > updt)
updt = 4*t;
nextupdate = elapsed.nsecsElapsed() + 1e6 * updt;
}
void TabStructures::onTreeItemClicked(QTreeWidgetItem *item, int column)
{
(void) column;
@ -340,6 +402,10 @@ void TabStructures::on_pushStart_clicked()
thread.stop = true;
return;
}
updt = 20;
nextupdate = 0;
elapsed.start();
parent->getSeed(&thread.wi);
thread.seeds.clear();
if (ui->comboSeedSource->currentIndex() == 0)
@ -353,10 +419,7 @@ void TabStructures::on_pushStart_clicked()
int z2 = ui->lineZ2->text().toInt();
if (x2 < x1) std::swap(x1, x2);
if (z2 < z1) std::swap(z1, z2);
thread.x1 = x1;
thread.z1 = z1;
thread.x2 = x2;
thread.z2 = z2;
thread.area = AnalysisStructures::Dat{x1, z1, x2, z2};
thread.collect = ui->checkCollect->isChecked();
@ -366,14 +429,20 @@ void TabStructures::on_pushStart_clicked()
if (ui->tabWidget->currentWidget() == ui->tabStructures)
{
thread.quad = false;
dats = thread.area;
ui->treeStructs->setSortingEnabled(false);
while (ui->treeStructs->topLevelItemCount() > 0)
delete ui->treeStructs->takeTopLevelItem(0);
ui->treeStructs->setSortingEnabled(true);
}
else
{
thread.quad = true;
while (ui->treeStructs->topLevelItemCount() > 0)
delete ui->treeStructs->takeTopLevelItem(0);
datq = thread.area;
ui->treeQuads->setSortingEnabled(false);
while (ui->treeQuads->topLevelItemCount() > 0)
delete ui->treeQuads->takeTopLevelItem(0);
ui->treeQuads->setSortingEnabled(true);
}
ui->pushExport->setEnabled(false);
@ -382,6 +451,18 @@ void TabStructures::on_pushStart_clicked()
thread.start();
}
static
void csvline(QTextStream& stream, const QString& qte, const QString& sep, QStringList& cols)
{
if (qte.isEmpty())
{
for (QString& s : cols)
if (s.contains(sep))
s = "\"" + s + "\"";
}
stream << qte << cols.join(sep) << qte << "\n";
}
void TabStructures::on_pushExport_clicked()
{
QString fnam = QFileDialog::getSaveFileName(
@ -399,74 +480,104 @@ void TabStructures::on_pushExport_clicked()
return;
}
QString qte = parent->config.quote;
QString sep = parent->config.separator;
QTextStream stream(&file);
stream << "Sep=" + sep + "\n";
sep = qte + sep + qte;
stream << "#X1; " << thread.x1 << "\n";
stream << "#Z1; " << thread.z1 << "\n";
stream << "#X2; " << thread.x2 << "\n";
stream << "#Z2; " << thread.z2 << "\n";
QTreeWidgetItemIterator it(ui->treeStructs);
if (ui->checkCollect->isChecked())
if (ui->tabWidget->currentWidget() == ui->tabStructures)
{
stream << "seed; structure; x; z; details\n";
QString seed;
QString structure;
for (; *it; ++it)
{
QTreeWidgetItem *item = *it;
if (item->text(C_SEED) != "-")
seed = item->text(C_SEED);
if (!item->text(C_STRUCT).isEmpty())
structure = item->text(C_STRUCT);
if (!item->data(0, Qt::UserRole+2).isValid())
continue;
stream << qte << "#X1" << sep << dats.x1 << qte << "\n";
stream << qte << "#Z1" << sep << dats.z1 << qte << "\n";
stream << qte << "#X2" << sep << dats.x2 << qte << "\n";
stream << qte << "#Z2" << sep << dats.z2 << qte << "\n";
QStringList cols;
cols.append(seed);
cols.append(structure);
cols.append(item->text(C_X));
cols.append(item->text(C_Z));
cols.append(item->text(C_DETAIL));
stream << cols.join(";") << "\n";
if (ui->checkCollect->isChecked())
{
QStringList header = { tr("seed"), tr("structure"), tr("x"), tr("z"), tr("details") };
csvline(stream, qte, sep, header);
QString seed;
QString structure;
for (QTreeWidgetItemIterator it(ui->treeStructs); *it; ++it)
{
QTreeWidgetItem *item = *it;
if (item->text(C_SEED) != "-")
seed = item->text(C_SEED);
if (!item->text(C_STRUCT).isEmpty())
structure = item->text(C_STRUCT);
if (!item->data(0, Qt::UserRole+2).isValid())
continue;
QStringList cols;
cols.append(seed);
cols.append(structure);
cols.append(item->text(C_X));
cols.append(item->text(C_Z));
cols.append(item->text(C_DETAIL));
csvline(stream, qte, sep, cols);
}
}
else
{
std::set<QString> structures;
std::map<uint64_t, std::map<QString, QString>> cnt; // [seed][stype]
uint64_t seed;
QString structure;
for (QTreeWidgetItemIterator it(ui->treeStructs); *it; ++it)
{
QTreeWidgetItem *item = *it;
if (item->data(0, Qt::UserRole).isValid())
seed = item->data(0, Qt::UserRole).toLongLong();
if (!item->text(C_STRUCT).isEmpty())
structures.insert((structure = item->text(C_STRUCT)));
if (!item->text(C_COUNT).isEmpty())
cnt[seed][structure] = item->text(C_COUNT);
}
QStringList header = { tr("seed") };
for (auto& sit : structures)
header.append(sit);
csvline(stream, qte, sep, header);
for (auto& m : cnt)
{
QStringList cols;
cols << QString::asprintf("%" PRId64, m.first);
for (auto& sit : structures)
{
QString cntstr = m.second[sit];
if (cntstr.isEmpty())
cntstr = "0";
cols.append(cntstr);
}
csvline(stream, qte, sep, cols);
}
}
}
else
else if(ui->tabWidget->currentWidget() == ui->tabQuads)
{
std::set<QString> structures;
std::map<uint64_t, std::map<QString, QString>> cnt; // [seed][stype]/[row][col]
stream << qte << "#X1" << sep << datq.x1 << qte << "\n";
stream << qte << "#Z1" << sep << datq.z1 << qte << "\n";
stream << qte << "#X2" << sep << datq.x2 << qte << "\n";
stream << qte << "#Z2" << sep << datq.z2 << qte << "\n";
uint64_t seed;
QString structure;
for (; *it; ++it)
QStringList header = { tr("seed"), tr("type"), tr("distance"), tr("x"), tr("z"), tr("radius"), tr("spawn area") };
csvline(stream, qte, sep, header);
QString seed;
for (QTreeWidgetItemIterator it(ui->treeQuads); *it; ++it)
{
QTreeWidgetItem *item = *it;
if (item->data(0, Qt::UserRole).isValid())
seed = item->data(0, Qt::UserRole).toLongLong();
if (!item->text(C_STRUCT).isEmpty())
structures.insert((structure = item->text(C_STRUCT)));
if (!item->text(C_COUNT).isEmpty())
cnt[seed][structure] = item->text(C_COUNT);
}
QStringList header;
header.append("seed");
for (auto& sit : structures)
header.append(sit);
stream << header.join(";") << "\n";
for (auto& m : cnt)
{
QStringList cols;
cols << QString::asprintf("%" PRId64, m.first);
for (auto& sit : structures)
if (item->text(0) != "-")
{
QString cntstr = m.second[sit];
if (cntstr.isEmpty())
cntstr = "0";
cols.append(cntstr);
seed = item->text(0);
continue;
}
stream << cols.join(";") << "\n";
QStringList cols = { seed };
for (int i = 1, n = item->columnCount(); i < n; i++)
cols.append(item->text(i));
csvline(stream, qte, sep, cols);
}
}
}
@ -486,3 +597,16 @@ void TabStructures::on_buttonFromVisible_clicked()
ui->lineX2->setText( QString::number(bx1) );
ui->lineZ2->setText( QString::number(bz1) );
}
void TabStructures::on_tabWidget_currentChanged(int)
{
bool ok = false;
if (!thread.isRunning())
{
if (ui->tabWidget->currentWidget() == ui->tabStructures)
ok = ui->treeStructs->topLevelItemCount() > 0;
if (ui->tabWidget->currentWidget() == ui->tabQuads)
ok = ui->treeQuads->topLevelItemCount() > 0;
}
ui->pushExport->setEnabled(ok);
}

View File

@ -13,7 +13,7 @@ class AnalysisStructures : public QThread
Q_OBJECT
public:
explicit AnalysisStructures(QObject *parent = nullptr)
: QThread(parent) {}
: QThread(parent),idx() {}
virtual void run() override;
void runStructs(Generator *g);
@ -27,7 +27,8 @@ public:
QVector<uint64_t> seeds;
WorldInfo wi;
std::atomic_bool stop;
int x1, z1, x2, z2;
std::atomic_int idx;
struct Dat { int x1, z1, x2, z2; } area;
bool mapshow[STRUCT_NUM];
bool collect;
bool quad;
@ -45,23 +46,33 @@ public:
virtual void load(QSettings& settings) override;
private slots:
void onHeaderClick(QTreeView *tree);
void onAnalysisItemDone(QTreeWidgetItem *item);
void onAnalysisQuadDone(QTreeWidgetItem *item);
void onAnalysisFinished();
void onBufferTimeout();
void onTreeItemClicked(QTreeWidgetItem *item, int column);
void on_pushStart_clicked();
void on_pushExport_clicked();
void on_buttonFromVisible_clicked();
void on_tabWidget_currentChanged(int index);
private:
Ui::TabStructures *ui;
MainWindow *parent;
AnalysisStructures thread;
AnalysisStructures::Dat dats, datq;
int sortcols, sortcolq;
QElapsedTimer elapsed;
uint64_t nextupdate;
uint64_t updt;
QList<QTreeWidgetItem*> qbufs;
QList<QTreeWidgetItem*> qbufq;
};
#endif // TABSTRUCTURES_H

View File

@ -54,9 +54,10 @@ void AnalysisTriggers::run()
{
stop = false;
for (int64_t seed : qAsConst(seeds))
for (idx = 0; idx < seeds.size(); idx++)
{
if (stop) break;
int64_t seed = seeds[idx];
wi.seed = seed;
QTreeWidgetItem *seeditem = new QTreeWidgetItem();
seeditem->setText(0, QString::asprintf("%" PRId64, seed));
@ -97,19 +98,23 @@ TabTriggers::TabTriggers(MainWindow *parent)
, ui(new Ui::TabTriggers)
, parent(parent)
, thread()
, nextupdate()
, updt(20)
{
ui->setupUi(this);
ui->treeWidget->setColumnWidth(1, 280);
ui->treeWidget->setColumnWidth(2, 65);
ui->treeWidget->setColumnWidth(3, 65);
ui->treeWidget->setSortingEnabled(false); // sortable triggers are not necessary
ui->treeWidget->sortByColumn(0, Qt::AscendingOrder);
connect(&thread, &AnalysisTriggers::itemDone, this, &TabTriggers::onAnalysisItemDone, Qt::BlockingQueuedConnection);
connect(&thread, &AnalysisTriggers::finished, this, &TabTriggers::onAnalysisFinished);
}
TabTriggers::~TabTriggers()
{
thread.stop = true;
thread.wait(500);
delete ui;
}
@ -126,18 +131,45 @@ void TabTriggers::load(QSettings& settings)
void TabTriggers::onAnalysisItemDone(QTreeWidgetItem *item)
{
ui->treeWidget->addTopLevelItem(item);
QString progress = QString::asprintf(" (%d/%d)", ui->treeWidget->topLevelItemCount()+1, thread.seeds.size());
ui->pushStart->setText(tr("Stop") + progress);
qbuf.push_back(item);
quint64 ns = elapsed.nsecsElapsed();
if (ns > nextupdate)
{
nextupdate = ns + updt * 1e6;
QTimer::singleShot(updt, this, &TabTriggers::onBufferTimeout);
}
}
void TabTriggers::onAnalysisFinished()
{
onBufferTimeout();
ui->pushExport->setEnabled(ui->treeWidget->topLevelItemCount() > 0);
ui->pushStart->setChecked(false);
ui->pushStart->setText(tr("Analyze"));
}
void TabTriggers::onBufferTimeout()
{
uint64_t t = -elapsed.elapsed();
if (!qbuf.empty())
{
ui->treeWidget->setUpdatesEnabled(false);
ui->treeWidget->addTopLevelItems(qbuf);
ui->treeWidget->setUpdatesEnabled(true);
QString progress = QString::asprintf(" (%d/%d)", thread.idx.load(), thread.seeds.size());
ui->pushStart->setText(tr("Stop") + progress);
qbuf.clear();
}
t += elapsed.elapsed();
if (8*t > updt)
updt = 4*t;
nextupdate = elapsed.nsecsElapsed() + 1e6 * updt;
}
void TabTriggers::on_pushStart_clicked()
{
if (thread.isRunning())
@ -145,6 +177,10 @@ void TabTriggers::on_pushStart_clicked()
thread.stop = true;
return;
}
updt = 20;
nextupdate = 0;
elapsed.start();
parent->getSeed(&thread.wi);
thread.conds = parent->formCond->getConditions();
thread.seeds.clear();
@ -153,6 +189,7 @@ void TabTriggers::on_pushStart_clicked()
else
thread.seeds = parent->formControl->getResults();
//ui->treeWidget->setSortingEnabled(false);
while (ui->treeWidget->topLevelItemCount() > 0)
delete ui->treeWidget->takeTopLevelItem(0);
@ -191,6 +228,18 @@ void TabTriggers::on_pushExpand_clicked()
ui->treeWidget->expandAll();
}
static
void csvline(QTextStream& stream, const QString& qte, const QString& sep, QStringList& cols)
{
if (qte.isEmpty())
{
for (QString& s : cols)
if (s.contains(sep))
s = "\"" + s + "\"";
}
stream << qte << cols.join(sep) << qte << "\n";
}
void TabTriggers::on_pushExport_clicked()
{
QString fnam = QFileDialog::getSaveFileName(
@ -208,8 +257,15 @@ void TabTriggers::on_pushExport_clicked()
return;
}
QString qte = parent->config.quote;
QString sep = parent->config.separator;
QTextStream stream(&file);
stream << "#seed; condition; x; z\n";
stream << "Sep=" + sep + "\n";
sep = qte + sep + qte;
QStringList header = { tr("seed"), tr("condition"), tr("x"), tr("z") };
csvline(stream, qte, sep, header);
QTreeWidgetItemIterator it(ui->treeWidget);
for (; *it; ++it)
@ -218,12 +274,11 @@ void TabTriggers::on_pushExport_clicked()
QStringList cols;
for (int i = 0, n = item->columnCount(); i < n; i++)
{
QString txt = item->text(i);
if (txt == "-") txt = "";
if (i == 1) txt = "\"" + txt + "\"";
cols << txt;
QString s = item->text(i);
if (s == "-") s = "";
cols.append(s);
}
stream << cols.join("; ") << "\n";
csvline(stream, qte, sep, cols);
}
}

View File

@ -18,7 +18,7 @@ class AnalysisTriggers : public QThread
Q_OBJECT
public:
explicit AnalysisTriggers(QObject *parent = nullptr)
: QThread(parent) {}
: QThread(parent), conds(),seeds(),wi(),stop(),idx() {}
virtual void run() override;
@ -30,6 +30,7 @@ public:
QVector<uint64_t> seeds;
WorldInfo wi;
std::atomic_bool stop;
std::atomic_int idx;
};
@ -47,17 +48,22 @@ public:
private slots:
void onAnalysisItemDone(QTreeWidgetItem *item);
void onAnalysisFinished();
void onBufferTimeout();
void on_pushStart_clicked();
void on_pushExpand_clicked();
void on_pushExport_clicked();
void on_treeWidget_itemClicked(QTreeWidgetItem *item, int column);
private:
Ui::TabTriggers *ui;
MainWindow *parent;
AnalysisTriggers thread;
QElapsedTimer elapsed;
uint64_t nextupdate;
uint64_t updt;
QList<QTreeWidgetItem*> qbuf;
};
#endif // TABTRIGGERS_H

View File

@ -86,16 +86,16 @@ const QPixmap& getMapIcon(int opt, VarPos *vp)
return icons[opt];
}
QIcon getBiomeIcon(int id)
QIcon getBiomeIcon(int id, bool warn)
{
static QPixmap pixmap(14, 14);
pixmap.fill(QColor(0,0,0,0));
QPainter p(&pixmap);
p.setRenderHint(QPainter::Antialiasing);
QPainterPath path;
path.addRoundedRect(QRectF(1, 1, 12, 12), 3, 3);
QPen pen(Qt::black, 1);
p.setPen(pen);
int b = warn ? 2 : 1;
path.addRoundedRect(pixmap.rect().adjusted(b, b, -b, -b), 3, 3);
p.setPen(QPen(warn ? Qt::red : Qt::black, b));
QColor col(g_biomeColors[id][0], g_biomeColors[id][1], g_biomeColors[id][2]);
p.fillPath(path, col);
p.drawPath(path);
@ -561,7 +561,7 @@ QWorld::QWorld(WorldInfo wi, int dim, int layeropt)
, selx()
, selz()
, selopt(-1)
, selvp(Pos{}, -1)
, selvp(Pos{0,0}, -1)
, qual()
{
setupGenerator(&g, wi.mc, wi.large);

View File

@ -165,7 +165,7 @@ struct VarPos
};
const QPixmap& getMapIcon(int opt, VarPos *variation = 0);
QIcon getBiomeIcon(int id);
QIcon getBiomeIcon(int id, bool warn = false);
void getStructs(std::vector<VarPos> *out, const StructureConfig sconf,
WorldInfo wi, int dim, int x0, int z0, int x1, int z1);