33 from __future__ 
import print_function
 
   35 from argparse 
import ArgumentParser, SUPPRESS
 
   46     def __init__(self, qtgui_path, invoked_filename=None, settings_filename=None):
 
   48         if invoked_filename 
is None:
 
   49             invoked_filename = os.path.abspath(__file__)
 
   50         Main.main_filename = invoked_filename
 
   51         if settings_filename 
is None:
 
   52             settings_filename = 
'qt_gui' 
   62             from dbus.mainloop.glib 
import DBusGMainLoop
 
   63             DBusGMainLoop(set_as_default=
True)
 
   70             except dbus.exceptions.DBusException:
 
   75     def add_arguments(self, parser, standalone=False, plugin_argument_provider=None):
 
   76         common_group = parser.add_argument_group(
'Options for GUI instance')
 
   77         common_group.add_argument(
 
   78             '-b', 
'--qt-binding', dest=
'qt_binding', type=str, metavar=
'BINDING',
 
   79             help=
'choose Qt bindings to be used [pyqt|pyside]')
 
   80         common_group.add_argument(
 
   81             '--clear-config', dest=
'clear_config', default=
False, action=
'store_true',
 
   82             help=
'clear the configuration (including all perspectives and plugin settings)')
 
   84             common_group.add_argument(
 
   85                 '-f', 
'--freeze-layout', dest=
'freeze_layout', action=
'store_true',
 
   86                 help=
'freeze the layout of the GUI (prevent rearranging widgets, disable ' 
   88         common_group.add_argument(
 
   89             '--force-discover', dest=
'force_discover', default=
False, action=
'store_true',
 
   90             help=
'force a rediscover of plugins')
 
   91         common_group.add_argument(
 
   92             '-h', 
'--help', action=
'help', help=
'show this help message and exit')
 
   93         common_group.add_argument(
 
   94                 '-ht', 
'--hide-title', dest=
'hide_title', action=
'store_true',
 
   95                 help=
'hide the title label, the icon, and the help button (combine with -l and -f ' 
   96                      'to eliminate the entire title bar and reclaim the space)')
 
   98             common_group.add_argument(
 
   99                 '-l', 
'--lock-perspective', dest=
'lock_perspective', action=
'store_true',
 
  100                 help=
'lock the GUI to the used perspective (hide menu bar and close buttons of ' 
  103             common_group.add_argument(
 
  104                 '-p', 
'--perspective', dest=
'perspective', type=str, metavar=
'PERSPECTIVE',
 
  105                 help=
'start with this named perspective')
 
  106             common_group.add_argument(
 
  107                 '--perspective-file', dest=
'perspective_file', type=str,
 
  108                 metavar=
'PERSPECTIVE_FILE', help=
'start with a perspective loaded from a file')
 
  109         common_group.add_argument(
 
  110             '--reload-import', dest=
'reload_import', default=
False, action=
'store_true',
 
  111             help=
'reload every imported module')
 
  113             common_group.add_argument(
 
  114                 '-s', 
'--standalone', dest=
'standalone_plugin', type=str, metavar=
'PLUGIN',
 
  115                 help=
'start only this plugin (implies -l). To pass arguments to the plugin use ' 
  117         common_group.add_argument(
 
  118             '-t', 
'--on-top', dest=
'on_top', default=
False, action=
'store_true',
 
  119             help=
'set window mode to always on top')
 
  120         common_group.add_argument(
 
  121             '-v', 
'--verbose', dest=
'verbose', default=
False, action=
'store_true',
 
  122             help=
'output qDebug messages')
 
  125             common_group.add_argument(
 
  126                 '--args', dest=
'plugin_args', nargs=
'*', type=str,
 
  127                 help=
'arbitrary arguments which are passes to the plugin ' 
  128                      '(only with -s, --command-start-plugin or --embed-plugin). ' 
  129                      'It must be the last option since it collects all following options.')
 
  131             group = parser.add_argument_group(
 
  132                 'Options to query information without starting a GUI instance',
 
  133                 'These options can be used to query information about valid arguments for various ' 
  136                 '--list-perspectives', dest=
'list_perspectives', action=
'store_true',
 
  137                 help=
'list available perspectives')
 
  139                 '--list-plugins', dest=
'list_plugins', action=
'store_true',
 
  140                 help=
'list available plugins')
 
  141             parser.add_argument_group(group)
 
  143             group = parser.add_argument_group(
 
  144                 'Options to operate on a running GUI instance',
 
  145                 'These options can be used to perform actions on a running GUI instance.')
 
  147                 '--command-pid', dest=
'command_pid', type=int, metavar=
'PID',
 
  148                 help=
'pid of the GUI instance to operate on, defaults to oldest running GUI ' 
  151                 '--command-start-plugin', dest=
'command_start_plugin', type=str, metavar=
'PLUGIN',
 
  154                 '--command-switch-perspective', dest=
'command_switch_perspective', type=str,
 
  155                 metavar=
'PERSPECTIVE', help=
'switch perspective')
 
  157                 group.description = 
'These options are not available since DBus is not available!' 
  158                 for o 
in group._group_actions:
 
  160             parser.add_argument_group(group)
 
  162             group = parser.add_argument_group(
 
  163                 'Special options for embedding widgets from separate processes',
 
  164                 'These options should never be used on the CLI but only from the GUI code itself.')
 
  166                 '--embed-plugin', dest=
'embed_plugin', type=str, metavar=
'PLUGIN',
 
  167                 help=
'embed a plugin into an already running GUI instance (requires all other ' 
  168                      '--embed-* options)')
 
  170                 '--embed-plugin-serial', dest=
'embed_plugin_serial', type=int, metavar=
'SERIAL',
 
  171                 help=
'serial number of plugin to be embedded ' 
  172                      '(requires all other --embed-* options)')
 
  174                 '--embed-plugin-address', dest=
'embed_plugin_address', type=str, metavar=
'ADDRESS',
 
  175                 help=
'dbus server address of the GUI instance to embed plugin into ' 
  176                      '(requires all other --embed-* options)')
 
  177             for o 
in group._group_actions:
 
  179             parser.add_argument_group(group)
 
  181         if plugin_argument_provider:
 
  182             plugin_argument_provider(parser)
 
  190         reload_importer.add_reload_path(os.path.join(os.path.dirname(__file__), *(
'..',) * 4))
 
  193         from python_qt_binding.QtGui 
import QIcon
 
  195         if QIcon.fromTheme(
'document-save').isNull() 
or \
 
  196            QIcon.fromTheme(
'document-open').isNull() 
or \
 
  197            QIcon.fromTheme(
'edit-cut').isNull() 
or \
 
  198            QIcon.fromTheme(
'object-flip-horizontal').isNull():
 
  199             if platform.system() == 
'Darwin' and \
 
  200                     '/usr/local/share/icons' not in QIcon.themeSearchPaths():
 
  201                 QIcon.setThemeSearchPaths(QIcon.themeSearchPaths() + [
'/usr/local/share/icons'])
 
  202             original_theme = QIcon.themeName()
 
  203             QIcon.setThemeName(
'Tango')
 
  204             if QIcon.fromTheme(
'document-save').isNull():
 
  205                 QIcon.setThemeName(original_theme)
 
  208         from python_qt_binding.QtCore 
import Qt
 
  209         from python_qt_binding.QtWidgets 
import QApplication
 
  210         app = QApplication(argv)
 
  211         app.setAttribute(Qt.AA_DontShowIconsInMenus, 
False)
 
  214     def main(self, argv=None, standalone=None, plugin_argument_provider=None,
 
  215              plugin_manager_settings_prefix=''):
 
  225             if '--args' in arguments:
 
  226                 index = arguments.index(
'--args')
 
  227                 plugin_args = arguments[index + 1:]
 
  228                 arguments = arguments[0:index + 1]
 
  230         parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=
False)
 
  232                            plugin_argument_provider=plugin_argument_provider)
 
  233         self.
_options = parser.parse_args(arguments)
 
  237             parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=
False)
 
  239             self.
_options, plugin_args = parser.parse_known_args(arguments)
 
  241         self.
_options.plugin_args = plugin_args
 
  246             self.
_options.lock_perspective = 
False 
  248             self.
_options.perspective_file = 
None 
  249             self.
_options.standalone_plugin = standalone
 
  250             self.
_options.list_perspectives = 
False 
  253             self.
_options.command_start_plugin = 
None 
  254             self.
_options.command_switch_perspective = 
None 
  256             self.
_options.embed_plugin_serial = 
None 
  257             self.
_options.embed_plugin_address = 
None 
  262                     not self.
_options.standalone_plugin 
and \
 
  263                     not self.
_options.command_start_plugin 
and \
 
  266                     'Option --args can only be used together with either --standalone, ' 
  267                     '--command-start-plugin or --embed-plugin option')
 
  271                     'Option --freeze_layout can only be used together with the ' 
  272                     '--lock_perspective option')
 
  274             list_options = (self.
_options.list_perspectives, self.
_options.list_plugins)
 
  275             list_options_set = [opt 
for opt 
in list_options 
if opt 
is not False]
 
  276             if len(list_options_set) > 1:
 
  277                 raise RuntimeError(
'Only one --list-* option can be used at a time')
 
  280                 self.
_options.command_start_plugin, self.
_options.command_switch_perspective)
 
  281             command_options_set = [opt 
for opt 
in command_options 
if opt 
is not None]
 
  284                     'Without DBus support the --command-* options are not available')
 
  285             if len(command_options_set) > 1:
 
  287                     'Only one --command-* option can be used at a time (except --command-pid ' 
  288                     'which is optional)')
 
  289             if len(command_options_set) == 0 
and self.
_options.command_pid 
is not None:
 
  291                     'Option --command_pid can only be used together with an other --command-* ' 
  297             embed_options_set = [opt 
for opt 
in embed_options 
if opt 
is not None]
 
  299                 raise RuntimeError(
'Without DBus support the --embed-* options are not available')
 
  300             if len(embed_options_set) > 0 
and len(embed_options_set) < len(embed_options):
 
  301                 raise RuntimeError(
'Missing option(s) - all \'--embed-*\' options must be set')
 
  303             if len(embed_options_set) > 0 
and self.
_options.clear_config:
 
  305                     'Option --clear-config can only be used without any --embed-* option')
 
  307             groups = (list_options_set, command_options_set, embed_options_set)
 
  308             groups_set = [opt 
for opt 
in groups 
if len(opt) > 0]
 
  309             if len(groups_set) > 1:
 
  311                     'Options from different groups (--list, --command, --embed) can not be used ' 
  314             perspective_options = (self.
_options.perspective, self.
_options.perspective_file)
 
  315             perspective_options_set = [opt 
for opt 
in perspective_options 
if opt 
is not None]
 
  316             if len(perspective_options_set) > 1:
 
  317                 raise RuntimeError(
'Only one --perspective-* option can be used at a time')
 
  319             if self.
_options.perspective_file 
is not None and \
 
  320                     not os.path.isfile(self.
_options.perspective_file):
 
  321                 raise RuntimeError(
'Option --perspective-file must reference existing file')
 
  323         except RuntimeError 
as e:
 
  330         if self.
_options.standalone_plugin 
is not None:
 
  331             self.
_options.lock_perspective = 
True 
  334         from .application_context 
import ApplicationContext
 
  335         context = ApplicationContext()
 
  340             from dbus 
import DBusException, Interface, SessionBus
 
  344             context.provide_app_dbus_interfaces = len(groups_set) == 0
 
  345             context.dbus_base_bus_name = 
'org.ros.qt_gui' 
  346             if context.provide_app_dbus_interfaces:
 
  347                 context.dbus_unique_bus_name = context.dbus_base_bus_name + 
'.pid%d' % os.getpid()
 
  350                 from .application_dbus_interface 
import ApplicationDBusInterface
 
  351                 _dbus_server = ApplicationDBusInterface(context.dbus_base_bus_name)  
 
  355         if len(command_options_set) > 0 
or len(embed_options_set) > 0:
 
  357             if self.
_options.command_pid 
is not None:
 
  358                 host_pid = self.
_options.command_pid
 
  361                     remote_object = SessionBus().get_object(
 
  362                         context.dbus_base_bus_name, 
'/Application')
 
  363                 except DBusException:
 
  366                     remote_interface = Interface(
 
  367                         remote_object, context.dbus_base_bus_name + 
'.Application')
 
  368                     host_pid = remote_interface.get_pid()
 
  369             if host_pid 
is not None:
 
  370                 context.dbus_host_bus_name = context.dbus_base_bus_name + 
'.pid%d' % host_pid
 
  373         if len(command_options_set) > 0:
 
  374             if self.
_options.command_start_plugin 
is not None:
 
  376                     remote_object = SessionBus().get_object(
 
  377                         context.dbus_host_bus_name, 
'/PluginManager')
 
  378                 except DBusException:
 
  379                     (rc, msg) = (1, 
'unable to communicate with GUI instance "%s"' %
 
  380                                  context.dbus_host_bus_name)
 
  382                     remote_interface = Interface(
 
  383                         remote_object, context.dbus_base_bus_name + 
'.PluginManager')
 
  384                     (rc, msg) = remote_interface.start_plugin(
 
  387                     print(
'qt_gui_main() started plugin "%s" in GUI "%s"' %
 
  388                           (msg, context.dbus_host_bus_name))
 
  390                     print(
'qt_gui_main() could not start plugin "%s" in GUI "%s": %s' %
 
  391                           (self.
_options.command_start_plugin, context.dbus_host_bus_name, msg))
 
  393             elif self.
_options.command_switch_perspective 
is not None:
 
  394                 remote_object = SessionBus().get_object(
 
  395                     context.dbus_host_bus_name, 
'/PerspectiveManager')
 
  396                 remote_interface = Interface(
 
  397                     remote_object, context.dbus_base_bus_name + 
'.PerspectiveManager')
 
  398                 remote_interface.switch_perspective(self.
_options.command_switch_perspective)
 
  399                 print(
'qt_gui_main() switched to perspective "%s" in GUI "%s"' %
 
  400                       (self.
_options.command_switch_perspective, context.dbus_host_bus_name))
 
  402             raise RuntimeError(
'Unknown command not handled')
 
  405         setattr(sys, 
'SELECT_QT_BINDING', self.
_options.qt_binding)
 
  406         from python_qt_binding 
import QT_BINDING
 
  408         from python_qt_binding.QtCore 
import (qDebug, qInstallMessageHandler,
 
  409                                               QSettings, Qt, QtCriticalMsg, QtDebugMsg)
 
  410         from python_qt_binding.QtCore 
import QtFatalMsg, QTimer, QtWarningMsg
 
  412         from python_qt_binding.QtGui 
import QIcon
 
  413         from python_qt_binding.QtWidgets 
import QAction
 
  415         from .about_handler 
import AboutHandler
 
  416         from .composite_plugin_provider 
import CompositePluginProvider
 
  417         from .container_manager 
import ContainerManager
 
  418         from .help_provider 
import HelpProvider
 
  419         from .icon_loader 
import get_icon
 
  420         from .main_window 
import MainWindow
 
  421         from .minimized_dock_widgets_toolbar 
import MinimizedDockWidgetsToolbar
 
  422         from .perspective_manager 
import PerspectiveManager
 
  423         from .plugin_manager 
import PluginManager
 
  426         if QT_BINDING != 
'pyside':
 
  427             def message_handler(type_, context, msg):
 
  428                 colored_output = 
'TERM' in os.environ 
and 'ANSI_COLORS_DISABLED' not in os.environ
 
  429                 cyan_color = 
'\033[36m' if colored_output 
else '' 
  430                 red_color = 
'\033[31m' if colored_output 
else '' 
  431                 reset_color = 
'\033[0m' if colored_output 
else '' 
  432                 if type_ == QtDebugMsg 
and self.
_options.verbose:
 
  433                     print(msg, file=sys.stderr)
 
  434                 elif type_ == QtWarningMsg:
 
  435                     print(cyan_color + msg + reset_color, file=sys.stderr)
 
  436                 elif type_ == QtCriticalMsg:
 
  437                     print(red_color + msg + reset_color, file=sys.stderr)
 
  438                 elif type_ == QtFatalMsg:
 
  439                     print(red_color + msg + reset_color, file=sys.stderr)
 
  441             qInstallMessageHandler(message_handler)
 
  447         settings = QSettings(
 
  449         if len(embed_options_set) == 0:
 
  453             main_window = MainWindow()
 
  455                 main_window.setWindowFlags(Qt.WindowStaysOnTopHint)
 
  457             main_window.statusBar()
 
  459             def sigint_handler(*args):
 
  460                 qDebug(
'\nsigint_handler()')
 
  462             signal.signal(signal.SIGINT, sigint_handler)
 
  466             timer.timeout.connect(
lambda: 
None)
 
  468             if not self.
_options.lock_perspective:
 
  469                 menu_bar = main_window.menuBar()
 
  470                 file_menu = menu_bar.addMenu(menu_bar.tr(
'&File'))
 
  471                 action = QAction(file_menu.tr(
'&Quit'), file_menu)
 
  472                 action.setIcon(QIcon.fromTheme(
'application-exit'))
 
  473                 action.triggered.connect(main_window.close)
 
  474                 file_menu.addAction(action)
 
  479             app.setQuitOnLastWindowClosed(
False)
 
  488         plugin_manager = PluginManager(
 
  489             plugin_provider, settings, context, settings_prefix=plugin_manager_settings_prefix)
 
  493             print(
'\n'.join(sorted(plugin_manager.get_plugins().values())))
 
  496         help_provider = HelpProvider()
 
  497         plugin_manager.plugin_help_signal.connect(help_provider.plugin_help_request)
 
  500         if main_window 
is not None:
 
  501             perspective_manager = PerspectiveManager(settings, context)
 
  505                 print(
'\n'.join(sorted(perspective_manager.perspectives)))
 
  508             perspective_manager = 
None 
  510         if main_window 
is not None:
 
  511             container_manager = ContainerManager(main_window, plugin_manager)
 
  512             plugin_manager.set_main_window(main_window, menu_bar, container_manager)
 
  515                 minimized_dock_widgets_toolbar = MinimizedDockWidgetsToolbar(
 
  516                     container_manager, main_window)
 
  517                 main_window.addToolBar(Qt.BottomToolBarArea, minimized_dock_widgets_toolbar)
 
  518                 plugin_manager.set_minimized_dock_widgets_toolbar(minimized_dock_widgets_toolbar)
 
  520         if menu_bar 
is not None:
 
  521             perspective_menu = menu_bar.addMenu(menu_bar.tr(
'P&erspectives'))
 
  522             perspective_manager.set_menu(perspective_menu)
 
  525         if perspective_manager 
is not None and main_window 
is not None:
 
  527             perspective_manager.perspective_changed_signal.connect(main_window.perspective_changed)
 
  529             perspective_manager.save_settings_signal.connect(main_window.save_settings)
 
  530             perspective_manager.restore_settings_signal.connect(main_window.restore_settings)
 
  531             perspective_manager.restore_settings_without_plugin_changes_signal.connect(
 
  532                 main_window.restore_settings)
 
  534         if perspective_manager 
is not None and plugin_manager 
is not None:
 
  535             perspective_manager.save_settings_signal.connect(plugin_manager.save_settings)
 
  536             plugin_manager.save_settings_completed_signal.connect(
 
  537                 perspective_manager.save_settings_completed)
 
  538             perspective_manager.restore_settings_signal.connect(plugin_manager.restore_settings)
 
  539             perspective_manager.restore_settings_without_plugin_changes_signal.connect(
 
  540                 plugin_manager.restore_settings_without_plugins)
 
  542         if plugin_manager 
is not None and main_window 
is not None:
 
  544             plugin_manager.plugins_about_to_change_signal.connect(main_window.save_setup)
 
  546             plugin_manager.plugins_changed_signal.connect(main_window.restore_state)
 
  548             main_window.save_settings_before_close_signal.connect(plugin_manager.close_application)
 
  550             plugin_manager.close_application_signal.connect(
 
  551                 main_window.close, type=Qt.QueuedConnection)
 
  553         if main_window 
is not None and menu_bar 
is not None:
 
  554             about_handler = AboutHandler(context.qtgui_path, main_window)
 
  555             help_menu = menu_bar.addMenu(menu_bar.tr(
'&Help'))
 
  556             action = QAction(help_menu.tr(
'&About'), help_menu)
 
  557             action.setIcon(QIcon.fromTheme(
'help-about'))
 
  558             action.triggered.connect(about_handler.show)
 
  559             help_menu.addAction(action)
 
  562         if main_window 
is not None:
 
  565                 main_window.adjustSize()
 
  568                 main_window.resize(600, 450)
 
  569                 main_window.move(100, 100)
 
  572         src_path = os.path.realpath(os.path.join(os.path.dirname(__file__), 
'..'))
 
  573         if src_path 
not in sys.path:
 
  574             sys.path.append(src_path)
 
  579         if self.
_options.embed_plugin 
is not None:
 
  581             plugin_serial = self.
_options.embed_plugin_serial
 
  582         elif self.
_options.standalone_plugin 
is not None:
 
  583             plugin = self.
_options.standalone_plugin
 
  585         if plugin 
is not None:
 
  586             plugins = plugin_manager.find_plugins_by_name(plugin)
 
  587             if len(plugins) == 0:
 
  588                 print(
'qt_gui_main() found no plugin matching "%s"' % plugin)
 
  589                 print(
'try passing the option "--force-discover"')
 
  591             elif len(plugins) > 1:
 
  592                 print(
'qt_gui_main() found multiple plugins matching "%s"\n%s' %
 
  593                       (plugin, 
'\n'.join(plugins.values())))
 
  595             plugin = list(plugins.keys())[0]
 
  597         qDebug(
'QtBindingHelper using %s' % QT_BINDING)
 
  599         plugin_manager.discover()
 
  602             qDebug(
'ReloadImporter() automatically reload all subsequent imports')
 
  603             from .reload_importer 
import ReloadImporter
 
  604             _reload_importer = ReloadImporter()
 
  606             _reload_importer.enable()
 
  609         if perspective_manager 
is not None:
 
  611                 perspective_manager.set_perspective(plugin, hide_and_without_plugin_changes=
True)
 
  612             elif self.
_options.perspective_file:
 
  613                 perspective_manager.import_perspective_from_file(
 
  615                     perspective_manager.HIDDEN_PREFIX + os.path.basename(
 
  618                 perspective_manager.set_perspective(self.
_options.perspective)
 
  622             plugin_manager.load_plugin(plugin, plugin_serial, self.
_options.plugin_args)
 
  623             running = plugin_manager.is_plugin_running(plugin, plugin_serial)
 
  628                 plugin_descriptor = plugin_manager.get_plugin_descriptor(plugin)
 
  629                 action_attributes = plugin_descriptor.action_attributes()
 
  630                 if 'icon' in action_attributes 
and action_attributes[
'icon'] 
is not None:
 
  631                     base_path = plugin_descriptor.attributes().get(
'plugin_path')
 
  634                             action_attributes[
'icon'],
 
  635                             action_attributes.get(
'icontype', 
None),
 
  640                         app.setWindowIcon(icon)
 
  642         if main_window 
is not None:
 
  644             if sys.platform == 
'darwin':
 
  650 if __name__ == 
'__main__':
 
  652     sys.exit(main.main())