diff --git a/CMakeLists.txt b/CMakeLists.txt index 898012154..60977531d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,10 +67,22 @@ set(IGN_RENDERING_VER ${ignition-rendering5_VERSION_MAJOR}) ign_find_package(ignition-msgs7 REQUIRED) set(IGN_MSGS_VER ${ignition-msgs7_VERSION_MAJOR}) +#-------------------------------------- +# Find ignition-tools +ign_find_package(ignition-tools QUIET) +if (ignition-tools_FOUND) + set (HAVE_IGN_TOOLS TRUE) +endif() + #-------------------------------------- # Find if ign command is available find_program(HAVE_IGN_TOOLS ign) +#-------------------------------------- +# Find ignition-utils +ign_find_package(ignition-utils1 REQUIRED COMPONENTS cli) +set(IGN_UTILS_VER ${ignition-utils1_VERSION_MAJOR}) + #-------------------------------------- # Find QT ign_find_package (Qt5 @@ -79,8 +91,9 @@ ign_find_package (Qt5 Quick QuickControls2 Widgets + Qml REQUIRED - PKGCONFIG "Qt5Core Qt5Quick Qt5QuickControls2 Qt5Widgets" + PKGCONFIG "Qt5Core Qt5Quick Qt5Qml Qt5QuickControls2 Qt5Widgets" ) set(IGNITION_GUI_PLUGIN_INSTALL_DIR @@ -92,11 +105,6 @@ set(IGNITION_GUI_PLUGIN_INSTALL_DIR #============================================================================ ign_configure_build(QUIT_IF_BUILD_ERRORS) -#============================================================================ -# ign command line support -#============================================================================ -add_subdirectory(conf) - #============================================================================ # Create package information #============================================================================ diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt deleted file mode 100644 index 6c32a6a7c..000000000 --- a/conf/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -set(ign_library_path "${CMAKE_BINARY_DIR}/src/cmd/cmdgui${PROJECT_VERSION_MAJOR}") - -# Generate a configuration file for internal testing. -# Note that the major version of the library is included in the name. -# Ex: gui0.yaml -configure_file( - "gui.yaml.in" - "${CMAKE_BINARY_DIR}/test/conf/gui${PROJECT_VERSION_MAJOR}.yaml" @ONLY) - -set(ign_library_path "${CMAKE_INSTALL_PREFIX}/lib/ruby/ignition/cmdgui${PROJECT_VERSION_MAJOR}") - -# Generate a configuration file. -# Note that the major version of the library is included in the name. -# Ex: gui0.yaml -configure_file( - "gui.yaml.in" - "${CMAKE_CURRENT_BINARY_DIR}/gui${PROJECT_VERSION_MAJOR}.yaml" @ONLY) - -# Install the yaml configuration files in an unversioned location. -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/gui${PROJECT_VERSION_MAJOR}.yaml DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/ignition/) diff --git a/src/Application.cc b/src/Application.cc index 57104b09b..92ba97869 100644 --- a/src/Application.cc +++ b/src/Application.cc @@ -315,8 +315,17 @@ bool Application::LoadPlugin(const std::string &_filename, // Add default folder and install folder std::string home; common::env(IGN_HOMEDIR, home); - systemPaths.AddPluginPaths(home + "/.ignition/gui/plugins:" + - IGN_GUI_PLUGIN_INSTALL_DIR); + +#ifdef _WIN32 + std::string delimiter = ";"; +#else + std::string delimiter = ":"; +#endif + + systemPaths.AddPluginPaths( + common::joinPaths(home, ".ignition", "gui", "plugins") + + delimiter + + IGN_GUI_PLUGIN_INSTALL_DIR); auto pathToLib = systemPaths.FindSharedLibrary(_filename); if (pathToLib.empty()) @@ -589,7 +598,7 @@ std::vector>> // 3. ~/.ignition/gui/plugins std::string home; common::env(IGN_HOMEDIR, home); - paths.push_back(home + "/.ignition/gui/plugins"); + paths.push_back(common::joinPaths(home, ".ignition", "gui", "plugins")); // 4. Install path paths.push_back(IGN_GUI_PLUGIN_INSTALL_DIR); @@ -610,8 +619,14 @@ std::vector>> // All we verify is that the file starts with "lib", any further // checks would require loading the plugin. +#ifdef _WIN32 + if (plugin.find(".lib") != std::string::npos) +#else if (plugin.find("lib") == 0) +#endif + { ps.push_back(plugin); + } } plugins.push_back(std::make_pair(path, ps)); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4a5040b4c..63c876790 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,7 +6,6 @@ set (sources ${CMAKE_CURRENT_SOURCE_DIR}/Dialog.cc ${CMAKE_CURRENT_SOURCE_DIR}/DragDropModel.cc ${CMAKE_CURRENT_SOURCE_DIR}/Helpers.cc - ${CMAKE_CURRENT_SOURCE_DIR}/ign.cc ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.cc ${CMAKE_CURRENT_SOURCE_DIR}/PlottingInterface.cc ${CMAKE_CURRENT_SOURCE_DIR}/Plugin.cc @@ -15,16 +14,15 @@ set (sources ) set (gtest_sources - Application_TEST - Conversions_TEST - DragDropModel_TEST - Helpers_TEST - GuiEvents_TEST - ign_TEST - MainWindow_TEST - PlottingInterface_TEST - Plugin_TEST - SearchModel_TEST + Application_TEST.cc + Conversions_TEST.cc + DragDropModel_TEST.cc + Helpers_TEST.cc + GuiEvents_TEST.cc + MainWindow_TEST.cc + PlottingInterface_TEST.cc + Plugin_TEST.cc + SearchModel_TEST.cc ) if (MSVC) diff --git a/src/MainWindow.cc b/src/MainWindow.cc index d7d62c597..2c3dee08a 100644 --- a/src/MainWindow.cc +++ b/src/MainWindow.cc @@ -86,7 +86,9 @@ MainWindow::MainWindow() return; } - App()->setWindowIcon(QIcon(":/qml/images/ignition_logo_50x50.png")); + App()->setWindowIcon(QIcon( + common::joinPaths( + ":", "qml", "images", "ignition_logo_50x50.png").c_str())); } ///////////////////////////////////////////////// @@ -103,8 +105,13 @@ QStringList MainWindow::PluginListModel() const { for (auto const &plugin : path.second) { +#ifndef _WIN32 // Remove lib and .so auto pluginName = plugin.substr(3, plugin.find(".") - 3); +#else + // Remove lib and .dll + auto pluginName = plugin.substr(0, plugin.find(".")); +#endif // Split WWWCamelCase3D -> WWW Camel Case 3D std::regex reg("(\\B[A-Z][a-z])|(\\B[0-9])"); diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index 45245a12e..4436e2985 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -1,13 +1,189 @@ -# Generate a the ruby script. +# Collect source files into the "sources" variable and unit test files into the +# "gtest_sources" variable. +ign_get_libsources_and_unittests(sources gtest_sources) + +add_library(ign STATIC ign.cc) +target_include_directories(ign PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(ign PUBLIC + ${PROJECT_LIBRARY_TARGET_NAME} + ignition-common${IGN_COMMON_VER}::ignition-common${IGN_COMMON_VER} +) + +set(gui_executable ign-gui) +add_executable(${gui_executable} gui_main.cc) +target_link_libraries(${gui_executable} + ign + ignition-utils${IGN_UTILS_VER}::cli + ignition-common${IGN_COMMON_VER}::ignition-common${IGN_COMMON_VER} +) + +set(EXE_INSTALL_DIR "${IGN_LIB_INSTALL_DIR}/ignition/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}") + +install( + TARGETS + ${gui_executable} + DESTINATION + ${EXE_INSTALL_DIR} +) + +# Build the unit tests. +ign_build_tests(TYPE UNIT SOURCES ${gtest_sources} + TEST_LIST test_list + LIB_DEPS + ${EXTRA_TEST_LIB_DEPS} + ignition-common${IGN_COMMON_VER}::ignition-common${IGN_COMMON_VER} +) + +foreach(test ${test_list}) + target_link_libraries(${test} ign) + + # Inform each test of its output directory so it knows where to call the + # auxiliary files from. Using a generator expression here is useful for + # multi-configuration generators, like Visual Studio. + target_compile_definitions(${test} PRIVATE + "DETAIL_IGN_TRANSPORT_TEST_DIR=\"$\"" + "IGN_TEST_LIBRARY_PATH=\"$\"") + +endforeach() + +#=============================================================================== +# Generate the ruby script and yaml configuration files for installation. # Note that the major version of the library is included in the name. -if (APPLE) - set(IGN_LIBRARY_NAME lib${PROJECT_NAME_LOWER}.dylib) -else() - set(IGN_LIBRARY_NAME lib${PROJECT_NAME_LOWER}.so) -endif() +# Ex: gui5.yaml +# Ex: cmdgui5.rb +set(ign_library_path "${CMAKE_INSTALL_PREFIX}/lib/ruby/ignition/cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}") +# installed yaml file contains no generator expressions, so it can be configured directly +set(conf_yaml_configured "${CMAKE_CURRENT_BINARY_DIR}/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml") configure_file( - "cmdgui.rb.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmdgui${PROJECT_VERSION_MAJOR}.rb" @ONLY) + "${IGN_DESIGNATION}.yaml.in" + "${conf_yaml_configured}" @ONLY) + +# Install the yaml configuration files in an unversioned location. +install(FILES ${conf_yaml_configured} + DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/ignition/) + + +# Set the exe_location variable to the relative path to the executable file +# within the install directory structure. +set(gui_exe_location "../../../${EXE_INSTALL_DIR}/$") + +set(cmd_script_configured "${CMAKE_CURRENT_BINARY_DIR}/cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.rb.configured") +configure_file( + "cmd${IGN_DESIGNATION}.rb.in" + "${cmd_script_configured}" + @ONLY) + +set(cmd_script_generated "${CMAKE_CURRENT_BINARY_DIR}/$/cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.rb") +file(GENERATE + OUTPUT "${cmd_script_generated}" + INPUT "${cmd_script_configured}") # Install the ruby command line library in an unversioned location. -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmdgui${PROJECT_VERSION_MAJOR}.rb DESTINATION lib/ruby/ignition) +install(FILES ${cmd_script_generated} DESTINATION lib/ruby/ignition) + +#=============================================================================== +# Generate the ruby script and yaml configuration files for internal testing. +# Note that the major version of the library is included in the name. +# Ex: gui1.yaml +# Ex: cmdgui1.rb + +# test yaml file contains generator expressions +set(ign_library_path "${CMAKE_BINARY_DIR}/test/lib/$/ruby/ignition/cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}") +configure_file( + "${IGN_DESIGNATION}.yaml.in" + "${CMAKE_CURRENT_BINARY_DIR}/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml.configured" @ONLY) + +file(GENERATE + OUTPUT "${CMAKE_BINARY_DIR}/test/conf/$/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml" + INPUT "${CMAKE_CURRENT_BINARY_DIR}/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml.configured") + + +# Set the exe_location variable to the full path of the executable file within +# the build directory. +set(gui_exe_location "$") + +set(cmd_script_configured_test "${CMAKE_CURRENT_BINARY_DIR}/test_cmd${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.rb.configured") +configure_file( + "cmd${IGN_DESIGNATION}.rb.in" + "${cmd_script_configured_test}" + @ONLY) + +set(cmd_script_generated_test "${ign_library_path}.rb") +file(GENERATE + OUTPUT "${cmd_script_generated_test}" + INPUT "${cmd_script_configured_test}") + + +if (TARGET UNIT_ign_TEST) + add_dependencies(UNIT_ign_TEST + ${gui_executable} + Publisher + TopicEcho + ) + + set(_env_vars) + list(APPEND _env_vars "IGN_CONFIG_PATH=${CMAKE_BINARY_DIR}/test/conf/$") + list(APPEND _env_vars "IGN_GUI_PLUGIN_PATH=$") + if(WIN32) + list(APPEND _env_vars "QT_QPA_PLATFORM_PLUGIN_PATH=C:/vcpkg/installed/x64-windows/plugins/platforms") + list(APPEND _env_vars "QT_QPA_PLATFORM=offscreen") + endif() + set_tests_properties( + UNIT_ign_TEST + PROPERTIES + ENVIRONMENT "${_env_vars}" + ) +endif() + +if(Qt5_FOUND AND WIN32 AND TARGET Qt5::qmake AND NOT TARGET Qt5::windeployqt) + get_target_property(_qt5_qmake_location + Qt5::qmake IMPORTED_LOCATION + ) + + execute_process( + COMMAND "${_qt5_qmake_location}" -query QT_INSTALL_PREFIX + RESULT_VARIABLE return_code + OUTPUT_VARIABLE qt5_install_prefix + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # Anaconda3 Location + set(imported_location "${qt5_install_prefix}/bin/windeployqt.exe") + # vcpkg location + set(imported_location2 "${qt5_install_prefix}/tools/qt5/bin/windeployqt.exe") + + if(EXISTS ${imported_location}) + set(QML_DIR ${qt5_install_prefix}/qml) + add_executable(Qt5::windeployqt IMPORTED) + + set_target_properties(Qt5::windeployqt PROPERTIES + IMPORTED_LOCATION ${imported_location} + ) + elseif(EXISTS ${imported_location2}) + set(QML_DIR ${qt5_install_prefix}/qml) + add_executable(Qt5::windeployqt IMPORTED) + + set_target_properties(Qt5::windeployqt PROPERTIES + IMPORTED_LOCATION ${imported_location2} + ) + else() + message("Not able to locate windeployqt.exe") + endif() +endif() + +if(TARGET Qt5::windeployqt) + # execute windeployqt in a tmp directory after build + add_custom_command(TARGET ${gui_executable} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt" + COMMAND set PATH=%PATH%$${qt5_install_prefix}/bin + COMMAND Qt5::windeployqt --dir "${CMAKE_CURRENT_BINARY_DIR}/windeployqt" --qmldir ${QML_DIR} ${CMAKE_BINARY_DIR}/bin/$/ign-gui.exe --quick --qml + ) + + # copy deployment directory during installation + install( + DIRECTORY + "${CMAKE_CURRENT_BINARY_DIR}/windeployqt/" + DESTINATION ${IGN_LIB_INSTALL_DIR}/ignition/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}/ + ) +endif() diff --git a/src/cmd/cmdgui.rb.in b/src/cmd/cmdgui.rb.in index 2b766662f..83984a62d 100644 --- a/src/cmd/cmdgui.rb.in +++ b/src/cmd/cmdgui.rb.in @@ -1,6 +1,6 @@ -#!/usr/bin/ruby +#!/usr/bin/env ruby -# Copyright (C) 2017 Open Source Robotics Foundation +# Copyright (C) 2014 Open Source Robotics Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,193 +14,52 @@ # See the License for the specific language governing permissions and # limitations under the License. -# We use 'dl' for Ruby <= 1.9.x and 'fiddle' for Ruby >= 2.0.x -if RUBY_VERSION.split('.')[0] < '2' - require 'dl' - require 'dl/import' - include DL -else - require 'fiddle' - require 'fiddle/import' - include Fiddle -end - -require 'optparse' +require 'open3' # Constants. -LIBRARY_NAME = '@IGN_LIBRARY_NAME@' LIBRARY_VERSION = '@PROJECT_VERSION_FULL@' -COMMON_OPTIONS = - " -h [ --help ] Print this help message.\n"\ - " \n" + - " --force-version Use a specific library version.\n"\ - " \n" + - ' --versions Show the available versions.' -COMMANDS = { 'gui' => - "Ignition GUI tool.\n\n" + - " ign gui [options]\n\n" + - "Options:\n\n" + - " -l [ --list ] List all available plugins.\n" + - "\n" + - " -s [ --standalone ] arg Run a plugin as a standalone window.\n" + - " Give the plugin filename as an argument.\n" + - "\n" + - " -c [ --config ] arg Open the main window with a configuration file.\n" + - " Give the configuration file path as an argument.\n" + - "\n" + - " -v [ --verbose ] [arg] Adjust the level of console output (0~4).\n" + - " The default verbosity is 1, use -v without\n"\ - " arguments for level 3.\n"\ - "\n" + - COMMON_OPTIONS, - } +COMMANDS = { + "gui" => "@gui_exe_location@", +} # # Class for the Ignition gui command line tools. # class Cmd - # - # Return a structure describing the options. - # - def parse(args) - options = { - 'verbose' => '1' - } - - usage = COMMANDS[args[0]] - - # Read the command line arguments. - opt_parser = OptionParser.new do |opts| - opts.banner = usage - - opts.on('-h', '--help', 'Print this help message') do - puts usage - exit(0) - end - opts.on('-l', '--list', 'List plugins') do |l| - options['list'] = l - end - opts.on('-s standalone', '--standalone', String, - 'Run a plugin standalone') do |s| - options['standalone'] = s - end - opts.on('-c config', '--config', String, - 'Load a configuration file') do |c| - options['config'] = c - end - opts.on('-v [verbose]', '--verbose [verbose]', String, - 'Adjust level of console output') do |v| - options['verbose'] = v || '3' - end - - end - begin - opt_parser.parse!(args) - rescue - puts usage - exit(-1) - end - - # Handle empty window case - # * no options or - # * none of the following: - # - standalone - # - config - # - list - if options.empty? || (!options.key?('standalone') && - !options.key?('config') && - !options.key?('list')) - options['emptywindow'] = '' - end - - # Check that there is at least one command we can handle - if ARGV.empty? || !COMMANDS.key?(ARGV[0]) - puts usage - exit(-1) - end - - options['command'] = ARGV[0] - - options - end # parse() - def execute(args) - options = parse(args) - - # puts 'Parsed:' - # puts options - - # Read the plugin that handles the command. - plugin = LIBRARY_NAME - conf_version = LIBRARY_VERSION - - begin - Importer.dlload plugin - rescue DLError - puts "Library error: [#{plugin}] not found." - exit(-1) - end - - # Read the library version. - Importer.extern 'char* ignitionVersion()' - begin - plugin_version = Importer.ignitionVersion.to_s - rescue DLError - puts "Library error: Problem running 'ignitionVersion()' from #{plugin}." - exit(-1) + command = args[0] + exe_name = COMMANDS[command] + + if exe_name[0] == '/' + # If the first character is a slash, we'll assume that we've been given an + # absolute path to the executable. This is only used during test mode. + else + # We're assuming that the library path is relative to the current + # location of this script. + exe_name = File.expand_path(File.join(File.dirname(__FILE__), exe_name)) end + conf_version = LIBRARY_VERSION + exe_version = `#{exe_name} --version`.strip # Sanity check: Verify that the version of the yaml file matches the version # of the library that we are using. - unless plugin_version.eql? conf_version + unless exe_version.eql? conf_version puts "Error: Version mismatch. Your configuration file version is - [#{conf_version}] but #{plugin} version is [#{plugin_version}]." + [#{conf_version}] but #{exe_name} version is [#{exe_version}]." exit(-1) end - begin - case options['command'] - when 'gui' - - # Options which don't open windows - if options.key?('list') - Importer.extern 'void cmdPluginList()' - Importer.cmdPluginList - # Options which open windows - elsif options.key?('standalone') or - options.key?('config') or - options.key?('emptywindow') - - # Global configurations - if options.key?('verbose') - Importer.extern 'void cmdVerbose(const char *)' - Importer.cmdVerbose(options['verbose']) - end - - # Open specific window - if options.key?('standalone') - Importer.extern 'void cmdStandalone(const char *)' - Importer.cmdStandalone(options['standalone']) - elsif options.key?('config') - Importer.extern 'void cmdConfig(const char *)' - Importer.cmdConfig(options['config']) - elsif options.key?('emptywindow') - Importer.extern 'void cmdEmptyWindow()' - Importer.cmdEmptyWindow() - end - - # Unknown command - else - puts 'Command error: I do not have an implementation '\ - 'for this command.' + # Drop command from list of arguments + Open3.popen2e(exe_name, *args[1..-1]) do |_in, out_err, wait_thr| + begin + out_err.each do |line| + print line end - else - puts 'Command error: I do not have an implementation for '\ - "command [ign #{options['command']}]." + exit(wait_thr.value.exitstatus) + rescue Interrupt => e + print e.message + exit(-1) end - rescue - puts "Library error: Problem running [#{options['command']}]() "\ - "from #{plugin}." end end end diff --git a/conf/gui.yaml.in b/src/cmd/gui.yaml.in similarity index 100% rename from conf/gui.yaml.in rename to src/cmd/gui.yaml.in diff --git a/src/cmd/gui_main.cc b/src/cmd/gui_main.cc new file mode 100644 index 000000000..4a7ce297d --- /dev/null +++ b/src/cmd/gui_main.cc @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include +#include + +#include + +#include "ignition/gui/config.hh" +#include "ignition/gui/ign.hh" +#include "ign.hh" + +////////////////////////////////////////////////// +/// \brief Enumeration of available commands +enum class GUICommand +{ + kNone, + kGUIConfig, + kGUIHelp, + kGUIList, + kGUIStandalone +}; + +////////////////////////////////////////////////// +/// \brief Structure to hold all available topic options +struct GUIOptions +{ + /// \brief Command to execute + GUICommand command{GUICommand::kNone}; + + /// \brief Plugin name to open + std::string pluginName; + + /// \brief Config file to load + std::string configFile; + + /// Verbosity level + int verboseLevel = 1; +}; + +void runGUICommand(const GUIOptions &_opt) +{ + cmdVerbose(_opt.verboseLevel); + + switch(_opt.command) + { + case GUICommand::kGUIList: + cmdPluginList(); + break; + case GUICommand::kGUIConfig: + cmdConfig(_opt.configFile.c_str()); + break; + case GUICommand::kGUIStandalone: + cmdStandalone(_opt.pluginName.c_str()); + break; + case GUICommand::kNone: + default: + cmdEmptyWindow(); + } +} + +void addGUIFlags(CLI::App &_app) +{ + auto opt = std::make_shared(); + + _app.add_option("-v,--verbose", + opt->verboseLevel, + "Adjust the level of console output (0~4).\n" + "The default verbosity is 1, use -v without \n" + "arguments for level 3."); + + _app.add_option("-s,--standalone", + opt->pluginName, + "Run a plugin as a standalone window.\n" + "Give the plugin filename as an argument."); + + _app.add_option("-c,--config", + opt->configFile, + "Open the main window with a configuration file.\n" + "Give the configuration file path as an argument."); + + _app.add_flag_callback("-l,--list", + [opt](){ + opt->command = GUICommand::kGUIList; + }, "List available GUI plugins"); + + _app.callback([&_app, opt](){ + if (!opt->pluginName.empty()) + { + opt->command = GUICommand::kGUIStandalone; + } + if (!opt->configFile.empty()) + { + opt->command = GUICommand::kGUIConfig; + } + runGUICommand(*opt); + }); +} + +////////////////////////////////////////////////// +int main(int argc, char** argv) +{ + CLI::App app{"Introspect Ignition GUI"}; + + app.set_help_all_flag("--help-all", "Show all help"); + + app.add_flag_callback("--version", [](){ + std::cout << strdup(IGNITION_GUI_VERSION_FULL) << std::endl; + throw CLI::Success(); + }); + + addGUIFlags(app); + CLI11_PARSE(app, argc, argv); +} diff --git a/src/ign.cc b/src/cmd/ign.cc similarity index 79% rename from src/ign.cc rename to src/cmd/ign.cc index 12ef07713..7e0dcd907 100644 --- a/src/ign.cc +++ b/src/cmd/ign.cc @@ -18,14 +18,15 @@ #include #include +#include #include #include "ignition/gui/Application.hh" #include "ignition/gui/config.hh" -#include "ignition/gui/Export.hh" #include "ignition/gui/ign.hh" #include "ignition/gui/MainWindow.hh" +#include "ign.hh" int g_argc = 1; char* g_argv[] = @@ -34,13 +35,13 @@ char* g_argv[] = }; ////////////////////////////////////////////////// -extern "C" IGNITION_GUI_VISIBLE char *ignitionVersion() +extern "C" char *ignitionVersion() { return strdup(IGNITION_GUI_VERSION_FULL); } ////////////////////////////////////////////////// -extern "C" IGNITION_GUI_VISIBLE void cmdPluginList() +extern "C" void cmdPluginList() { ignition::gui::Application app(g_argc, g_argv); @@ -52,18 +53,32 @@ extern "C" IGNITION_GUI_VISIBLE void cmdPluginList() for (unsigned int i = 0; i < path.second.size(); ++i) { if (i == path.second.size() - 1) +#ifndef _WIN32 std::cout << "└── " << path.second[i] << std::endl; +#else + std::cout << " -- " << path.second[i] << std::endl; +#endif else +#ifndef _WIN32 std::cout << "├── " << path.second[i] << std::endl; +#else + std::cout << "|-- " << path.second[i] << std::endl; +#endif } if (path.second.empty()) + { +#ifndef _WIN32 std::cout << "└── No plugins" << std::endl; +#else + std::cout << " -- No plugins" << std::endl; +#endif + } } } ////////////////////////////////////////////////// -extern "C" IGNITION_GUI_VISIBLE void cmdStandalone(const char *_filename) +extern "C" void cmdStandalone(const char *_filename) { ignition::gui::Application app(g_argc, g_argv, ignition::gui::WindowType::kDialog); @@ -77,7 +92,7 @@ extern "C" IGNITION_GUI_VISIBLE void cmdStandalone(const char *_filename) } ////////////////////////////////////////////////// -extern "C" IGNITION_GUI_VISIBLE void cmdConfig(const char *_config) +extern "C" void cmdConfig(const char *_config) { ignition::gui::Application app(g_argc, g_argv); @@ -95,13 +110,13 @@ extern "C" IGNITION_GUI_VISIBLE void cmdConfig(const char *_config) } ////////////////////////////////////////////////// -extern "C" IGNITION_GUI_VISIBLE void cmdVerbose(const char *_verbosity) +extern "C" void cmdVerbose(int _verbosity) { - ignition::common::Console::SetVerbosity(std::atoi(_verbosity)); + ignition::common::Console::SetVerbosity(_verbosity); } ////////////////////////////////////////////////// -extern "C" IGNITION_GUI_VISIBLE void cmdEmptyWindow() +extern "C" void cmdEmptyWindow() { ignition::gui::Application app(g_argc, g_argv); @@ -116,7 +131,7 @@ extern "C" IGNITION_GUI_VISIBLE void cmdEmptyWindow() } ////////////////////////////////////////////////// -extern "C" IGNITION_GUI_VISIBLE void cmdSetStyleFromFile( +extern "C" void cmdSetStyleFromFile( const char * /*_filename*/) { // ignition::gui::setStyleFromFile(std::string(_filename)); diff --git a/src/cmd/ign.hh b/src/cmd/ign.hh new file mode 100644 index 000000000..b562f9d37 --- /dev/null +++ b/src/cmd/ign.hh @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_GUI_SRC_CMD_IGN_HH_ +#define IGNITION_GUI_SRC_CMD_IGN_HH_ + + +/// \brief Set the verbosity level +/// \param[in] _verbosity Verbosity level +extern "C" void cmdVerbose(int _verbosity); + +#endif diff --git a/src/ign_TEST.cc b/src/cmd/ign_TEST.cc similarity index 71% rename from src/ign_TEST.cc rename to src/cmd/ign_TEST.cc index 80a240ac8..8c9fd6288 100644 --- a/src/ign_TEST.cc +++ b/src/cmd/ign_TEST.cc @@ -21,6 +21,7 @@ #include +#include #include #include "test_config.h" // NOLINT(build/include) @@ -53,9 +54,22 @@ std::string custom_exec_str(std::string _cmd) } // See https://github.com/ignitionrobotics/ign-gui/issues/75 -TEST(CmdLine, IGN_UTILS_TEST_ENABLED_ONLY_ON_LINUX(list)) +TEST(CmdLine, list) { - std::string output = custom_exec_str("ign gui -l"); + std::string ignConfigPath; + ignition::common::env("IGN_CONFIG_PATH", ignConfigPath, true); +#ifndef _WIN32 + std::string cmd = std::string("IGN_CONFIG_PATH=") + ignConfigPath + + " ign gui -l"; +#else + std::string pathstr; + std::string ign = std::string(IGN_PATH) + "/ign.rb"; + ignition::common::env("PATH", pathstr, true); + std::string cmd = std::string("set IGN_CONFIG_PATH=") + ignConfigPath + + " && set \"PATH=" + pathstr + "\" && " + ign + " gui -l -v 4"; +#endif + std::string output = custom_exec_str(cmd); EXPECT_NE(output.find("TopicEcho"), std::string::npos) << output; EXPECT_NE(output.find("Publisher"), std::string::npos) << output; + } diff --git a/test/test_config.h.in b/test/test_config.h.in index 0ccf50434..92489025a 100644 --- a/test/test_config.h.in +++ b/test/test_config.h.in @@ -1,5 +1,7 @@ #define PROJECT_SOURCE_PATH "${PROJECT_SOURCE_DIR}" #define PROJECT_BINARY_PATH "${CMAKE_BINARY_DIR}" +#define IGN_CONFIG_PATH "@CMAKE_BINARY_DIR@/test/conf" +#define IGN_PATH "@IGNITION-TOOLS_BINARY_DIRS@" #if (_MSC_VER >= 1400) // Visual Studio 2005 #include