Path: blob/main/src/netedit/dialogs/options/GNEOptionsEditor.cpp
193874 views
/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2001-2026 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 <netedit/frames/common/GNEGroupBoxModule.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 modules94GNEGroupBoxModule* groupBoxTree = new GNEGroupBoxModule(elementsFrameTree, TL("Topics"));95GNEGroupBoxModule* groupBoxOptions = new GNEGroupBoxModule(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// check if is editable124const bool editable = myOptionsContainer.isEditable(entry);125// continue depending of type126if (type == "STR") {127myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionString(this, myEntriesFrame, topic, entry, description, defaultValue, editable));128} else if (type == "TIME") {129myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionTime(this, myEntriesFrame, topic, entry, description, defaultValue, editable));130} else if ((type == "FILE") || (type == "NETWORK") || (type == "ADDITIONAL") || (type == "ROUTE") || (type == "DATA")) {131myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionFilename(this, myEntriesFrame, topic, entry, description, defaultValue, editable));132} else if (type == "BOOL") {133myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionBool(this, myEntriesFrame, topic, entry, description, defaultValue, editable));134} else if (type == "INT") {135myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionInt(this, myEntriesFrame, topic, entry, description, defaultValue, editable));136} else if (type == "FLOAT") {137myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionFloat(this, myEntriesFrame, topic, entry, description, defaultValue, editable));138} else if (type == "INT[]") {139myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionIntVector(this, myEntriesFrame, topic, entry, description, defaultValue, editable));140} else if (type == "STR[]") {141myOptionRowEntries.push_back(new GNEOptionsEditorRow::OptionStringVector(this, myEntriesFrame, topic, entry, description, defaultValue, editable));142}143}144}145}146}147// create search elements148FXHorizontalFrame* searchFrame = new FXHorizontalFrame(this, GUIDesignAuxiliarHorizontalFrame);149new FXLabel(searchFrame, TL("Search"), nullptr, GUIDesignLabelThickedFixed(TREELISTWIDTH - GUIDesignHeight + 14));150myDescriptionSearchCheckButton = new MFXCheckButtonTooltip(searchFrame, staticTooltipMenu, "", this, MID_GNE_SEARCH_USEDESCRIPTION, GUIDesignCheckButtonThick);151myDescriptionSearchCheckButton->setToolTipText(TL("Include description in search"));152mySearchButton = new MFXTextFieldSearch(searchFrame, staticTooltipMenu, this, MID_GNE_SET_ATTRIBUTE, GUIDesignTextField);153// after creation, adjust entries name sizes154for (const auto& entry : myOptionRowEntries) {155entry->adjustNameSize();156}157// set myShowToolTipsMenu158myShowToolTipsMenu->setChecked(getApp()->reg().readIntEntry("gui", "menuToolTips", 0) != 1);159}160161162GNEOptionsEditor::~GNEOptionsEditor() {163delete myCopyOfOptionsContainer;164}165166167void168GNEOptionsEditor::runInternalTest(const InternalTestStep::DialogArgument* /*dialogArgument*/) {169// not finished yet170}171172173bool174GNEOptionsEditor::isOptionModified() const {175return myOptionsModified;176}177178void179GNEOptionsEditor::resetAllOptions() {180for (const auto& entry : myOptionRowEntries) {181entry->onCmdResetOption(nullptr, 0, nullptr);182}183myOptionsModified = false;184}185186187long188GNEOptionsEditor::onCmdSelectTopic(FXObject*, FXSelector, void*) {189if (mySearchButton->getText().count() == 0) {190updateVisibleEntriesByTopic();191}192return 1;193}194195196long197GNEOptionsEditor::onCmdSearch(FXObject*, FXSelector, void*) {198if (mySearchButton->getText().count() > 0) {199updateVisibleEntriesBySearch(mySearchButton->getText().text());200} else {201updateVisibleEntriesByTopic();202}203return 1;204}205206207long208GNEOptionsEditor::onCmdShowToolTipsMenu(FXObject*, FXSelector, void*) {209// get staticTooltipMenu210auto viewNet = myDialog->getApplicationWindow()->getViewNet();211// toggle check212myShowToolTipsMenu->setChecked(!myShowToolTipsMenu->amChecked());213if (viewNet) {214viewNet->getViewParent()->getShowToolTipsMenu()->setChecked(myShowToolTipsMenu->amChecked());215viewNet->getViewParent()->getShowToolTipsMenu()->update();216}217// enable/disable static tooltip218myDialog->getApplicationWindow()->getStaticTooltipMenu()->enableStaticToolTip(myShowToolTipsMenu->amChecked());219// save in registry220getApp()->reg().writeIntEntry("gui", "menuToolTips", myShowToolTipsMenu->amChecked() ? 0 : 1);221update();222223return 1;224}225226227long228GNEOptionsEditor::onCmdSaveOptions(FXObject*, FXSelector, void*) {229// open file dialog230const GNEFileDialog optionsFileDialog(myDialog->getApplicationWindow(), myDialog,231TL("options file"),232SUMOXMLDefinitions::XMLFileExtensions.getStrings(),233GNEFileDialog::OpenMode::SAVE,234GNEFileDialog::ConfigType::NETEDIT);235// check file236if (optionsFileDialog.getResult() == GNEDialog::Result::ACCEPT) {237std::ofstream out(StringUtils::transcodeToLocal(optionsFileDialog.getFilename()));238myOptionsContainer.writeConfiguration(out, true, false, false, optionsFileDialog.getFilename(), true);239out.close();240}241return 1;242}243244245long246GNEOptionsEditor::onCmdLoadOptions(FXObject*, FXSelector, void*) {247// open file dialog248const GNEFileDialog optionsFileDialog(myDialog->getApplicationWindow(), myDialog,249TL("options file"),250SUMOXMLDefinitions::XMLFileExtensions.getStrings(),251GNEFileDialog::OpenMode::LOAD_SINGLE,252GNEFileDialog::ConfigType::NETEDIT);253// check file254if ((optionsFileDialog.getResult() == GNEDialog::Result::ACCEPT) && loadConfiguration(optionsFileDialog.getFilename())) {255// update entries256for (const auto& entry : myOptionRowEntries) {257entry->updateOption();258}259}260return 1;261}262263264long265GNEOptionsEditor::onCmdResetDefault(FXObject*, FXSelector, void*) {266// restore entries267for (const auto& entry : myOptionRowEntries) {268entry->restoreOption();269}270return 1;271}272273274GNEOptionsEditor::GNEOptionsEditor() :275myOptionsContainer(OptionsCont::EMPTY_OPTIONS),276myOriginalOptionsContainer(OptionsCont::EMPTY_OPTIONS) {277}278279280bool281GNEOptionsEditor::updateVisibleEntriesByTopic() {282// iterate over tree elements and get the selected item283for (const auto& treeItemTopic : myTreeItemTopics) {284if (treeItemTopic.first->isSelected()) {285// iterate over entries286for (const auto& entry : myOptionRowEntries) {287if (entry->getTopic() == treeItemTopic.second) {288entry->show();289} else {290entry->hide();291}292}293myEntriesFrame->recalc();294myEntriesFrame->update();295return true;296}297}298// no topic selected, then show all299for (const auto& entry : myOptionRowEntries) {300entry->show();301}302myEntriesFrame->recalc();303myEntriesFrame->update();304return true;305}306307308void309GNEOptionsEditor::updateVisibleEntriesBySearch(std::string searchText) {310// first lower case search text311searchText = StringUtils::to_lower_case(searchText);312// iterate over entries313for (const auto& entry : myOptionRowEntries) {314if (searchText.empty()) {315// show all entries if searchText is empty316entry->show();317} else if (entry->getNameLower().find(searchText) != std::string::npos) {318entry->show();319} else if ((myDescriptionSearchCheckButton->getCheck() == TRUE) &&320(entry->getDescriptionLower().find(searchText) != std::string::npos)) {321entry->show();322} else {323entry->hide();324}325}326myEntriesFrame->recalc();327myEntriesFrame->update();328}329330331bool332GNEOptionsEditor::loadConfiguration(const std::string& file) {333// make all options writable334myOptionsContainer.resetWritable();335// build parser336XERCES_CPP_NAMESPACE::SAXParser parser;337parser.setValidationScheme(XERCES_CPP_NAMESPACE::SAXParser::Val_Never);338parser.setDisableDefaultEntityResolution(true);339// start the parsing340OptionsLoader handler(myOptionsContainer);341try {342parser.setDocumentHandler(&handler);343parser.setErrorHandler(&handler);344parser.parse(StringUtils::transcodeToLocal(file).c_str());345if (handler.errorOccurred()) {346WRITE_ERROR(TL("Could not load configuration '") + file + "'.");347return false;348}349} catch (const XERCES_CPP_NAMESPACE::XMLException& e) {350WRITE_ERROR(TL("Could not load tool configuration '") + file + "':\n " + StringUtils::transcode(e.getMessage()));351return false;352}353// write info354WRITE_MESSAGE(TL("Loaded configuration."));355return true;356}357358/****************************************************************************/359360361