Path: blob/main/src/netedit/frames/GNEMatchAttribute.cpp
169678 views
/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.3// This program and the accompanying materials are made available under the4// terms of the Eclipse Public License 2.0 which is available at5// https://www.eclipse.org/legal/epl-2.0/6// This Source Code may also be made available under the following Secondary7// Licenses when the conditions for such availability set forth in the Eclipse8// Public License 2.0 are satisfied: GNU General Public License, version 29// or later which is available at10// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html11// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later12/****************************************************************************/13/// @file GNEMatchAttribute.cpp14/// @author Pablo Alvarez Lopez15/// @date Feb 202016///17// The Widget for modifying selections of network-elements18/****************************************************************************/1920#include <netedit/frames/common/GNESelectorFrame.h>21#include <netedit/dialogs/basic/GNEHelpBasicDialog.h>22#include <netedit/GNENet.h>23#include <netedit/GNEViewNet.h>24#include <netedit/GNEViewParent.h>25#include <netedit/GNEApplicationWindow.h>26#include <netedit/GNETagProperties.h>27#include <netedit/GNEAttributeProperties.h>28#include <netedit/GNETagPropertiesDatabase.h>29#include <utils/foxtools/MFXComboBoxAttrProperty.h>30#include <utils/foxtools/MFXComboBoxTagProperty.h>31#include <utils/gui/div/GUIDesigns.h>32#include <utils/gui/windows/GUIAppEnum.h>3334#include "GNEMatchAttribute.h"3536// ===========================================================================37// FOX callback mapping38// ===========================================================================3940FXDEFMAP(GNEMatchAttribute) GNEMatchAttributeMap[] = {41FXMAPFUNC(SEL_COMMAND, MID_GNE_SELECTORFRAME_SELECTTAG, GNEMatchAttribute::onCmdTagSelected),42FXMAPFUNC(SEL_COMMAND, MID_GNE_SELECTORFRAME_SELECTATTRIBUTE, GNEMatchAttribute::onCmdAttributeSelected),43FXMAPFUNC(SEL_COMMAND, MID_GNE_SELECTORFRAME_TOGGLECOMMON, GNEMatchAttribute::onCmdToogleOnlyCommon),44FXMAPFUNC(SEL_COMMAND, MID_GNE_SELECTORFRAME_PROCESSSTRING, GNEMatchAttribute::onCmdProcessString),45FXMAPFUNC(SEL_COMMAND, MID_HELP, GNEMatchAttribute::onCmdHelp)46};4748// Object implementation49FXIMPLEMENT(GNEMatchAttribute, MFXGroupBoxModule, GNEMatchAttributeMap, ARRAYNUMBER(GNEMatchAttributeMap))5051// ===========================================================================52// method definitions53// ===========================================================================5455// ---------------------------------------------------------------------------56// GNEMatchAttribute - methods57// ---------------------------------------------------------------------------58#ifdef _MSC_VER59#pragma warning(push)60#pragma warning(disable: 4355) // mask warning about "this" in initializers61#endif62GNEMatchAttribute::GNEMatchAttribute(GNESelectorFrame* selectorFrameParent) :63MFXGroupBoxModule(selectorFrameParent, TL("Match Attribute")),64mySelectorFrameParent(selectorFrameParent),65myCurrentEditedProperties(new CurrentEditedProperties(this)) {66const auto staticTooltipMenu = selectorFrameParent->getViewNet()->getViewParent()->getGNEAppWindows()->getStaticTooltipMenu();67// Create MFXComboBoxIcons (sum 1 due children)68for (int i = 0; i < selectorFrameParent->getViewNet()->getNet()->getTagPropertiesDatabase()->getHierarchyDepth() + 1; i++) {69auto comboBoxIcon = new MFXComboBoxTagProperty(getCollapsableFrame(), staticTooltipMenu, true, GUIDesignComboBoxVisibleItems,70this, MID_GNE_SELECTORFRAME_SELECTTAG, GUIDesignComboBox);71myTagComboBoxVector.push_back(comboBoxIcon);72}73myShowOnlyCommonAttributes = new FXCheckButton(getCollapsableFrame(), TL("Only common"), this, MID_GNE_SELECTORFRAME_TOGGLECOMMON, GUIDesignCheckButton);74myShowOnlyCommonAttributes->setCheck(FALSE);75// Create MFXComboBoxIcon for Attributes76myAttributeComboBox = new MFXComboBoxAttrProperty(getCollapsableFrame(), staticTooltipMenu, true, GUIDesignComboBoxVisibleItems,77this, MID_GNE_SELECTORFRAME_SELECTATTRIBUTE, GUIDesignComboBox);78// Create TextField for Match string79myMatchString = new FXTextField(getCollapsableFrame(), GUIDesignTextFieldNCol, this, MID_GNE_SELECTORFRAME_PROCESSSTRING, GUIDesignTextField);80// create button81myMatchStringButton = GUIDesigns::buildFXButton(getCollapsableFrame(), TL("Apply selection"), "", "", nullptr, this, MID_GNE_SELECTORFRAME_PROCESSSTRING, GUIDesignButton);82// Create help button83GUIDesigns::buildFXButton(getCollapsableFrame(), TL("Help"), "", "", nullptr, this, MID_HELP, GUIDesignButtonRectangular);84// refresh with the current tag and attr85refreshMatchAttribute();86}87#ifdef _MSC_VER88#pragma warning(pop)89#endif9091GNEMatchAttribute::~GNEMatchAttribute() {92delete myCurrentEditedProperties;93}949596void97GNEMatchAttribute::enableMatchAttribute() {98for (const auto& tagComboBox : myTagComboBoxVector) {99tagComboBox->enable();100}101myAttributeComboBox->enable();102myMatchString->enable();103myMatchStringButton->enable();104}105106107void108GNEMatchAttribute::disableMatchAttribute() {109for (const auto& tagComboBox : myTagComboBoxVector) {110tagComboBox->disable();111tagComboBox->setTextColor(GUIDesignTextColorBlack);112}113myAttributeComboBox->disable();114myMatchString->disable();115myMatchStringButton->disable();116// change colors to black (even if there are invalid values)117myAttributeComboBox->setTextColor(GUIDesignTextColorBlack);118myMatchString->setTextColor(GUIDesignTextColorBlack);119}120121122void123GNEMatchAttribute::showMatchAttribute() {124// refresh before show125refreshMatchAttribute();126// show groupbox127show();128}129130131void132GNEMatchAttribute::hideMatchAttribute() {133// hide groupbox134hide();135}136137138void139GNEMatchAttribute::refreshMatchAttribute() {140// continue depending of current141auto parentHierarchy = myCurrentEditedProperties->getTagProperties()->getHierarchicalParentsRecuersively();142// fill hierarchy143for (size_t i = 0; i < parentHierarchy.size(); i++) {144auto comboBox = myTagComboBoxVector.at(i);145// clear previous elements146comboBox->clearItems();147// add <all> always as first element148myTagComboBoxVector.at(i)->appendTagItem(myCurrentEditedProperties->getTagPropertiesAll());149// add siblings (except for root)150if (parentHierarchy.at(i)->getHierarchicalParent()) {151for (const auto tagSibling : parentHierarchy.at(i)->getHierarchicalParent()->getHierarchicalChildren()) {152if (tagSibling->isDrawable()) {153myTagComboBoxVector.at(i)->appendTagItem(tagSibling);154}155}156// update tag157if (myTagComboBoxVector.at(i)->hasTagProperty(parentHierarchy.at(i))) {158myTagComboBoxVector.at(i)->setCurrentItem(parentHierarchy.at(i), FALSE);159myTagComboBoxVector.at(i)->show();160} else {161myTagComboBoxVector.at(i)->hide();162}163}164}165// hide the two first combo boxes(root and supermode)166myTagComboBoxVector.at(0)->hide();167myTagComboBoxVector.at(1)->hide();168// hide comboBox with only one element (+ <all>)169if (myTagComboBoxVector.at(parentHierarchy.size() - 1)->getNumItems() == 2) {170myTagComboBoxVector.at(parentHierarchy.size() - 1)->hide();171}172// check if show children173auto comboBoxChildren = myTagComboBoxVector.at(parentHierarchy.size());174if (parentHierarchy.back()->getHierarchicalChildren().size() > 0) {175// clear previous elements176comboBoxChildren->clearItems();177// add <all> always as first element178comboBoxChildren->appendTagItem(myCurrentEditedProperties->getTagPropertiesAll());179for (const auto childTagProperty : parentHierarchy.back()->getHierarchicalChildren()) {180comboBoxChildren->appendTagItem(childTagProperty);181}182comboBoxChildren->show();183} else {184comboBoxChildren->hide();185}186// hide rest of combo boxes187for (size_t i = (parentHierarchy.size() + 1); i < myTagComboBoxVector.size(); i++) {188myTagComboBoxVector.at(i)->hide();189}190// now fill attributes191myAttributeComboBox->clearItems();192// get all children recursivelly193const auto attributes = myCurrentEditedProperties->getTagProperties()->getHierarchicalChildrenAttributesRecursively(myShowOnlyCommonAttributes->getCheck() == TRUE, true);194for (const auto& attribute : attributes) {195myAttributeComboBox->appendAttrItem(attribute.second);196}197// update tag198if (myAttributeComboBox->getNumItems() == 0) {199myAttributeComboBox->appendAttrItem(myCurrentEditedProperties->getAttributePropertiesNoCommon());200myAttributeComboBox->disable();201// set match string202myMatchString->setText("", FALSE);203myMatchString->disable();204} else {205myAttributeComboBox->enable();206if (myAttributeComboBox->hasAttrProperty(myCurrentEditedProperties->getAttributeProperties())) {207myAttributeComboBox->setCurrentItem(myCurrentEditedProperties->getAttributeProperties(), FALSE);208} else {209myAttributeComboBox->setCurrentItem(attributes.begin()->second, FALSE);210myCurrentEditedProperties->setAttributeProperties(myAttributeComboBox->getCurrentAttrProperty());211}212// set match string213myMatchString->setText(myCurrentEditedProperties->getMatchValue().c_str(), FALSE);214myMatchString->enable();215}216}217218219long220GNEMatchAttribute::onCmdTagSelected(FXObject* obj, FXSelector, void*) {221// iterate over all comboBoxes222int tagComboBoxIndex = 0;223for (int i = 0; i < (int)myTagComboBoxVector.size(); i++) {224if (myTagComboBoxVector.at(i) == obj) {225tagComboBoxIndex = i;226}227}228// check if tag property exist229if (myTagComboBoxVector.at(tagComboBoxIndex)->getCurrentTagProperty()) {230const auto selectedTag = myTagComboBoxVector.at(tagComboBoxIndex)->getCurrentTagProperty();231// if we select <all>, use parent tag232if (selectedTag == myCurrentEditedProperties->getTagPropertiesAll()) {233myCurrentEditedProperties->setTagProperties(myTagComboBoxVector.at(tagComboBoxIndex - 1)->getCurrentTagProperty());234} else {235myCurrentEditedProperties->setTagProperties(selectedTag);236}237refreshMatchAttribute();238}239return 0;240}241242243long244GNEMatchAttribute::onCmdAttributeSelected(FXObject*, FXSelector, void*) {245myCurrentEditedProperties->setAttributeProperties(myAttributeComboBox->getCurrentAttrProperty());246refreshMatchAttribute();247return 1;248}249250251long252GNEMatchAttribute::onCmdToogleOnlyCommon(FXObject*, FXSelector, void*) {253// simply refresh attribute254refreshMatchAttribute();255return 1;256}257258259long260GNEMatchAttribute::onCmdProcessString(FXObject*, FXSelector, void*) {261std::vector<GNEAttributeCarrier*> matches;262// obtain expresion263std::string expr = myMatchString->getText().text();264bool valid = true;265if (expr == "") {266// the empty expression matches all objects267matches = getMatches('@', 0, expr);268} else if (myCurrentEditedProperties->getAttributeProperties()->isNumerical()) {269// The expression must have the form270// <val matches if attr < val271// >val matches if attr > val272// =val matches if attr = val273// val matches if attr = val274char compOp = expr[0];275if (compOp == '<' || compOp == '>' || compOp == '=') {276expr = expr.substr(1);277} else {278compOp = '=';279}280// check if value can be parsed to double281if (GNEAttributeCarrier::canParse<double>(expr.c_str())) {282matches = getMatches(compOp, GNEAttributeCarrier::parse<double>(expr.c_str()), expr);283} else {284valid = false;285}286} else {287// The expression must have the form288// =str: matches if <str> is an exact match289// !str: matches if <str> is not a substring290// ^str: matches if <str> is not an exact match291// str: matches if <str> is a substring (sends compOp '@')292// Alternatively, if the expression is empty it matches all objects293char compOp = expr[0];294if (compOp == '=' || compOp == '!' || compOp == '^') {295expr = expr.substr(1);296} else {297compOp = '@';298}299matches = getMatches(compOp, 0, expr);300}301if (valid) {302mySelectorFrameParent->handleIDs(matches);303myMatchString->setTextColor(GUIDesignTextColorBlack);304myMatchString->killFocus();305} else {306myMatchString->setTextColor(GUIDesignTextColorRed);307}308return 1;309310}311312313long314GNEMatchAttribute::onCmdHelp(FXObject*, FXSelector, void*) {315// set help text316std::ostringstream help;317help318<< TL("- The 'Match Attribute' controls allow to specify a set of objects which are then applied to the current selection") << "\n"319<< TL(" according to the current 'Modification Mode'.") << "\n"320<< TL(" 1. Select an object type from the first input box") << "\n"321<< TL(" 2. Select an attribute from the second input box") << "\n"322<< TL(" 3. Enter a 'match expression' in the third input box and press <return>") << "\n"323<< "\n"324<< TL("- The empty expression matches all objects") << "\n"325<< TL("- For numerical attributes the match expression must consist of a comparison operator ('<', '>', '=') and a number.") << "\n"326<< TL("- An object matches if the comparison between its attribute and the given number by the given operator evaluates to 'true'") << "\n"327<< "\n"328<< TL("- For string attributes the match expression must consist of a comparison operator ('', '=', '!', '^') and a string.") << "\n"329<< TL(" '' (no operator) matches if string is a substring of that object's attribute.") << "\n"330<< TL(" '=' matches if string is an exact match.") << "\n"331<< TL(" '!' matches if string is not a substring.") << "\n"332<< TL(" '^' matches if string is not an exact match.") << "\n"333<< "\n"334<< TL("- Examples:")335<< TL(" junction; id; 'foo' -> match all junctions that have 'foo' in their id") << "\n"336<< TL(" junction; type; '=priority' -> match all junctions of type 'priority', but not of type 'priority_stop'") << "\n"337<< TL(" edge; speed; '>10' -> match all edges with a speed above 10");338// open help dialog339GNEHelpBasicDialog(mySelectorFrameParent->getViewNet()->getViewParent()->getGNEAppWindows(),340TL("Netedit parameters Help"), help);341return 1;342}343344345std::vector<GNEAttributeCarrier*>346GNEMatchAttribute::getMatches(const char compOp, const double val, const std::string& expr) {347std::vector<GNEAttributeCarrier*> result;348// first retrieve all ACs using ACTag349const auto allACbyTag = mySelectorFrameParent->getViewNet()->getNet()->getAttributeCarriers()->retrieveAttributeCarriers(myCurrentEditedProperties->getTagProperties()->getTag());350// iterate over all ACs351for (const auto& AC : allACbyTag) {352// first check if the attribute exist in the given tag353if (AC->getTagProperty()->hasAttribute(myCurrentEditedProperties->getAttributeProperties()->getAttr())) {354if (expr == "" && compOp == '@') {355result.push_back(AC);356} else if (myCurrentEditedProperties->getAttributeProperties()->isNumerical()) {357double acVal;358std::istringstream buf(AC->getAttribute(myCurrentEditedProperties->getAttributeProperties()->getAttr()));359buf >> acVal;360switch (compOp) {361case '<':362if (acVal < val) {363result.push_back(AC);364}365break;366case '>':367if (acVal > val) {368result.push_back(AC);369}370break;371case '=':372if (acVal == val) {373result.push_back(AC);374}375break;376}377} else {378// string match379std::string acVal = AC->getAttributeForSelection(myCurrentEditedProperties->getAttributeProperties()->getAttr());380switch (compOp) {381case '@':382if (acVal.find(expr) != std::string::npos) {383result.push_back(AC);384}385break;386case '!':387if (acVal.find(expr) == std::string::npos) {388result.push_back(AC);389}390break;391case '=':392if (acVal == expr) {393result.push_back(AC);394}395break;396case '^':397if (acVal != expr) {398result.push_back(AC);399}400break;401}402}403}404}405return result;406}407408409std::vector<GNEAttributeCarrier*>410GNEMatchAttribute::getGenericMatches(const std::vector<GNEGenericData*>& genericDatas, const std::string& attr, const char compOp, const double val, const std::string& expr) {411std::vector<GNEAttributeCarrier*> result;412// iterate over generic datas413for (const auto& genericData : genericDatas) {414if (expr == "" && compOp == '@') {415result.push_back(genericData);416} else if (attr != toString(GNE_ATTR_PARENT)) {417double acVal;418std::istringstream buf(genericData->getParameter(attr, "0"));419buf >> acVal;420switch (compOp) {421case '<':422if (acVal < val) {423result.push_back(genericData);424}425break;426case '>':427if (acVal > val) {428result.push_back(genericData);429}430break;431case '=':432if (acVal == val) {433result.push_back(genericData);434}435break;436}437} else {438// string match439std::string acVal = genericData->getAttributeForSelection(GNE_ATTR_PARENT);440switch (compOp) {441case '@':442if (acVal.find(expr) != std::string::npos) {443result.push_back(genericData);444}445break;446case '!':447if (acVal.find(expr) == std::string::npos) {448result.push_back(genericData);449}450break;451case '=':452if (acVal == expr) {453result.push_back(genericData);454}455break;456case '^':457if (acVal != expr) {458result.push_back(genericData);459}460break;461}462}463}464return result;465}466467// ---------------------------------------------------------------------1------468// GNEMatchAttribute::CurrentEditedProperties - methods469// ---------------------------------------------------------------------------470471GNEMatchAttribute::CurrentEditedProperties::CurrentEditedProperties(const GNEMatchAttribute* matchAttributeParent) :472myMatchAttributeParent(matchAttributeParent) {473// build special attributes474myTagPropertiesAllAttributes = new GNETagProperties(GNE_TAG_ATTRIBUTES_ALL,475nullptr,476GUIIcon::EMPTY,477TL("Show all attributes"),478FXRGBA(255, 255, 255, 255),479TL("<all>"));480myAttributePropertiesNoCommon = new GNEAttributeProperties(myTagPropertiesAllAttributes,481GNE_ATTR_NOCOMMON,482TL("No common attributes defined"));483// set default tag and attribute for every property484const auto database = myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getNet()->getTagPropertiesDatabase();485setTagProperties(database->getTagProperty(SUMO_TAG_EDGE, true));486setAttributeProperties(myNetworkTagProperties.back()->getAttributeProperties(SUMO_ATTR_SPEED));487myNetworkMatchValue = ">10";488setTagProperties(database->getTagProperty(SUMO_TAG_VEHICLE, true));489setAttributeProperties(myDemandTagProperties.back()->getAttributeProperties(SUMO_ATTR_ID));490setTagProperties(database->getTagProperty(GNE_TAG_DATAS, true));491}492493494GNEMatchAttribute::CurrentEditedProperties::~CurrentEditedProperties() {495delete myTagPropertiesAllAttributes;496}497498499const GNETagProperties*500GNEMatchAttribute::CurrentEditedProperties::getTagPropertiesAll() const {501return myTagPropertiesAllAttributes;502}503504505const GNEAttributeProperties*506GNEMatchAttribute::CurrentEditedProperties::getAttributePropertiesNoCommon() const {507return myAttributePropertiesNoCommon;508}509510511const GNETagProperties*512GNEMatchAttribute::CurrentEditedProperties::getTagProperties() const {513if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeNetwork()) {514return myNetworkTagProperties.back();515} else if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeDemand()) {516return myDemandTagProperties.back();517} else if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeData()) {518return myDataTagProperties.back();519} else {520throw ProcessError("Invalid supermode");521}522}523524525const GNEAttributeProperties*526GNEMatchAttribute::CurrentEditedProperties::getAttributeProperties() const {527if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeNetwork()) {528return myNetworkAttributeProperties;529} else if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeDemand()) {530return myDemandAttributeProperties;531} else if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeData()) {532return myDataAttributeProperties;533} else {534throw ProcessError("Invalid supermode");535}536}537538539const std::string&540GNEMatchAttribute::CurrentEditedProperties::getMatchValue() const {541if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeNetwork()) {542return myNetworkMatchValue;543} else if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeDemand()) {544return myDemandMatchValue;545} else if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeData()) {546return myDataMatchValue;547} else {548throw ProcessError("Invalid supermode");549}550}551552553void554GNEMatchAttribute::CurrentEditedProperties::setTagProperties(const GNETagProperties* tagProperty) {555if (tagProperty->getSupermode() == Supermode::NETWORK) {556myNetworkTagProperties = tagProperty->getHierarchicalParentsRecuersively();557} else if (tagProperty->getSupermode() == Supermode::DEMAND) {558myDemandTagProperties = tagProperty->getHierarchicalParentsRecuersively();559} else if (tagProperty->getSupermode() == Supermode::DATA) {560myDataTagProperties = tagProperty->getHierarchicalParentsRecuersively();561}562}563564565void566GNEMatchAttribute::CurrentEditedProperties::setAttributeProperties(const GNEAttributeProperties* attrProperty) {567if (attrProperty->getTagPropertyParent()->getSupermode() == Supermode::NETWORK) {568myNetworkAttributeProperties = attrProperty;569} else if (attrProperty->getTagPropertyParent()->getSupermode() == Supermode::DEMAND) {570myDemandAttributeProperties = attrProperty;571} else if (attrProperty->getTagPropertyParent()->getSupermode() == Supermode::DATA) {572myDataAttributeProperties = attrProperty;573}574}575576577void578GNEMatchAttribute::CurrentEditedProperties::setMatchValue(const std::string value) {579if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeNetwork()) {580myNetworkMatchValue = value;581} else if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeDemand()) {582myDemandMatchValue = value;583} else if (myMatchAttributeParent->mySelectorFrameParent->getViewNet()->getEditModes().isCurrentSupermodeData()) {584myDataMatchValue = value;585}586}587588/****************************************************************************/589590591