Path: blob/main/src/netedit/dialogs/options/GNEOptionsEditor.cpp
169684 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 GNEOptionsEditor.cpp14/// @author Pablo Alvarez Lopez15/// @date May 202316///17// A Dialog for setting options (see OptionsCont)18/****************************************************************************/19#include <config.h>2021#include <fstream>2223#include <netedit/dialogs/GNEDialog.h>24#include <netedit/GNEApplicationWindow.h>25#include <netedit/GNEViewNet.h>26#include <netedit/GNEViewParent.h>27#include <utils/foxtools/MFXCheckButtonTooltip.h>28#include <utils/foxtools/MFXGroupBoxModule.h>29#include <utils/foxtools/MFXStaticToolTip.h>30#include <utils/foxtools/MFXTextFieldSearch.h>31#include <utils/gui/div/GUIDesigns.h>32#include <utils/options/OptionsLoader.h>33#include <xercesc/parsers/SAXParser.hpp>3435#include "GNEOptionsEditor.h"3637// ===========================================================================38// Defines39// ===========================================================================4041#define TREELISTWIDTH 2004243// ===========================================================================44// FOX callback mapping45// ===========================================================================4647FXDEFMAP(GNEOptionsEditor) GNEOptionsEditorMap[] = {48FXMAPFUNC(SEL_COMMAND, MID_GNE_SELECT, GNEOptionsEditor::onCmdSelectTopic),49FXMAPFUNC(SEL_COMMAND, MID_GNE_SEARCH_USEDESCRIPTION, GNEOptionsEditor::onCmdSearch),50FXMAPFUNC(SEL_COMMAND, MID_MTEXTFIELDSEARCH_UPDATED, GNEOptionsEditor::onCmdSearch),51FXMAPFUNC(SEL_COMMAND, MID_SHOWTOOLTIPS_MENU, GNEOptionsEditor::onCmdShowToolTipsMenu),52FXMAPFUNC(SEL_COMMAND, MID_CHOOSEN_SAVE, GNEOptionsEditor::onCmdSaveOptions),53FXMAPFUNC(SEL_COMMAND, MID_CHOOSEN_LOAD, GNEOptionsEditor::onCmdLoadOptions),54FXMAPFUNC(SEL_COMMAND, MID_GNE_BUTTON_DEFAULT, GNEOptionsEditor::onCmdResetDefault),55};5657// Object implementation58FXIMPLEMENT(GNEOptionsEditor, FXVerticalFrame, GNEOptionsEditorMap, ARRAYNUMBER(GNEOptionsEditorMap))5960// ===========================================================================61// method definitions62// ===========================================================================6364GNEOptionsEditor::GNEOptionsEditor(GNEDialog* dialog, const std::string& titleName, OptionsCont& optionsContainer,65const OptionsCont& originalOptionsContainer) :66FXVerticalFrame(dialog->getContentFrame(), GUIDesignAuxiliarFrame),67myDialog(dialog),68myOptionsContainer(optionsContainer),69myCopyOfOptionsContainer(optionsContainer.clone()),70myOriginalOptionsContainer(originalOptionsContainer) {71// get staticTooltipMenu72auto staticTooltipMenu = dialog->getApplicationWindow()->getStaticTooltipMenu();73// add buttons frame74FXHorizontalFrame* buttonsFrame = new FXHorizontalFrame(this, GUIDesignHorizontalFrameNoPadding);75myShowToolTipsMenu = new MFXCheckableButton(false, buttonsFrame, staticTooltipMenu,76(std::string("\t") + TL("Toggle Menu Tooltips") + std::string("\t") + TL("Toggles whether tooltips in the menu shall be shown.")).c_str(),77GUIIconSubSys::getIcon(GUIIcon::SHOWTOOLTIPS_MENU), this, MID_SHOWTOOLTIPS_MENU, GUIDesignMFXCheckableButtonSquare);78auto saveFile = new MFXButtonTooltip(buttonsFrame, staticTooltipMenu, TL("Save options"),79GUIIconSubSys::getIcon(GUIIcon::SAVE), this, MID_CHOOSEN_SAVE, GUIDesignButtonConfiguration);80saveFile->setTipText(TL("Save configuration file"));81auto loadFile = new MFXButtonTooltip(buttonsFrame, staticTooltipMenu, TL("Load options"),82GUIIconSubSys::getIcon(GUIIcon::OPEN), this, MID_CHOOSEN_LOAD, GUIDesignButtonConfiguration);83loadFile->setTipText(TL("Load configuration file"));84auto resetDefault = new MFXButtonTooltip(buttonsFrame, staticTooltipMenu, TL("Default options"),85GUIIconSubSys::getIcon(GUIIcon::RESET), this, MID_GNE_BUTTON_DEFAULT, GUIDesignButtonConfiguration);86resetDefault->setTipText(TL("Reset all options to default"));87// add separator88new FXSeparator(this);89// create elements frame90FXHorizontalFrame* elementsFrame = new FXHorizontalFrame(this, GUIDesignAuxiliarFrame);91FXVerticalFrame* elementsFrameTree = new FXVerticalFrame(elementsFrame, GUIDesignAuxiliarVerticalFrame);92FXVerticalFrame* elementsFrameValues = new FXVerticalFrame(elementsFrame, GUIDesignAuxiliarFrame);93// Create GroupBox modules94MFXGroupBoxModule* groupBoxTree = new MFXGroupBoxModule(elementsFrameTree, TL("Topics"));95MFXGroupBoxModule* groupBoxOptions = new MFXGroupBoxModule(elementsFrameValues, TL("Options"));96// create FXTreeList97myTopicsTreeList = new FXTreeList(groupBoxTree->getCollapsableFrame(), this, MID_GNE_SELECT, GUIDesignTreeListFixedWidth);98myTopicsTreeList->setWidth(TREELISTWIDTH);99// add root item100myRootItem = myTopicsTreeList->appendItem(nullptr, titleName.c_str());101myRootItem->setExpanded(TRUE);102// create scroll103FXScrollWindow* scrollTabEntries = new FXScrollWindow(groupBoxOptions->getCollapsableFrame(), LAYOUT_FILL_X | LAYOUT_FILL_Y);104// create vertical frame for entries105myEntriesFrame = new FXVerticalFrame(scrollTabEntries, GUIDesignAuxiliarFrame);106// iterate over all topics107for (const auto& topic : myOptionsContainer.getSubTopics()) {108// check if we have to ignore this topic109if (myIgnoredTopics.count(topic) == 0) {110// add topic into myTreeItemTopics and tree111myTreeItemTopics[myTopicsTreeList->appendItem(myRootItem, topic.c_str())] = topic;112// iterate over entries113const std::vector<std::string> entries = myOptionsContainer.getSubTopicsEntries(topic);114for (const auto& entry : entries) {115// check if we have to ignore this entry116if (myIgnoredEntries.count(entry) == 0) {117// get type118const std::string type = myOptionsContainer.getTypeName(entry);119// get description120const std::string description = myOptionsContainer.getDescription(entry);121// get default value122const std::string defaultValue = myOptionsContainer.getValueString(entry);123// continue depending of type124if (type == "STR") {125myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionString(this, myEntriesFrame, topic, entry, description, defaultValue));126} else if (type == "TIME") {127myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionTime(this, myEntriesFrame, topic, entry, description, defaultValue));128} else if ((type == "FILE") || (type == "NETWORK") || (type == "ADDITIONAL") || (type == "ROUTE") || (type == "DATA")) {129myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionFilename(this, myEntriesFrame, topic, entry, description, defaultValue));130} else if (type == "BOOL") {131myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionBool(this, myEntriesFrame, topic, entry, description, defaultValue));132} else if (type == "INT") {133myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionInt(this, myEntriesFrame, topic, entry, description, defaultValue));134} else if (type == "FLOAT") {135myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionFloat(this, myEntriesFrame, topic, entry, description, defaultValue));136} else if (type == "INT[]") {137myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionIntVector(this, myEntriesFrame, topic, entry, description, defaultValue));138} else if (type == "STR[]") {139myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionStringVector(this, myEntriesFrame, topic, entry, description, defaultValue));140}141}142}143}144}145// create search elements146FXHorizontalFrame* searchFrame = new FXHorizontalFrame(this, GUIDesignAuxiliarHorizontalFrame);147new FXLabel(searchFrame, TL("Search"), nullptr, GUIDesignLabelThickedFixed(TREELISTWIDTH - GUIDesignHeight + 14));148myDescriptionSearchCheckButton = new MFXCheckButtonTooltip(searchFrame, staticTooltipMenu, "", this, MID_GNE_SEARCH_USEDESCRIPTION, GUIDesignCheckButtonThick);149myDescriptionSearchCheckButton->setToolTipText(TL("Include description in search"));150mySearchButton = new MFXTextFieldSearch(searchFrame, staticTooltipMenu, this, MID_GNE_SET_ATTRIBUTE, GUIDesignTextField);151// after creation, adjust entries name sizes152for (const auto& entry : myOptionRowEntries) {153entry->adjustNameSize();154}155// set myShowToolTipsMenu156myShowToolTipsMenu->setChecked(getApp()->reg().readIntEntry("gui", "menuToolTips", 0) != 1);157}158159160GNEOptionsEditor::~GNEOptionsEditor() {161delete myCopyOfOptionsContainer;162}163164165void166GNEOptionsEditor::runInternalTest(const InternalTestStep::DialogArgument* /*dialogArgument*/) {167// not finished yet168}169170171bool172GNEOptionsEditor::isOptionModified() const {173return myOptionsModified;174}175176void177GNEOptionsEditor::resetAllOptions() {178for (const auto& entry : myOptionRowEntries) {179entry->onCmdResetOption(nullptr, 0, nullptr);180}181myOptionsModified = false;182}183184185long186GNEOptionsEditor::onCmdSelectTopic(FXObject*, FXSelector, void*) {187if (mySearchButton->getText().count() == 0) {188updateVisibleEntriesByTopic();189}190return 1;191}192193194long195GNEOptionsEditor::onCmdSearch(FXObject*, FXSelector, void*) {196if (mySearchButton->getText().count() > 0) {197updateVisibleEntriesBySearch(mySearchButton->getText().text());198} else {199updateVisibleEntriesByTopic();200}201return 1;202}203204205long206GNEOptionsEditor::onCmdShowToolTipsMenu(FXObject*, FXSelector, void*) {207// get staticTooltipMenu208auto viewNet = myDialog->getApplicationWindow()->getViewNet();209// toggle check210myShowToolTipsMenu->setChecked(!myShowToolTipsMenu->amChecked());211if (viewNet) {212viewNet->getViewParent()->getShowToolTipsMenu()->setChecked(myShowToolTipsMenu->amChecked());213viewNet->getViewParent()->getShowToolTipsMenu()->update();214}215// enable/disable static tooltip216myDialog->getApplicationWindow()->getStaticTooltipMenu()->enableStaticToolTip(myShowToolTipsMenu->amChecked());217// save in registry218getApp()->reg().writeIntEntry("gui", "menuToolTips", myShowToolTipsMenu->amChecked() ? 0 : 1);219update();220221return 1;222}223224225long226GNEOptionsEditor::onCmdSaveOptions(FXObject*, FXSelector, void*) {227// open file dialog228const auto optionsFileDialog = GNEFileDialog(myDialog->getApplicationWindow(), TL("options file"),229SUMOXMLDefinitions::XMLFileExtensions.getStrings(),230GNEFileDialog::OpenMode::SAVE,231GNEFileDialog::ConfigType::NETEDIT);232// check file233if (optionsFileDialog.getResult() == GNEDialog::Result::ACCEPT) {234std::ofstream out(StringUtils::transcodeToLocal(optionsFileDialog.getFilename()));235myOptionsContainer.writeConfiguration(out, true, false, false, optionsFileDialog.getFilename(), true);236out.close();237}238return 1;239}240241242long243GNEOptionsEditor::onCmdLoadOptions(FXObject*, FXSelector, void*) {244// open file dialog245const auto optionsFileDialog = GNEFileDialog(myDialog->getApplicationWindow(), TL("options file"),246SUMOXMLDefinitions::XMLFileExtensions.getStrings(),247GNEFileDialog::OpenMode::LOAD_SINGLE,248GNEFileDialog::ConfigType::NETEDIT);249// check file250if ((optionsFileDialog.getResult() == GNEDialog::Result::ACCEPT) && loadConfiguration(optionsFileDialog.getFilename())) {251// update entries252for (const auto& entry : myOptionRowEntries) {253entry->updateOption();254}255}256return 1;257}258259260long261GNEOptionsEditor::onCmdResetDefault(FXObject*, FXSelector, void*) {262// restore entries263for (const auto& entry : myOptionRowEntries) {264entry->restoreOption();265}266return 1;267}268269270GNEOptionsEditor::GNEOptionsEditor() :271myOptionsContainer(OptionsCont::EMPTY_OPTIONS),272myOriginalOptionsContainer(OptionsCont::EMPTY_OPTIONS) {273}274275276bool277GNEOptionsEditor::updateVisibleEntriesByTopic() {278// iterate over tree elements and get the selected item279for (const auto& treeItemTopic : myTreeItemTopics) {280if (treeItemTopic.first->isSelected()) {281// iterate over entries282for (const auto& entry : myOptionRowEntries) {283if (entry->getTopic() == treeItemTopic.second) {284entry->show();285} else {286entry->hide();287}288}289myEntriesFrame->recalc();290myEntriesFrame->update();291return true;292}293}294// no topic selected, then show all295for (const auto& entry : myOptionRowEntries) {296entry->show();297}298myEntriesFrame->recalc();299myEntriesFrame->update();300return true;301}302303304void305GNEOptionsEditor::updateVisibleEntriesBySearch(std::string searchText) {306// first lower case search text307searchText = StringUtils::to_lower_case(searchText);308// iterate over entries309for (const auto& entry : myOptionRowEntries) {310if (searchText.empty()) {311// show all entries if searchText is empty312entry->show();313} else if (entry->getNameLower().find(searchText) != std::string::npos) {314entry->show();315} else if ((myDescriptionSearchCheckButton->getCheck() == TRUE) &&316(entry->getDescriptionLower().find(searchText) != std::string::npos)) {317entry->show();318} else {319entry->hide();320}321}322myEntriesFrame->recalc();323myEntriesFrame->update();324}325326327bool328GNEOptionsEditor::loadConfiguration(const std::string& file) {329// make all options writable330myOptionsContainer.resetWritable();331// build parser332XERCES_CPP_NAMESPACE::SAXParser parser;333parser.setValidationScheme(XERCES_CPP_NAMESPACE::SAXParser::Val_Never);334parser.setDisableDefaultEntityResolution(true);335// start the parsing336OptionsLoader handler(myOptionsContainer);337try {338parser.setDocumentHandler(&handler);339parser.setErrorHandler(&handler);340parser.parse(StringUtils::transcodeToLocal(file).c_str());341if (handler.errorOccurred()) {342WRITE_ERROR(TL("Could not load configuration '") + file + "'.");343return false;344}345} catch (const XERCES_CPP_NAMESPACE::XMLException& e) {346WRITE_ERROR(TL("Could not load tool configuration '") + file + "':\n " + StringUtils::transcode(e.getMessage()));347return false;348}349// write info350WRITE_MESSAGE(TL("Loaded configuration."));351return true;352}353354/****************************************************************************/355356357