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')
94 common_group.add_argument(
95 '-l',
'--lock-perspective', dest=
'lock_perspective', action=
'store_true',
96 help=
'lock the GUI to the used perspective (hide menu bar and close buttons of ' 98 common_group.add_argument(
99 '-ht',
'--hide-title', dest=
'hide_title', action=
'store_true',
100 help=
'hide the title label, the icon, and the help button (combine with -l and -f ' 101 'to eliminate the entire title bar and reclaim the space)')
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)
240 self._options.multi_process =
False 241 self._options.plugin_args = plugin_args
245 self._options.freeze_layout =
False 246 self._options.lock_perspective =
False 247 self._options.hide_title =
False 248 self._options.perspective =
None 249 self._options.perspective_file =
None 250 self._options.standalone_plugin = standalone
251 self._options.list_perspectives =
False 252 self._options.list_plugins =
False 253 self._options.command_pid =
None 254 self._options.command_start_plugin =
None 255 self._options.command_switch_perspective =
None 256 self._options.embed_plugin =
None 257 self._options.embed_plugin_serial =
None 258 self._options.embed_plugin_address =
None 262 if self._options.plugin_args
and \
263 not self._options.standalone_plugin
and \
264 not self._options.command_start_plugin
and \
265 not self._options.embed_plugin:
267 'Option --args can only be used together with either --standalone, ' 268 '--command-start-plugin or --embed-plugin option')
270 if self._options.freeze_layout
and not self._options.lock_perspective:
272 'Option --freeze_layout can only be used together with the ' 273 '--lock_perspective option')
275 list_options = (self._options.list_perspectives, self._options.list_plugins)
276 list_options_set = [opt
for opt
in list_options
if opt
is not False]
277 if len(list_options_set) > 1:
278 raise RuntimeError(
'Only one --list-* option can be used at a time')
281 self._options.command_start_plugin, self._options.command_switch_perspective)
282 command_options_set = [opt
for opt
in command_options
if opt
is not None]
285 'Without DBus support the --command-* options are not available')
286 if len(command_options_set) > 1:
288 'Only one --command-* option can be used at a time (except --command-pid ' 289 'which is optional)')
290 if len(command_options_set) == 0
and self._options.command_pid
is not None:
292 'Option --command_pid can only be used together with an other --command-* ' 296 self._options.embed_plugin, self._options.embed_plugin_serial,
297 self._options.embed_plugin_address)
298 embed_options_set = [opt
for opt
in embed_options
if opt
is not None]
300 raise RuntimeError(
'Without DBus support the --embed-* options are not available')
301 if len(embed_options_set) > 0
and len(embed_options_set) < len(embed_options):
302 raise RuntimeError(
'Missing option(s) - all \'--embed-*\' options must be set')
304 if len(embed_options_set) > 0
and self._options.clear_config:
306 'Option --clear-config can only be used without any --embed-* option')
308 groups = (list_options_set, command_options_set, embed_options_set)
309 groups_set = [opt
for opt
in groups
if len(opt) > 0]
310 if len(groups_set) > 1:
312 'Options from different groups (--list, --command, --embed) can not be used ' 315 perspective_options = (self._options.perspective, self._options.perspective_file)
316 perspective_options_set = [opt
for opt
in perspective_options
if opt
is not None]
317 if len(perspective_options_set) > 1:
318 raise RuntimeError(
'Only one --perspective-* option can be used at a time')
320 if self._options.perspective_file
is not None and \
321 not os.path.isfile(self._options.perspective_file):
322 raise RuntimeError(
'Option --perspective-file must reference existing file')
324 except RuntimeError
as e:
331 if self._options.standalone_plugin
is not None:
332 self._options.lock_perspective =
True 335 from .application_context
import ApplicationContext
336 context = ApplicationContext()
341 from dbus
import DBusException, Interface, SessionBus
345 context.provide_app_dbus_interfaces = len(groups_set) == 0
346 context.dbus_base_bus_name =
'org.ros.qt_gui' 347 if context.provide_app_dbus_interfaces:
348 context.dbus_unique_bus_name = context.dbus_base_bus_name +
'.pid%d' % os.getpid()
351 from .application_dbus_interface
import ApplicationDBusInterface
352 _dbus_server = ApplicationDBusInterface(context.dbus_base_bus_name)
356 if len(command_options_set) > 0
or len(embed_options_set) > 0:
358 if self._options.command_pid
is not None:
359 host_pid = self._options.command_pid
362 remote_object = SessionBus().get_object(
363 context.dbus_base_bus_name,
'/Application')
364 except DBusException:
367 remote_interface = Interface(
368 remote_object, context.dbus_base_bus_name +
'.Application')
369 host_pid = remote_interface.get_pid()
370 if host_pid
is not None:
371 context.dbus_host_bus_name = context.dbus_base_bus_name +
'.pid%d' % host_pid
374 if len(command_options_set) > 0:
375 if self._options.command_start_plugin
is not None:
377 remote_object = SessionBus().get_object(
378 context.dbus_host_bus_name,
'/PluginManager')
379 except DBusException:
380 (rc, msg) = (1,
'unable to communicate with GUI instance "%s"' %
381 context.dbus_host_bus_name)
383 remote_interface = Interface(
384 remote_object, context.dbus_base_bus_name +
'.PluginManager')
385 (rc, msg) = remote_interface.start_plugin(
386 self._options.command_start_plugin,
' '.join(self._options.plugin_args))
388 print(
'qt_gui_main() started plugin "%s" in GUI "%s"' %
389 (msg, context.dbus_host_bus_name))
391 print(
'qt_gui_main() could not start plugin "%s" in GUI "%s": %s' %
392 (self._options.command_start_plugin, context.dbus_host_bus_name, msg))
394 elif self._options.command_switch_perspective
is not None:
395 remote_object = SessionBus().get_object(
396 context.dbus_host_bus_name,
'/PerspectiveManager')
397 remote_interface = Interface(
398 remote_object, context.dbus_base_bus_name +
'.PerspectiveManager')
399 remote_interface.switch_perspective(self._options.command_switch_perspective)
400 print(
'qt_gui_main() switched to perspective "%s" in GUI "%s"' %
401 (self._options.command_switch_perspective, context.dbus_host_bus_name))
403 raise RuntimeError(
'Unknown command not handled')
406 setattr(sys,
'SELECT_QT_BINDING', self._options.qt_binding)
407 from python_qt_binding
import QT_BINDING
409 from python_qt_binding.QtCore
import (qDebug, qInstallMessageHandler,
410 QSettings, Qt, QtCriticalMsg, QtDebugMsg)
411 from python_qt_binding.QtCore
import QtFatalMsg, QTimer, QtWarningMsg
413 from python_qt_binding.QtGui
import QIcon
414 from python_qt_binding.QtWidgets
import QAction
416 from .about_handler
import AboutHandler
417 from .composite_plugin_provider
import CompositePluginProvider
418 from .container_manager
import ContainerManager
419 from .help_provider
import HelpProvider
420 from .icon_loader
import get_icon
421 from .main_window
import MainWindow
422 from .minimized_dock_widgets_toolbar
import MinimizedDockWidgetsToolbar
423 from .perspective_manager
import PerspectiveManager
424 from .plugin_manager
import PluginManager
427 if QT_BINDING !=
'pyside':
428 def message_handler(type_, context, msg):
429 colored_output =
'TERM' in os.environ
and 'ANSI_COLORS_DISABLED' not in os.environ
430 cyan_color =
'\033[36m' if colored_output
else '' 431 red_color =
'\033[31m' if colored_output
else '' 432 reset_color =
'\033[0m' if colored_output
else '' 433 if type_ == QtDebugMsg
and self._options.verbose:
434 print(msg, file=sys.stderr)
435 elif type_ == QtWarningMsg:
436 print(cyan_color + msg + reset_color, file=sys.stderr)
437 elif type_ == QtCriticalMsg:
438 print(red_color + msg + reset_color, file=sys.stderr)
439 elif type_ == QtFatalMsg:
440 print(red_color + msg + reset_color, file=sys.stderr)
442 qInstallMessageHandler(message_handler)
448 settings = QSettings(
450 if len(embed_options_set) == 0:
451 if self._options.clear_config:
454 main_window = MainWindow()
455 if self._options.on_top:
456 main_window.setWindowFlags(Qt.WindowStaysOnTopHint)
458 main_window.statusBar()
460 def sigint_handler(*args):
461 qDebug(
'\nsigint_handler()')
463 signal.signal(signal.SIGINT, sigint_handler)
467 timer.timeout.connect(
lambda:
None)
469 if not self._options.lock_perspective:
470 menu_bar = main_window.menuBar()
471 file_menu = menu_bar.addMenu(menu_bar.tr(
'&File'))
472 action = QAction(file_menu.tr(
'&Quit'), file_menu)
473 action.setIcon(QIcon.fromTheme(
'application-exit'))
474 action.triggered.connect(main_window.close)
475 file_menu.addAction(action)
480 app.setQuitOnLastWindowClosed(
False)
489 plugin_manager = PluginManager(
490 plugin_provider, settings, context, settings_prefix=plugin_manager_settings_prefix)
492 if self._options.list_plugins:
494 print(
'\n'.join(sorted(plugin_manager.get_plugins().values())))
497 help_provider = HelpProvider()
498 plugin_manager.plugin_help_signal.connect(help_provider.plugin_help_request)
501 if main_window
is not None:
502 perspective_manager = PerspectiveManager(settings, context)
504 if self._options.list_perspectives:
506 print(
'\n'.join(sorted(perspective_manager.perspectives)))
509 perspective_manager =
None 511 if main_window
is not None:
512 container_manager = ContainerManager(main_window, plugin_manager)
513 plugin_manager.set_main_window(main_window, menu_bar, container_manager)
515 if not self._options.freeze_layout:
516 minimized_dock_widgets_toolbar = MinimizedDockWidgetsToolbar(
517 container_manager, main_window)
518 main_window.addToolBar(Qt.BottomToolBarArea, minimized_dock_widgets_toolbar)
519 plugin_manager.set_minimized_dock_widgets_toolbar(minimized_dock_widgets_toolbar)
521 if menu_bar
is not None:
522 perspective_menu = menu_bar.addMenu(menu_bar.tr(
'P&erspectives'))
523 perspective_manager.set_menu(perspective_menu)
526 if perspective_manager
is not None and main_window
is not None:
528 perspective_manager.perspective_changed_signal.connect(main_window.perspective_changed)
530 perspective_manager.save_settings_signal.connect(main_window.save_settings)
531 perspective_manager.restore_settings_signal.connect(main_window.restore_settings)
532 perspective_manager.restore_settings_without_plugin_changes_signal.connect(
533 main_window.restore_settings)
535 if perspective_manager
is not None and plugin_manager
is not None:
536 perspective_manager.save_settings_signal.connect(plugin_manager.save_settings)
537 plugin_manager.save_settings_completed_signal.connect(
538 perspective_manager.save_settings_completed)
539 perspective_manager.restore_settings_signal.connect(plugin_manager.restore_settings)
540 perspective_manager.restore_settings_without_plugin_changes_signal.connect(
541 plugin_manager.restore_settings_without_plugins)
543 if plugin_manager
is not None and main_window
is not None:
545 plugin_manager.plugins_about_to_change_signal.connect(main_window.save_setup)
547 plugin_manager.plugins_changed_signal.connect(main_window.restore_state)
549 main_window.save_settings_before_close_signal.connect(plugin_manager.close_application)
551 plugin_manager.close_application_signal.connect(
552 main_window.close, type=Qt.QueuedConnection)
554 if main_window
is not None and menu_bar
is not None:
555 about_handler = AboutHandler(context.qtgui_path, main_window)
556 help_menu = menu_bar.addMenu(menu_bar.tr(
'&Help'))
557 action = QAction(help_menu.tr(
'&About'), help_menu)
558 action.setIcon(QIcon.fromTheme(
'help-about'))
559 action.triggered.connect(about_handler.show)
560 help_menu.addAction(action)
563 if main_window
is not None:
566 main_window.adjustSize()
569 main_window.resize(600, 450)
570 main_window.move(100, 100)
573 src_path = os.path.realpath(os.path.join(os.path.dirname(__file__),
'..'))
574 if src_path
not in sys.path:
575 sys.path.append(src_path)
580 if self._options.embed_plugin
is not None:
581 plugin = self._options.embed_plugin
582 plugin_serial = self._options.embed_plugin_serial
583 elif self._options.standalone_plugin
is not None:
584 plugin = self._options.standalone_plugin
586 if plugin
is not None:
587 plugins = plugin_manager.find_plugins_by_name(plugin)
588 if len(plugins) == 0:
589 print(
'qt_gui_main() found no plugin matching "%s"' % plugin)
590 print(
'try passing the option "--force-discover"')
592 elif len(plugins) > 1:
593 print(
'qt_gui_main() found multiple plugins matching "%s"\n%s' %
594 (plugin,
'\n'.join(plugins.values())))
596 plugin = list(plugins.keys())[0]
598 qDebug(
'QtBindingHelper using %s' % QT_BINDING)
600 plugin_manager.discover()
602 if self._options.reload_import:
603 qDebug(
'ReloadImporter() automatically reload all subsequent imports')
604 from .reload_importer
import ReloadImporter
605 _reload_importer = ReloadImporter()
607 _reload_importer.enable()
610 if perspective_manager
is not None:
612 perspective_manager.set_perspective(plugin, hide_and_without_plugin_changes=
True)
613 elif self._options.perspective_file:
614 perspective_manager.import_perspective_from_file(
615 self._options.perspective_file,
616 perspective_manager.HIDDEN_PREFIX + os.path.basename(
617 self._options.perspective_file))
619 perspective_manager.set_perspective(self._options.perspective)
623 plugin_manager.load_plugin(plugin, plugin_serial, self._options.plugin_args)
624 running = plugin_manager.is_plugin_running(plugin, plugin_serial)
627 if self._options.standalone_plugin:
629 plugin_descriptor = plugin_manager.get_plugin_descriptor(plugin)
630 action_attributes = plugin_descriptor.action_attributes()
631 if 'icon' in action_attributes
and action_attributes[
'icon']
is not None:
632 base_path = plugin_descriptor.attributes().get(
'plugin_path')
635 action_attributes[
'icon'],
636 action_attributes.get(
'icontype',
None),
641 app.setWindowIcon(icon)
643 if main_window
is not None:
645 if sys.platform ==
'darwin':
651 if __name__ ==
'__main__':
653 sys.exit(main.main())
def _add_plugin_providers(self)
def create_application(self, argv)
def main(self, argv=None, standalone=None, plugin_argument_provider=None, plugin_manager_settings_prefix='')
def __init__(self, qtgui_path, invoked_filename=None, settings_filename=None)
def add_arguments(self, parser, standalone=False, plugin_argument_provider=None)
def _check_icon_theme_compliance(self)
def _add_reload_paths(self, reload_importer)
def get_icon(name, type_=None, base_path=None)