#include "SplitterContent.h" #include #include #include #include #include #include #include #include #include "BeltSystem.h" #include "BuildingTarget.h" #include "ClearBeltControl.h" #include "Command.h" #include "CommandRequestedEvent.h" #include "EventManager.h" #include "FactoryQueries.h" #include "GameConfig.h" #include "ItemType.h" #include "Rotation.h" #include "SelectionNames.h" #include "Simulation.h" namespace { // Height cap on a filter list, so two of them plus the rest of the card still fit. const int kFilterListHeightPx = 100; QString getRotationLabel(Rotation rotation) { // Written as code points because the sources are read as ASCII by the compiler. const QChar upArrow(0x2191); // U+2191 UPWARDS ARROW const QChar rightArrow(0x2192); // U+2192 RIGHTWARDS ARROW const QChar downArrow(0x2193); // U+2193 DOWNWARDS ARROW const QChar leftArrow(0x2190); // U+2190 LEFTWARDS ARROW switch (rotation) { case Rotation::North: return QObject::tr("North (%1)").arg(upArrow); case Rotation::East: return QObject::tr("East (%1)").arg(rightArrow); case Rotation::South: return QObject::tr("South (%1)").arg(downArrow); case Rotation::West: return QObject::tr("West (%1)").arg(leftArrow); } return QString(); } // Every item type the economy knows, from both sides of every recipe. std::vector getAllItemIds(const RecipesConfig& recipes) { std::set seen; for (const RecipeDef& recipe : recipes.recipes) { for (const RecipeIngredient& ingredient : recipe.inputs) { seen.insert(ingredient.item); } for (const RecipeOutput& output : recipe.outputs) { seen.insert(output.item); } } return std::vector(seen.begin(), seen.end()); } std::vector collectCheckedItems(const QListWidget* list) { std::vector filter; for (int row = 0; row < list->count(); ++row) { const QListWidgetItem* item = list->item(row); if (item->checkState() == Qt::Checked) { filter.push_back(ItemType{ item->text().toStdString() }); } } return filter; } } // namespace SplitterContent::SplitterContent(const SelectionContext& context, const SelectionRequest& request, QWidget* parent) : SelectionContent(context, asConstructionSite(context, request.buildings.front()), parent) , m_id(request.buildings.front()) , m_isSite(asConstructionSite(context, m_id).has_value()) , m_tile(0, 0) { m_filterALabel = new QLabel(this); m_filterAList = new QListWidget(this); m_filterBLabel = new QLabel(this); m_filterBList = new QListWidget(this); m_filterAList->setMaximumHeight(kFilterListHeightPx); m_filterBList->setMaximumHeight(kFilterListHeightPx); getConfigurationLayout()->addWidget(m_filterALabel); getConfigurationLayout()->addWidget(m_filterAList); getConfigurationLayout()->addWidget(m_filterBLabel); getConfigurationLayout()->addWidget(m_filterBList); getRuntimeLayout()->addWidget( new ClearBeltControl(context, request.buildings, this)); // Populated once, here, rather than on every refresh: re-checking the boxes at 30 Hz // would fight the player's clicks. populateFilters(); connect(m_filterAList, &QListWidget::itemChanged, this, [this]() { applyFilters(); }); connect(m_filterBList, &QListWidget::itemChanged, this, [this]() { applyFilters(); }); } void SplitterContent::refreshConfiguration() { const BuildingTarget target = resolveBuildingTarget(getContext(), m_id); if (!target.isValid()) { return; } setBuildingIdentity(target.type, getBuildingTypeName(target.type)); } void SplitterContent::populateFilters() { const BuildingTarget target = resolveBuildingTarget(getContext(), m_id); if (!target.isValid()) { return; } // An operational splitter's outputs and filters live in the belt subsystem, keyed by // tile; a site's are stored on the site itself (REQ-BLD-SITE-CONFIG). std::optional info; if (m_isSite) { info = getSiteSplitterInfo(getContext().sim->getFactoryState(), *getContext().config, m_id); } else { m_tile = target.anchor; info = getContext().sim->getBelts().getSplitterInfo(m_tile); } if (!info.has_value()) { m_filterALabel->hide(); m_filterAList->hide(); m_filterBLabel->hide(); m_filterBList->hide(); return; } const std::vector itemIds = getAllItemIds(getContext().config->recipes); auto fillList = [&](QListWidget* list, QLabel* label, const QString& directionLabel, const std::vector& filter) { label->setText(tr("%1 filter (empty = all):").arg(directionLabel)); list->blockSignals(true); list->clear(); for (const std::string& itemId : itemIds) { // Only implicitly unlocked item types are offered (REQ-LOCK-UI-SPLITTER). if (!getContext().sim->isItemUnlocked(itemId)) { continue; } QListWidgetItem* row = new QListWidgetItem(QString::fromStdString(itemId), list); const bool checked = !filter.empty() && std::find(filter.begin(), filter.end(), ItemType{ itemId }) != filter.end(); row->setCheckState(checked ? Qt::Checked : Qt::Unchecked); row->setFlags(row->flags() | Qt::ItemIsUserCheckable); } list->blockSignals(false); label->show(); list->show(); }; fillList(m_filterAList, m_filterALabel, getRotationLabel(info->outputA), info->filterA); fillList(m_filterBList, m_filterBLabel, getRotationLabel(info->outputB), info->filterB); } void SplitterContent::applyFilters() { if (m_isSite) { std::shared_ptr command = std::make_shared(); command->id = m_id; command->filterA = collectCheckedItems(m_filterAList); command->filterB = collectCheckedItems(m_filterBList); EventManager::getInstance()->sendEventImmediately( std::make_shared(command)); return; } std::shared_ptr command = std::make_shared(); command->tile = m_tile; command->filterA = collectCheckedItems(m_filterAList); command->filterB = collectCheckedItems(m_filterBList); EventManager::getInstance()->sendEventImmediately( std::make_shared(command)); }