Path: blob/main/src/netedit/dialogs/tools/GNEPythonToolDialog.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 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 auto optionsFileDialog = GNEFileDialog(myApplicationWindow, TL("options file"),143SUMOXMLDefinitions::XMLFileExtensions.getStrings(),144GNEFileDialog::OpenMode::SAVE,145GNEFileDialog::ConfigType::NETEDIT);146// check file147if (optionsFileDialog.getResult() == GNEDialog::Result::ACCEPT) {148myPythonTool->saveConfiguration(optionsFileDialog.getFilename());149}150return 1;151}152153154long155GNEPythonToolDialog::onCmdLoad(FXObject*, FXSelector, void*) {156// open file dialog157const auto optionsFileDialog = GNEFileDialog(myApplicationWindow, TL("options file"),158SUMOXMLDefinitions::XMLFileExtensions.getStrings(),159GNEFileDialog::OpenMode::LOAD_SINGLE,160GNEFileDialog::ConfigType::NETEDIT);161// check file162if ((optionsFileDialog.getResult() == GNEDialog::Result::ACCEPT) && myPythonTool->loadConfiguration(optionsFileDialog.getFilename())) {163// rebuild arguments164buildArguments((mySortedCheckButton->getCheck() == TRUE), (myGroupedCheckButton->getCheck() == TRUE));165}166return 1;167}168169170long171GNEPythonToolDialog::onCmdSetVisualization(FXObject*, FXSelector, void*) {172// rebuild arguments173buildArguments((mySortedCheckButton->getCheck() == TRUE), (myGroupedCheckButton->getCheck() == TRUE));174return 1;175}176177178long179GNEPythonToolDialog::onCmdRun(FXObject*, FXSelector, void*) {180// hide dialog181hide();182// run tool183return myApplicationWindow->tryHandle(myPythonTool->getMenuCommand(), FXSEL(SEL_COMMAND, MID_GNE_RUNPYTHONTOOL), nullptr);184}185186187long188GNEPythonToolDialog::onCmdReset(FXObject*, FXSelector, void*) {189// iterate over all arguments and reset values190for (const auto& argument : myArguments) {191argument->reset();192}193return 1;194}195196197long198GNEPythonToolDialog::onUpdRequiredAttributes(FXObject* sender, FXSelector, void*) {199// iterate over all arguments and check if required attribute is set200for (const auto& argument : myArguments) {201if (argument->requiredAttributeSet() == false) {202return sender->handle(this, FXSEL(SEL_COMMAND, ID_DISABLE), nullptr);203}204}205return sender->handle(this, FXSEL(SEL_COMMAND, ID_ENABLE), nullptr);206}207208209GNEPythonToolDialog::CategoryOptions::CategoryOptions(const std::string& category) :210std::string(category) {211}212213214void215GNEPythonToolDialog::CategoryOptions::addOption(const std::string& name, Option* option) {216myOptions.push_back(std::make_pair(name, option));217}218219220const std::vector<std::pair<std::string, Option*> >&221GNEPythonToolDialog::CategoryOptions::getOptions() const {222return myOptions;223}224225226void227GNEPythonToolDialog::CategoryOptions::sortByName() {228// just sort vector with options229std::sort(myOptions.begin(), myOptions.end());230}231232233void234GNEPythonToolDialog::buildArguments(bool sortByName, bool groupedByCategories) {235// clear arguments and categories236for (const auto& argument : myArguments) {237delete argument;238}239for (const auto& category : myCategories) {240delete category;241}242myArguments.clear();243myCategories.clear();244// get argument sorted by name and grouped by categories245auto categoryOptions = groupedByCategories ? getOptionsByCategories(myPythonTool->getToolsOptions()) : getOptions(myPythonTool->getToolsOptions());246// calculate number of arguments247int numArguments = 0;248for (auto& categoryOption : categoryOptions) {249numArguments += (int)categoryOption.getOptions().size() + 1;250}251const int halfNumArguments = numArguments / 2;252// declare counter for number of inserted arguments253int numInsertedArguments = 0;254// iterate over category options255for (auto& categoryOption : categoryOptions) {256// get argument frame257auto argumentFrame = (numInsertedArguments < halfNumArguments) ? myArgumentFrameLeft : myArgumentFrameRight;258// add category259if (categoryOption.size() > 0) {260myCategories.push_back(new GNEPythonToolDialogElements::Category(argumentFrame, categoryOption));261numInsertedArguments++;262}263// check if sort by name264if (sortByName) {265categoryOption.sortByName();266}267// add options268for (const auto& option : categoryOption.getOptions()) {269// get argument frame (again)270argumentFrame = (numInsertedArguments < halfNumArguments) ? myArgumentFrameLeft : myArgumentFrameRight;271// continue depending of type272if (option.second->isInteger()) {273myArguments.push_back(new GNEPythonToolDialogElements::IntArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));274} else if (option.second->isFloat()) {275myArguments.push_back(new GNEPythonToolDialogElements::FloatArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));276} else if (option.second->isBool()) {277myArguments.push_back(new GNEPythonToolDialogElements::BoolArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));278} else if (option.second->isFileName()) {279myArguments.push_back(new GNEPythonToolDialogElements::FileNameArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));280} else if (option.second->isNetwork()) {281myArguments.push_back(new GNEPythonToolDialogElements::NetworkArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));282} else if (option.second->isAdditional()) {283myArguments.push_back(new GNEPythonToolDialogElements::AdditionalArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));284} else if (option.second->isRoute()) {285myArguments.push_back(new GNEPythonToolDialogElements::RouteArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));286} else if (option.second->isData()) {287myArguments.push_back(new GNEPythonToolDialogElements::DataArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));288} else if (option.second->isSumoConfig()) {289myArguments.push_back(new GNEPythonToolDialogElements::SumoConfigArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));290} else if (option.second->isEdge()) {291myArguments.push_back(new GNEPythonToolDialogElements::EdgeArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));292} else if (option.second->isEdgeVector()) {293myArguments.push_back(new GNEPythonToolDialogElements::EdgeVectorArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));294} else {295myArguments.push_back(new GNEPythonToolDialogElements::StringArgument(this, myPythonTool, getApplicationWindow(), argumentFrame, option.first, option.second));296}297numInsertedArguments++;298}299}300// adjust parameter column (call always after create elements)301adjustParameterColumn();302}303304305void306GNEPythonToolDialog::adjustParameterColumn() {307int maximumWidth = 0;308// iterate over all arguments and find the maximum width309for (const auto& argument : myArguments) {310const auto label = argument->getParameterLabel();311const int columnWidth = label->getFont()->getTextWidth(label->getText().text(), label->getText().length() + MARGIN);312if (columnWidth > maximumWidth) {313maximumWidth = columnWidth;314}315}316// set maximum width for all parameter labels317for (const auto& argument : myArguments) {318argument->getParameterLabel()->setWidth(maximumWidth);319}320}321322323std::vector<GNEPythonToolDialog::CategoryOptions>324GNEPythonToolDialog::getOptions(OptionsCont& optionsCont) const {325// use a vector with only one empty category to reuse code of buildArguments326std::vector<GNEPythonToolDialog::CategoryOptions> result = {GNEPythonToolDialog::CategoryOptions("")};327// add all options to result328for (const auto& option : optionsCont) {329result.front().addOption(option.first, option.second);330}331return result;332}333334335std::vector<GNEPythonToolDialog::CategoryOptions>336GNEPythonToolDialog::getOptionsByCategories(OptionsCont& optionsCont) const {337// declare vector with common categories338const std::vector<std::string> commonCategories = {"input", "output", "processing", "time"};339// fill categories340std::vector<std::string> categories = commonCategories;341for (const auto& option : optionsCont) {342if (std::find(categories.begin(), categories.end(), option.second->getSubTopic()) == categories.end()) {343categories.push_back(option.second->getSubTopic());344}345}346// declare vector of category options and fill347std::vector<GNEPythonToolDialog::CategoryOptions> result;348for (const auto& category : categories) {349result.push_back(GNEPythonToolDialog::CategoryOptions(category));350}351// fill result with options352for (const auto& option : optionsCont) {353auto category = std::find(result.begin(), result.end(), option.second->getSubTopic());354// add option in category355category->addOption(option.first, option.second);356}357// drop empty categories358auto it = result.begin();359while (it != result.end()) {360if (it->getOptions().empty()) {361it = result.erase(it);362} else {363it++;364}365}366return result;367}368369370int371GNEPythonToolDialog::getNumRowColums() const {372const int column = (int)myArguments.size() / NUMROWSBYCOLUMN;373return (column < MAXNUMCOLUMNS) ? column : (MAXNUMCOLUMNS - 1);374}375376377FXVerticalFrame*378GNEPythonToolDialog::getArgumentFrameLeft() const {379return myArgumentFrameLeft;380}381382383FXVerticalFrame*384GNEPythonToolDialog::getArgumentFrameRight() const {385return myArgumentFrameRight;386}387388/****************************************************************************/389390391