Path: blob/main/src/netedit/dialogs/tools/GNEPythonToolDialog.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 GNEPythonToolDialog.cpp14/// @author Pablo Alvarez Lopez15/// @date Jun 202216///17// Dialog for tools18/****************************************************************************/1920#include <netedit/GNEApplicationWindow.h>21#include <netedit/GNEViewNet.h>22#include <netedit/GNEViewParent.h>23#include <netedit/tools/GNEPythonTool.h>24#include <utils/foxtools/MFXLabelTooltip.h>25#include <utils/foxtools/MFXStaticToolTip.h>26#include <utils/gui/div/GUIDesigns.h>2728#include "GNEPythonToolDialog.h"2930// ===========================================================================31// Defines32// ===========================================================================3334#define MARGIN 435#define MAXNUMCOLUMNS 436#define NUMROWSBYCOLUMN 203738// ===========================================================================39// FOX callback mapping40// ===========================================================================4142FXDEFMAP(GNEPythonToolDialog) GNEPythonToolDialogMap[] = {43FXMAPFUNC(SEL_COMMAND, MID_SHOWTOOLTIPS_MENU, GNEPythonToolDialog::onCmdShowToolTipsMenu),44FXMAPFUNC(SEL_COMMAND, MID_CHOOSEN_SAVE, GNEPythonToolDialog::onCmdSave),45FXMAPFUNC(SEL_UPDATE, MID_CHOOSEN_SAVE, GNEPythonToolDialog::onUpdRequiredAttributes),46FXMAPFUNC(SEL_COMMAND, MID_CHOOSEN_LOAD, GNEPythonToolDialog::onCmdLoad),47FXMAPFUNC(SEL_COMMAND, MID_GNE_SET_ATTRIBUTE, GNEPythonToolDialog::onCmdSetVisualization),48FXMAPFUNC(SEL_COMMAND, MID_GNE_BUTTON_RUN, GNEPythonToolDialog::onCmdRun),49FXMAPFUNC(SEL_UPDATE, MID_GNE_BUTTON_RUN, GNEPythonToolDialog::onUpdRequiredAttributes),50FXMAPFUNC(SEL_COMMAND, MID_GNE_BUTTON_RESET, GNEPythonToolDialog::onCmdReset)51};5253// Object implementation54FXIMPLEMENT(GNEPythonToolDialog, GNEDialog, GNEPythonToolDialogMap, ARRAYNUMBER(GNEPythonToolDialogMap))5556// ============================================-===============================57// member method definitions58// ===========================================================================5960GNEPythonToolDialog::GNEPythonToolDialog(GNEApplicationWindow* applicationWindow, GNEPythonTool* tool) :61GNEDialog(applicationWindow, TL("Tool"), GUIIcon::TOOL_PYTHON, DialogType::PYTHON,62GNEDialog::Buttons::RUN_CANCEL_RESET, OpenType::MODAL, ResizeMode::RESIZABLE) {63// create options64auto horizontalOptionsFrame = new FXHorizontalFrame(myContentFrame, GUIDesignHorizontalFrameNoPadding);65// build options66myShowToolTipsMenu = new MFXCheckableButton(false, horizontalOptionsFrame,67applicationWindow->getStaticTooltipMenu(), (std::string("\t") + TL("Toggle Menu Tooltips") + std::string("\t") + TL("Toggles whether tooltips in the menu shall be shown.")).c_str(),68GUIIconSubSys::getIcon(GUIIcon::SHOWTOOLTIPS_MENU), this, MID_SHOWTOOLTIPS_MENU, GUIDesignMFXCheckableButtonSquare);69auto saveFile = new MFXButtonTooltip(horizontalOptionsFrame, applicationWindow->getStaticTooltipMenu(), TL("Save toolcfg"),70GUIIconSubSys::getIcon(GUIIcon::SAVE), this, MID_CHOOSEN_SAVE, GUIDesignButtonConfiguration);71saveFile->setTipText(TL("Save file with tool configuration"));72auto loadFile = new MFXButtonTooltip(horizontalOptionsFrame, applicationWindow->getStaticTooltipMenu(), TL("Load toolcfg"),73GUIIconSubSys::getIcon(GUIIcon::OPEN), this, MID_CHOOSEN_LOAD, GUIDesignButtonConfiguration);74loadFile->setTipText(TL("Load file with tool configuration"));75mySortedCheckButton = new FXCheckButton(horizontalOptionsFrame, TL("Sorted by name"), this, MID_GNE_SET_ATTRIBUTE, GUIDesignCheckButton);76myGroupedCheckButton = new FXCheckButton(horizontalOptionsFrame, TL("Grouped by categories"), this, MID_GNE_SET_ATTRIBUTE, GUIDesignCheckButton);77// add separators78new FXSeparator(myContentFrame);79// Create scroll frame for content rows80auto contentScrollWindow = new FXScrollWindow(myContentFrame, GUIDesignScrollWindow);81auto horizontalRowFrames = new FXHorizontalFrame(contentScrollWindow, LAYOUT_FILL_X | LAYOUT_FILL_Y | PACK_UNIFORM_WIDTH);82myArgumentFrameLeft = new FXVerticalFrame(horizontalRowFrames, GUIDesignAuxiliarFrame);83myArgumentFrameRight = new FXVerticalFrame(horizontalRowFrames, GUIDesignAuxiliarFrame);84// set tool85myPythonTool = tool;86// set title87setTitle(myPythonTool->getToolName().c_str());88// reset checkboxes89mySortedCheckButton->setCheck(FALSE);90myGroupedCheckButton->setCheck(TRUE);91// set myShowToolTipsMenu92myShowToolTipsMenu->setChecked(getApp()->reg().readIntEntry("gui", "menuToolTips", 0) != 1);93// set current values in options (like current folders and similar)94myPythonTool->setCurrentValues();95// build arguments96buildArguments(false, true);97// get maximum height98const int maximumHeight = myArgumentFrameLeft->numChildren() * GUIDesignHeight + 120;99// resize100resize(1024, maximumHeight <= 768 ? maximumHeight : 768);101// open dialog102openDialog();103}104105106GNEPythonToolDialog::~GNEPythonToolDialog() {}107108109void110GNEPythonToolDialog::runInternalTest(const InternalTestStep::DialogArgument* /*dialogArgument*/) {111// nothing to do112}113114115const GNEPythonTool*116GNEPythonToolDialog::getPythonTool() const {117return myPythonTool;118}119120121long122GNEPythonToolDialog::onCmdShowToolTipsMenu(FXObject*, FXSelector, void*) {123// toggle check124myShowToolTipsMenu->setChecked(!myShowToolTipsMenu->amChecked());125if (myApplicationWindow->getViewNet()) {126myApplicationWindow->getViewNet()->getViewParent()->getShowToolTipsMenu()->setChecked(myShowToolTipsMenu->amChecked());127myApplicationWindow->getViewNet()->getViewParent()->getShowToolTipsMenu()->update();128}129// enable/disable static tooltip130myApplicationWindow->getStaticTooltipMenu()->enableStaticToolTip(myShowToolTipsMenu->amChecked());131// save in registry132getApp()->reg().writeIntEntry("gui", "menuToolTips", myShowToolTipsMenu->amChecked() ? 0 : 1);133update();134135return 1;136}137138139long140GNEPythonToolDialog::onCmdSave(FXObject*, FXSelector, void*) {141// open save dialog142const GNEFileDialog optionsFileDialog(myApplicationWindow, this,143TL("options file"),144SUMOXMLDefinitions::XMLFileExtensions.getStrings(),145GNEFileDialog::OpenMode::SAVE,146GNEFileDialog::ConfigType::NETEDIT);147// check file148if (optionsFileDialog.getResult() == GNEDialog::Result::ACCEPT) {149myPythonTool->saveConfiguration(optionsFileDialog.getFilename());150}151return 1;152}153154155long156GNEPythonToolDialog::onCmdLoad(FXObject*, FXSelector, void*) {157// open file dialog158const GNEFileDialog optionsFileDialog(myApplicationWindow, this,159TL("options file"),160SUMOXMLDefinitions::XMLFileExtensions.getStrings(),161GNEFileDialog::OpenMode::LOAD_SINGLE,162GNEFileDialog::ConfigType::NETEDIT);163// check file164if ((optionsFileDialog.getResult() == GNEDialog::Result::ACCEPT) && myPythonTool->loadConfiguration(optionsFileDialog.getFilename())) {165// rebuild arguments166buildArguments((mySortedCheckButton->getCheck() == TRUE), (myGroupedCheckButton->getCheck() == TRUE));167}168return 1;169}170171172long173GNEPythonToolDialog::onCmdSetVisualization(FXObject*, FXSelector, void*) {174// rebuild arguments175buildArguments((mySortedCheckButton->getCheck() == TRUE), (myGroupedCheckButton->getCheck() == TRUE));176return 1;177}178179180long181GNEPythonToolDialog::onCmdRun(FXObject*, FXSelector, void*) {182// hide dialog183closeDialogAccepting();184// run tool185return myApplicationWindow->tryHandle(myPythonTool->getMenuCommand(), FXSEL(SEL_COMMAND, MID_GNE_RUNPYTHONTOOL), nullptr);186}187188189long190GNEPythonToolDialog::onCmdReset(FXObject*, FXSelector, void*) {191// iterate over all arguments and reset values192for (const auto& argument : myArguments) {193argument->reset();194}195return 1;196}197198199long200GNEPythonToolDialog::onUpdRequiredAttributes(FXObject* sender, FXSelector, void*) {201// iterate over all arguments and check if required attribute is set202for (const auto& argument : myArguments) {203if (argument->requiredAttributeSet() == false) {204return sender->handle(this, FXSEL(SEL_COMMAND, ID_DISABLE), nullptr);205}206}207return sender->handle(this, FXSEL(SEL_COMMAND, ID_ENABLE), nullptr);208}209210211GNEPythonToolDialog::CategoryOptions::CategoryOptions(const std::string& category) :212std::string(category) {213}214215216void217GNEPythonToolDialog::CategoryOptions::addOption(const std::string& name, Option* option) {218myOptions.push_back(std::make_pair(name, option));219}220221222const std::vector<std::pair<std::string, Option*> >&223GNEPythonToolDialog::CategoryOptions::getOptions() const {224return myOptions;225}226227228void229GNEPythonToolDialog::CategoryOptions::sortByName() {230// just sort vector with options231std::sort(myOptions.begin(), myOptions.end());232}233234235void236GNEPythonToolDialog::buildArguments(bool sortByName, bool groupedByCategories) {237// clear arguments and categories238for (const auto& argument : myArguments) {239delete argument;240}241for (const auto& category : myCategories) {242delete category;243}244myArguments.clear();245myCategories.clear();246// get argument sorted by name and grouped by categories247auto categoryOptions = groupedByCategories ? getOptionsByCategories(myPythonTool->getToolsOptions()) : getOptions(myPythonTool->getToolsOptions());248// calculate number of arguments249int numArguments = 0;250for (auto& categoryOption : categoryOptions) {251numArguments += (int)categoryOption.getOptions().size() + 1;252}253const int halfNumArguments = numArguments / 2;254// declare counter for number of inserted arguments255int numInsertedArguments = 0;256// iterate over category options257for (auto& categoryOption : categoryOptions) {258// get argument frame259auto argumentFrame = (numInsertedArguments < halfNumArguments) ? myArgumentFrameLeft : myArgumentFrameRight;260// add category261if (categoryOption.size() > 0) {262myCategories.push_back(new GNEPythonToolDialogElements::Category(argumentFrame, categoryOption));263numInsertedArguments++;264}265// check if sort by name266if (sortByName) {267categoryOption.sortByName();268}269// add options270for (const auto& option : categoryOption.getOptions()) {271// get argument frame (again)272argumentFrame = (numInsertedArguments < halfNumArguments) ? myArgumentFrameLeft : myArgumentFrameRight;273// continue depending of type274if (option.second->isInteger()) {275myArguments.push_back(new GNEPythonToolDialogElements::IntArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));276} else if (option.second->isFloat()) {277myArguments.push_back(new GNEPythonToolDialogElements::FloatArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));278} else if (option.second->isBool()) {279myArguments.push_back(new GNEPythonToolDialogElements::BoolArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));280} else if (option.second->isFileName()) {281myArguments.push_back(new GNEPythonToolDialogElements::FileNameArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));282} else if (option.second->isNetwork()) {283myArguments.push_back(new GNEPythonToolDialogElements::NetworkArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));284} else if (option.second->isAdditional()) {285myArguments.push_back(new GNEPythonToolDialogElements::AdditionalArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));286} else if (option.second->isRoute()) {287myArguments.push_back(new GNEPythonToolDialogElements::RouteArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));288} else if (option.second->isData()) {289myArguments.push_back(new GNEPythonToolDialogElements::DataArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));290} else if (option.second->isSumoConfig()) {291myArguments.push_back(new GNEPythonToolDialogElements::SumoConfigArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));292} else if (option.second->isEdge()) {293myArguments.push_back(new GNEPythonToolDialogElements::EdgeArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));294} else if (option.second->isEdgeVector()) {295myArguments.push_back(new GNEPythonToolDialogElements::EdgeVectorArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));296} else {297myArguments.push_back(new GNEPythonToolDialogElements::StringArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));298}299numInsertedArguments++;300}301}302// check if create arguments303if (id() > 0) {304for (auto& argument : myArguments) {305argument->create();306}307}308// adjust parameter column (call always after create elements)309adjustParameterColumn();310}311312313void314GNEPythonToolDialog::adjustParameterColumn() {315int maximumWidth = 0;316// iterate over all arguments and find the maximum width317for (const auto& argument : myArguments) {318const auto label = argument->getParameterLabel();319const int columnWidth = label->getFont()->getTextWidth(label->getText().text(), label->getText().length() + MARGIN);320if (columnWidth > maximumWidth) {321maximumWidth = columnWidth;322}323}324// set maximum width for all parameter labels325for (const auto& argument : myArguments) {326argument->getParameterLabel()->setWidth(maximumWidth);327}328}329330331std::vector<GNEPythonToolDialog::CategoryOptions>332GNEPythonToolDialog::getOptions(OptionsCont& optionsCont) const {333// use a vector with only one empty category to reuse code of buildArguments334std::vector<GNEPythonToolDialog::CategoryOptions> result = {GNEPythonToolDialog::CategoryOptions("")};335// add all options to result336for (const auto& option : optionsCont) {337result.front().addOption(option.first, option.second);338}339return result;340}341342343std::vector<GNEPythonToolDialog::CategoryOptions>344GNEPythonToolDialog::getOptionsByCategories(OptionsCont& optionsCont) const {345// declare vector with common categories346const std::vector<std::string> commonCategories = {"input", "output", "processing", "time"};347// fill categories348std::vector<std::string> categories = commonCategories;349for (const auto& option : optionsCont) {350if (std::find(categories.begin(), categories.end(), option.second->getSubTopic()) == categories.end()) {351categories.push_back(option.second->getSubTopic());352}353}354// declare vector of category options and fill355std::vector<GNEPythonToolDialog::CategoryOptions> result;356for (const auto& category : categories) {357result.push_back(GNEPythonToolDialog::CategoryOptions(category));358}359// fill result with options360for (const auto& option : optionsCont) {361auto category = std::find(result.begin(), result.end(), option.second->getSubTopic());362// add option in category363category->addOption(option.first, option.second);364}365// drop empty categories366auto it = result.begin();367while (it != result.end()) {368if (it->getOptions().empty()) {369it = result.erase(it);370} else {371it++;372}373}374return result;375}376377378int379GNEPythonToolDialog::getNumRowColums() const {380const int column = (int)myArguments.size() / NUMROWSBYCOLUMN;381return (column < MAXNUMCOLUMNS) ? column : (MAXNUMCOLUMNS - 1);382}383384385FXVerticalFrame*386GNEPythonToolDialog::getArgumentFrameLeft() const {387return myArgumentFrameLeft;388}389390391FXVerticalFrame*392GNEPythonToolDialog::getArgumentFrameRight() const {393return myArgumentFrameRight;394}395396/****************************************************************************/397398399