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.themeName() ==
'' or \
196 QIcon.fromTheme(
'document-save').isNull()
or \
197 QIcon.fromTheme(
'document-open').isNull()
or \
198 QIcon.fromTheme(
'edit-cut').isNull()
or \
199 QIcon.fromTheme(
'object-flip-horizontal').isNull():
200 if 'darwin' in platform.platform().lower()
and \
201 '/usr/local/share/icons' not in QIcon.themeSearchPaths():
202 QIcon.setThemeSearchPaths(QIcon.themeSearchPaths() + [
'/usr/local/share/icons'])
203 original_theme = QIcon.themeName()
204 QIcon.setThemeName(
'Tango')
205 if QIcon.fromTheme(
'document-save').isNull():
206 QIcon.setThemeName(original_theme)
209 from python_qt_binding.QtCore
import Qt
210 from python_qt_binding.QtWidgets
import QApplication
211 app = QApplication(argv)
212 app.setAttribute(Qt.AA_DontShowIconsInMenus,
False)
215 def main(self, argv=None, standalone=None, plugin_argument_provider=None,
216 plugin_manager_settings_prefix=
''):
226 if '--args' in arguments:
227 index = arguments.index(
'--args')
228 plugin_args = arguments[index + 1:]
229 arguments = arguments[0:index + 1]
231 parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=
False)
233 plugin_argument_provider=plugin_argument_provider)
234 self.
_options = parser.parse_args(arguments)
238 parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=
False)
240 self.
_options, plugin_args = parser.parse_known_args(arguments)
241 self._options.multi_process =
False 242 self._options.plugin_args = plugin_args
246 self._options.freeze_layout =
False 247 self._options.lock_perspective =
False 248 self._options.hide_title =
False 249 self._options.perspective =
None 250 self._options.perspective_file =
None 251 self._options.standalone_plugin = standalone
252 self._options.list_perspectives =
False 253 self._options.list_plugins =
False 254 self._options.command_pid =
None 255 self._options.command_start_plugin =
None 256 self._options.command_switch_perspective =
None 257 self._options.embed_plugin =
None 258 self._options.embed_plugin_serial =
None 259 self._options.embed_plugin_address =
None 263 if self._options.plugin_args
and \
264 not self._options.standalone_plugin
and \
265 not self._options.command_start_plugin
and \
266 not self._options.embed_plugin:
268 'Option --args can only be used together with either --standalone, ' 269 '--command-start-plugin or --embed-plugin option')
271 if self._options.freeze_layout
and not self._options.lock_perspective:
273 'Option --freeze_layout can only be used together with the ' 274 '--lock_perspective option')
276 list_options = (self._options.list_perspectives, self._options.list_plugins)
277 list_options_set = [opt
for opt
in list_options
if opt
is not False]
278 if len(list_options_set) > 1:
279 raise RuntimeError(
'Only one --list-* option can be used at a time')
282 self._options.command_start_plugin, self._options.command_switch_perspective)
283 command_options_set = [opt
for opt
in command_options
if opt
is not None]
286 'Without DBus support the --command-* options are not available')
287 if len(command_options_set) > 1:
289 'Only one --command-* option can be used at a time (except --command-pid ' 290 'which is optional)')
291 if len(command_options_set) == 0
and self._options.command_pid
is not None:
293 'Option --command_pid can only be used together with an other --command-* ' 297 self._options.embed_plugin, self._options.embed_plugin_serial,
298 self._options.embed_plugin_address)
299 embed_options_set = [opt
for opt
in embed_options
if opt
is not None]
301 raise RuntimeError(
'Without DBus support the --embed-* options are not available')
302 if len(embed_options_set) > 0
and len(embed_options_set) < len(embed_options):
303 raise RuntimeError(
'Missing option(s) - all \'--embed-*\' options must be set')
305 if len(embed_options_set) > 0
and self._options.clear_config:
307 'Option --clear-config can only be used without any --embed-* option')
309 groups = (list_options_set, command_options_set, embed_options_set)
310 groups_set = [opt
for opt
in groups
if len(opt) > 0]
311 if len(groups_set) > 1:
313 'Options from different groups (--list, --command, --embed) can not be used ' 316 perspective_options = (self._options.perspective, self._options.perspective_file)
317 perspective_options_set = [opt
for opt
in perspective_options
if opt
is not None]
318 if len(perspective_options_set) > 1:
319 raise RuntimeError(
'Only one --perspective-* option can be used at a time')
321 if self._options.perspective_file
is not None and \
322 not os.path.isfile(self._options.perspective_file):
323 raise RuntimeError(
'Option --perspective-file must reference existing file')
325 except RuntimeError
as e:
332 if self._options.standalone_plugin
is not None:
333 self._options.lock_perspective =
True 336 from .application_context
import ApplicationContext
337 context = ApplicationContext()
342 from dbus
import DBusException, Interface, SessionBus
346 context.provide_app_dbus_interfaces = len(groups_set) == 0
347 context.dbus_base_bus_name =
'org.ros.qt_gui' 348 if context.provide_app_dbus_interfaces:
349 context.dbus_unique_bus_name = context.dbus_base_bus_name +
'.pid%d' % os.getpid()
352 from .application_dbus_interface
import ApplicationDBusInterface
353 _dbus_server = ApplicationDBusInterface(context.dbus_base_bus_name)
357 if len(command_options_set) > 0
or len(embed_options_set) > 0:
359 if self._options.command_pid
is not None:
360 host_pid = self._options.command_pid
363 remote_object = SessionBus().get_object(
364 context.dbus_base_bus_name,
'/Application')
365 except DBusException:
368 remote_interface = Interface(
369 remote_object, context.dbus_base_bus_name +
'.Application')
370 host_pid = remote_interface.get_pid()
371 if host_pid
is not None:
372 context.dbus_host_bus_name = context.dbus_base_bus_name +
'.pid%d' % host_pid
375 if len(command_options_set) > 0:
376 if self._options.command_start_plugin
is not None:
378 remote_object = SessionBus().get_object(
379 context.dbus_host_bus_name,
'/PluginManager')
380 except DBusException:
381 (rc, msg) = (1,
'unable to communicate with GUI instance "%s"' %
382 context.dbus_host_bus_name)
384 remote_interface = Interface(
385 remote_object, context.dbus_base_bus_name +
'.PluginManager')
386 (rc, msg) = remote_interface.start_plugin(
387 self._options.command_start_plugin,
' '.join(self._options.plugin_args))
389 print(
'qt_gui_main() started plugin "%s" in GUI "%s"' %
390 (msg, context.dbus_host_bus_name))
392 print(
'qt_gui_main() could not start plugin "%s" in GUI "%s": %s' %
393 (self._options.command_start_plugin, context.dbus_host_bus_name, msg))
395 elif self._options.command_switch_perspective
is not None:
396 remote_object = SessionBus().get_object(
397 context.dbus_host_bus_name,
'/PerspectiveManager')
398 remote_interface = Interface(
399 remote_object, context.dbus_base_bus_name +
'.PerspectiveManager')
400 remote_interface.switch_perspective(self._options.command_switch_perspective)
401 print(
'qt_gui_main() switched to perspective "%s" in GUI "%s"' %
402 (self._options.command_switch_perspective, context.dbus_host_bus_name))
404 raise RuntimeError(
'Unknown command not handled')
407 setattr(sys,
'SELECT_QT_BINDING', self._options.qt_binding)
408 from python_qt_binding
import QT_BINDING
410 from python_qt_binding.QtCore
import (qDebug, qInstallMessageHandler,
411 QSettings, Qt, QtCriticalMsg, QtDebugMsg)
412 from python_qt_binding.QtCore
import QtFatalMsg, QTimer, QtWarningMsg
414 from python_qt_binding.QtGui
import QIcon
415 from python_qt_binding.QtWidgets
import QAction
417 from .about_handler
import AboutHandler
418 from .composite_plugin_provider
import CompositePluginProvider
419 from .container_manager
import ContainerManager
420 from .help_provider
import HelpProvider
421 from .icon_loader
import get_icon
422 from .main_window
import MainWindow
423 from .minimized_dock_widgets_toolbar
import MinimizedDockWidgetsToolbar
424 from .perspective_manager
import PerspectiveManager
425 from .plugin_manager
import PluginManager
428 if QT_BINDING !=
'pyside':
429 def message_handler(type_, context, msg):
430 colored_output =
'TERM' in os.environ
and 'ANSI_COLORS_DISABLED' not in os.environ
431 cyan_color =
'\033[36m' if colored_output
else '' 432 red_color =
'\033[31m' if colored_output
else '' 433 reset_color =
'\033[0m' if colored_output
else '' 434 if type_ == QtDebugMsg
and self._options.verbose:
435 print(msg, file=sys.stderr)
436 elif type_ == QtWarningMsg:
437 print(cyan_color + msg + reset_color, file=sys.stderr)
438 elif type_ == QtCriticalMsg:
439 print(red_color + msg + reset_color, file=sys.stderr)
440 elif type_ == QtFatalMsg:
441 print(red_color + msg + reset_color, file=sys.stderr)
443 qInstallMessageHandler(message_handler)
449 settings = QSettings(
451 if len(embed_options_set) == 0:
452 if self._options.clear_config:
455 main_window = MainWindow()
456 if self._options.on_top:
457 main_window.setWindowFlags(Qt.WindowStaysOnTopHint)
459 main_window.statusBar()
461 def sigint_handler(*args):
462 qDebug(
'\nsigint_handler()')
464 signal.signal(signal.SIGINT, sigint_handler)
468 timer.timeout.connect(
lambda:
None)
470 if not self._options.lock_perspective:
471 menu_bar = main_window.menuBar()
472 file_menu = menu_bar.addMenu(menu_bar.tr(
'&File'))
473 action = QAction(file_menu.tr(
'&Quit'), file_menu)
474 action.setIcon(QIcon.fromTheme(
'application-exit'))
475 action.triggered.connect(main_window.close)
476 file_menu.addAction(action)
481 app.setQuitOnLastWindowClosed(
False)
490 plugin_manager = PluginManager(
491 plugin_provider, settings, context, settings_prefix=plugin_manager_settings_prefix)
493 if self._options.list_plugins:
495 print(
'\n'.join(sorted(plugin_manager.get_plugins().values())))
498 help_provider = HelpProvider()
499 plugin_manager.plugin_help_signal.connect(help_provider.plugin_help_request)
502 if main_window
is not None:
503 perspective_manager = PerspectiveManager(settings, context)
505 if self._options.list_perspectives:
507 print(
'\n'.join(sorted(perspective_manager.perspectives)))
510 perspective_manager =
None 512 if main_window
is not None:
513 container_manager = ContainerManager(main_window, plugin_manager)
514 plugin_manager.set_main_window(main_window, menu_bar, container_manager)
516 if not self._options.freeze_layout:
517 minimized_dock_widgets_toolbar = MinimizedDockWidgetsToolbar(
518 container_manager, main_window)
519 main_window.addToolBar(Qt.BottomToolBarArea, minimized_dock_widgets_toolbar)
520 plugin_manager.set_minimized_dock_widgets_toolbar(minimized_dock_widgets_toolbar)
522 if menu_bar
is not None:
523 perspective_menu = menu_bar.addMenu(menu_bar.tr(
'P&erspectives'))
524 perspective_manager.set_menu(perspective_menu)
527 if perspective_manager
is not None and main_window
is not None:
529 perspective_manager.perspective_changed_signal.connect(main_window.perspective_changed)
531 perspective_manager.save_settings_signal.connect(main_window.save_settings)
532 perspective_manager.restore_settings_signal.connect(main_window.restore_settings)
533 perspective_manager.restore_settings_without_plugin_changes_signal.connect(
534 main_window.restore_settings)
536 if perspective_manager
is not None and plugin_manager
is not None:
537 perspective_manager.save_settings_signal.connect(plugin_manager.save_settings)
538 plugin_manager.save_settings_completed_signal.connect(
539 perspective_manager.save_settings_completed)
540 perspective_manager.restore_settings_signal.connect(plugin_manager.restore_settings)
541 perspective_manager.restore_settings_without_plugin_changes_signal.connect(
542 plugin_manager.restore_settings_without_plugins)
544 if plugin_manager
is not None and main_window
is not None:
546 plugin_manager.plugins_about_to_change_signal.connect(main_window.save_setup)
548 plugin_manager.plugins_changed_signal.connect(main_window.restore_state)
550 main_window.save_settings_before_close_signal.connect(plugin_manager.close_application)
552 plugin_manager.close_application_signal.connect(
553 main_window.close, type=Qt.QueuedConnection)
555 if main_window
is not None and menu_bar
is not None:
556 about_handler = AboutHandler(context.qtgui_path, main_window)
557 help_menu = menu_bar.addMenu(menu_bar.tr(
'&Help'))
558 action = QAction(help_menu.tr(
'&About'), help_menu)
559 action.setIcon(QIcon.fromTheme(
'help-about'))
560 action.triggered.connect(about_handler.show)
561 help_menu.addAction(action)
564 if main_window
is not None:
565 main_window.resize(600, 450)
566 main_window.move(100, 100)
569 src_path = os.path.realpath(os.path.join(os.path.dirname(__file__),
'..'))
570 if src_path
not in sys.path:
571 sys.path.append(src_path)
576 if self._options.embed_plugin
is not None:
577 plugin = self._options.embed_plugin
578 plugin_serial = self._options.embed_plugin_serial
579 elif self._options.standalone_plugin
is not None:
580 plugin = self._options.standalone_plugin
582 if plugin
is not None:
583 plugins = plugin_manager.find_plugins_by_name(plugin)
584 if len(plugins) == 0:
585 print(
'qt_gui_main() found no plugin matching "%s"' % plugin)
586 print(
'try passing the option "--force-discover"')
588 elif len(plugins) > 1:
589 print(
'qt_gui_main() found multiple plugins matching "%s"\n%s' %
590 (plugin,
'\n'.join(plugins.values())))
592 plugin = list(plugins.keys())[0]
594 qDebug(
'QtBindingHelper using %s' % QT_BINDING)
596 plugin_manager.discover()
598 if self._options.reload_import:
599 qDebug(
'ReloadImporter() automatically reload all subsequent imports')
600 from .reload_importer
import ReloadImporter
601 _reload_importer = ReloadImporter()
603 _reload_importer.enable()
606 if perspective_manager
is not None:
608 perspective_manager.set_perspective(plugin, hide_and_without_plugin_changes=
True)
609 elif self._options.perspective_file:
610 perspective_manager.import_perspective_from_file(
611 self._options.perspective_file,
612 perspective_manager.HIDDEN_PREFIX + os.path.basename(
613 self._options.perspective_file))
615 perspective_manager.set_perspective(self._options.perspective)
619 plugin_manager.load_plugin(plugin, plugin_serial, self._options.plugin_args)
620 running = plugin_manager.is_plugin_running(plugin, plugin_serial)
623 if self._options.standalone_plugin:
625 plugin_descriptor = plugin_manager.get_plugin_descriptor(plugin)
626 action_attributes = plugin_descriptor.action_attributes()
627 if 'icon' in action_attributes
and action_attributes[
'icon']
is not None:
628 base_path = plugin_descriptor.attributes().get(
'plugin_path')
631 action_attributes[
'icon'],
632 action_attributes.get(
'icontype',
None),
637 app.setWindowIcon(icon)
639 if main_window
is not None:
641 if sys.platform ==
'darwin':
647 if __name__ ==
'__main__':
649 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)