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())