00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 from __future__ import print_function
00034
00035 import os
00036 import signal
00037 import sys
00038 from argparse import ArgumentParser, SUPPRESS
00039
00040
00041 class Main(object):
00042
00043 main_filename = None
00044
00045 def __init__(self, invoked_filename=None, settings_filename=None):
00046 if invoked_filename is None:
00047 invoked_filename = os.path.abspath(__file__)
00048 Main.main_filename = invoked_filename
00049 if settings_filename is None:
00050 settings_filename = 'qt_gui'
00051 self._settings_filename = settings_filename
00052 self.plugin_providers = []
00053 self._dbus_available = False
00054 self._options = None
00055
00056 def _add_arguments(self, parser):
00057 parser.add_argument('-b', '--qt-binding', dest='qt_binding', type=str, metavar='BINDING',
00058 help='choose Qt bindings to be used [pyqt|pyside]')
00059 parser.add_argument('--clear-config', dest='clear_config', default=False, action='store_true',
00060 help='clear the configuration (including all perspectives and plugin settings)')
00061 parser.add_argument('-l', '--lock-perspective', dest='lock_perspective', action='store_true',
00062 help='lock the GUI to the used perspective (hide menu bar and close buttons of plugins)')
00063 parser.add_argument('-m', '--multi-process', dest='multi_process', default=False, action='store_true',
00064 help='use separate processes for each plugin instance (currently only supported under X11)')
00065 parser.add_argument('-p', '--perspective', dest='perspective', type=str, metavar='PERSPECTIVE',
00066 help='start with this perspective')
00067 parser.add_argument('--reload-import', dest='reload_import', default=False, action='store_true',
00068 help='reload every imported module')
00069 parser.add_argument('-s', '--standalone', dest='standalone_plugin', type=str, metavar='PLUGIN',
00070 help='start only this plugin (implies -l). To pass arguments to the plugin use --args')
00071 parser.add_argument('-v', '--verbose', dest='verbose', default=False, action='store_true',
00072 help='output qDebug messages')
00073
00074 parser.add_argument('--args', dest='args', nargs='*', type=str,
00075 help='arbitrary arguments which are passes to the plugin (only with -s, --command-start-plugin or --embed-plugin). It must be the last option since it collects all following options.')
00076
00077 group = parser.add_argument_group('Options to query information without starting a GUI instance',
00078 'These options can be used to query information about valid arguments for various options.')
00079 group.add_argument('--list-perspectives', dest='list_perspectives', action='store_true',
00080 help='list available perspectives')
00081 group.add_argument('--list-plugins', dest='list_plugins', action='store_true',
00082 help='list available plugins')
00083 parser.add_argument_group(group)
00084
00085 group = parser.add_argument_group('Options to operate on a running GUI instance',
00086 'These options can be used to perform actions on a running GUI instance.')
00087 group.add_argument('--command-pid', dest='command_pid', type=int, metavar='PID',
00088 help='pid of the GUI instance to operate on, defaults to oldest running GUI instance')
00089 group.add_argument('--command-start-plugin', dest='command_start_plugin', type=str, metavar='PLUGIN',
00090 help='start plugin')
00091 group.add_argument('--command-switch-perspective', dest='command_switch_perspective', type=str, metavar='PERSPECTIVE',
00092 help='switch perspective')
00093 if not self._dbus_available:
00094 group.description = 'These options are not available since the DBus module is not found!'
00095 for o in group._group_actions:
00096 o.help = SUPPRESS
00097 parser.add_argument_group(group)
00098
00099 group = parser.add_argument_group('Special options for embedding widgets from separate processes',
00100 'These options should never be used on the CLI but only from the GUI code itself.')
00101 group.add_argument('--embed-plugin', dest='embed_plugin', type=str, metavar='PLUGIN',
00102 help='embed a plugin into an already running GUI instance (requires all other --embed-* options)')
00103 group.add_argument('--embed-plugin-serial', dest='embed_plugin_serial', type=int, metavar='SERIAL',
00104 help='serial number of plugin to be embedded (requires all other --embed-* options)')
00105 group.add_argument('--embed-plugin-address', dest='embed_plugin_address', type=str, metavar='ADDRESS',
00106 help='dbus server address of the GUI instance to embed plugin into (requires all other --embed-* options)')
00107 for o in group._group_actions:
00108 o.help = SUPPRESS
00109 parser.add_argument_group(group)
00110
00111 def _add_plugin_providers(self):
00112 pass
00113
00114 def _caching_hook(self):
00115 pass
00116
00117 def _add_reload_paths(self, reload_importer):
00118 reload_importer.add_reload_path(os.path.join(os.path.dirname(__file__), *('..',) * 4))
00119
00120 def __check_icon_theme_compliance(self):
00121 from python_qt_binding.QtGui import QIcon
00122
00123 if QIcon.themeName() == '' or \
00124 QIcon.fromTheme('document-save').isNull() or \
00125 QIcon.fromTheme('document-open').isNull() or \
00126 QIcon.fromTheme('edit-cut').isNull() or \
00127 QIcon.fromTheme('object-flip-horizontal').isNull():
00128 original_theme = QIcon.themeName()
00129 QIcon.setThemeName('Tango')
00130 if QIcon.fromTheme('document-save').isNull():
00131 QIcon.setThemeName(original_theme)
00132
00133 def main(self, argv=None, standalone=None):
00134
00135 try:
00136 import dbus
00137 del dbus
00138 self._dbus_available = True
00139 except ImportError:
00140 pass
00141
00142 if argv is None:
00143 argv = sys.argv
00144
00145
00146 arguments = argv[1:]
00147 args = []
00148 if '--args' in arguments:
00149 index = arguments.index('--args')
00150 args = arguments[index + 1:]
00151 arguments = arguments[0:index + 1]
00152
00153 if standalone:
00154 arguments += ['-s', standalone]
00155
00156 parser = ArgumentParser('usage: %prog [options]')
00157 self._add_arguments(parser)
00158 self._options = parser.parse_args(arguments)
00159 self._options.args = args
00160
00161
00162 try:
00163 if self._options.args and not self._options.standalone_plugin and not self._options.command_start_plugin and not self._options.embed_plugin:
00164 raise RuntimeError('Option --args can only be used together with either --standalone, --command-start-plugin or --embed-plugin option')
00165
00166 list_options = (self._options.list_perspectives, self._options.list_plugins)
00167 list_options_set = [opt for opt in list_options if opt is not False]
00168 if len(list_options_set) > 1:
00169 raise RuntimeError('Only one --list-* option can be used at a time')
00170
00171 command_options = (self._options.command_start_plugin, self._options.command_switch_perspective)
00172 command_options_set = [opt for opt in command_options if opt is not None]
00173 if len(command_options_set) > 0 and not self._dbus_available:
00174 raise RuntimeError('Without DBus support the --command-* options are not available')
00175 if len(command_options_set) > 1:
00176 raise RuntimeError('Only one --command-* option can be used at a time (except --command-pid which is optional)')
00177 if len(command_options_set) == 0 and self._options.command_pid is not None:
00178 raise RuntimeError('Option --command_pid can only be used together with an other --command-* option')
00179
00180 embed_options = (self._options.embed_plugin, self._options.embed_plugin_serial, self._options.embed_plugin_address)
00181 embed_options_set = [opt for opt in embed_options if opt is not None]
00182 if len(command_options_set) > 0 and not self._dbus_available:
00183 raise RuntimeError('Without DBus support the --embed-* options are not available')
00184 if len(embed_options_set) > 0 and len(embed_options_set) < len(embed_options):
00185 raise RuntimeError('Missing option(s) - all \'--embed-*\' options must be set')
00186
00187 if len(embed_options_set) > 0 and self._options.clear_config:
00188 raise RuntimeError('Option --clear-config can only be used without any --embed-* option')
00189
00190 groups = (list_options_set, command_options_set, embed_options_set)
00191 groups_set = [opt for opt in groups if len(opt) > 0]
00192 if len(groups_set) > 1:
00193 raise RuntimeError('Options from different groups (--list, --command, --embed) can not be used together')
00194
00195 except RuntimeError as e:
00196 print(str(e))
00197
00198
00199 return 1
00200
00201
00202 if self._options.standalone_plugin is not None:
00203 self._options.lock_perspective = True
00204
00205
00206 if self._dbus_available:
00207 from dbus.mainloop.glib import DBusGMainLoop
00208 from dbus import DBusException, Interface, SessionBus
00209 DBusGMainLoop(set_as_default=True)
00210
00211
00212 from .application_context import ApplicationContext
00213 context = ApplicationContext()
00214 context.options = self._options
00215
00216
00217 if self._dbus_available:
00218 context.provide_app_dbus_interfaces = len(groups_set) == 0
00219 context.dbus_base_bus_name = 'org.ros.qt_gui'
00220 if context.provide_app_dbus_interfaces:
00221 context.dbus_unique_bus_name = context.dbus_base_bus_name + '.pid%d' % os.getpid()
00222
00223
00224 from .application_dbus_interface import ApplicationDBusInterface
00225 _dbus_server = ApplicationDBusInterface(context.dbus_base_bus_name)
00226
00227
00228 if len(command_options_set) > 0 or len(embed_options_set) > 0:
00229 host_pid = None
00230 if self._options.command_pid is not None:
00231 host_pid = self._options.command_pid
00232 else:
00233 try:
00234 remote_object = SessionBus().get_object(context.dbus_base_bus_name, '/Application')
00235 except DBusException:
00236 pass
00237 else:
00238 remote_interface = Interface(remote_object, context.dbus_base_bus_name + '.Application')
00239 host_pid = remote_interface.get_pid()
00240 if host_pid is not None:
00241 context.dbus_host_bus_name = context.dbus_base_bus_name + '.pid%d' % host_pid
00242
00243
00244 if len(command_options_set) > 0:
00245 if self._options.command_start_plugin is not None:
00246 try:
00247 remote_object = SessionBus().get_object(context.dbus_host_bus_name, '/PluginManager')
00248 except DBusException:
00249 (rc, msg) = (1, 'unable to communicate with GUI instance "%s"' % context.dbus_host_bus_name)
00250 else:
00251 remote_interface = Interface(remote_object, context.dbus_base_bus_name + '.PluginManager')
00252 (rc, msg) = remote_interface.start_plugin(self._options.command_start_plugin, ' '.join(self._options.args))
00253 if rc == 0:
00254 print('qt_gui_main() started plugin "%s" in GUI "%s"' % (msg, context.dbus_host_bus_name))
00255 else:
00256 print('qt_gui_main() could not start plugin "%s" in GUI "%s": %s' % (self._options.command_start_plugin, context.dbus_host_bus_name, msg))
00257 return rc
00258 elif self._options.command_switch_perspective is not None:
00259 remote_object = SessionBus().get_object(context.dbus_host_bus_name, '/PerspectiveManager')
00260 remote_interface = Interface(remote_object, context.dbus_base_bus_name + '.PerspectiveManager')
00261 remote_interface.switch_perspective(self._options.command_switch_perspective)
00262 print('qt_gui_main() switched to perspective "%s" in GUI "%s"' % (self._options.command_switch_perspective, context.dbus_host_bus_name))
00263 return 0
00264 raise RuntimeError('Unknown command not handled')
00265
00266
00267 setattr(sys, 'SELECT_QT_BINDING', self._options.qt_binding)
00268 from python_qt_binding import QT_BINDING
00269
00270 from python_qt_binding.QtCore import qDebug, qInstallMsgHandler, QSettings, Qt, QtCriticalMsg, QtDebugMsg, QtFatalMsg, QTimer, QtWarningMsg
00271 from python_qt_binding.QtGui import QAction, QApplication, QIcon, QMenuBar
00272
00273 from .about_handler import AboutHandler
00274 from .composite_plugin_provider import CompositePluginProvider
00275 from .help_provider import HelpProvider
00276 from .main_window import MainWindow
00277 from .perspective_manager import PerspectiveManager
00278 from .plugin_manager import PluginManager
00279
00280 def message_handler(type_, msg):
00281 colored_output = 'TERM' in os.environ and 'ANSI_COLORS_DISABLED' not in os.environ
00282 cyan_color = '\033[36m' if colored_output else ''
00283 red_color = '\033[31m' if colored_output else ''
00284 reset_color = '\033[0m' if colored_output else ''
00285 if type_ == QtDebugMsg and self._options.verbose:
00286 print(msg, file=sys.stderr)
00287 elif type_ == QtWarningMsg:
00288 print(cyan_color + msg + reset_color, file=sys.stderr)
00289 elif type_ == QtCriticalMsg:
00290 print(red_color + msg + reset_color, file=sys.stderr)
00291 elif type_ == QtFatalMsg:
00292 print(red_color + msg + reset_color, file=sys.stderr)
00293 sys.exit(1)
00294 qInstallMsgHandler(message_handler)
00295
00296 app = QApplication(argv)
00297 app.setAttribute(Qt.AA_DontShowIconsInMenus, False)
00298
00299 self.__check_icon_theme_compliance()
00300
00301 if len(embed_options_set) == 0:
00302 settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'ros.org', self._settings_filename)
00303 if self._options.clear_config:
00304 settings.clear()
00305
00306 main_window = MainWindow()
00307 main_window.setDockNestingEnabled(True)
00308 main_window.statusBar()
00309
00310 def sigint_handler(*args):
00311 qDebug('\nsigint_handler()')
00312 main_window.close()
00313 signal.signal(signal.SIGINT, sigint_handler)
00314
00315 timer = QTimer()
00316 timer.start(500)
00317 timer.timeout.connect(lambda: None)
00318
00319
00320 menu_bar = QMenuBar()
00321 menu_bar.setNativeMenuBar(False)
00322 if not self._options.lock_perspective:
00323 main_window.setMenuBar(menu_bar)
00324
00325 file_menu = menu_bar.addMenu(menu_bar.tr('File'))
00326 action = QAction(file_menu.tr('Quit'), file_menu)
00327 action.setIcon(QIcon.fromTheme('application-exit'))
00328 action.triggered.connect(main_window.close)
00329 file_menu.addAction(action)
00330
00331 else:
00332 app.setQuitOnLastWindowClosed(False)
00333
00334 settings = None
00335 main_window = None
00336 menu_bar = None
00337
00338 self._add_plugin_providers()
00339
00340
00341 plugin_provider = CompositePluginProvider(self.plugin_providers)
00342 plugin_manager = PluginManager(plugin_provider, context)
00343
00344 if self._options.list_plugins:
00345
00346 print('\n'.join(sorted(plugin_manager.get_plugins().values())))
00347 return 0
00348
00349 help_provider = HelpProvider()
00350 plugin_manager.plugin_help_signal.connect(help_provider.plugin_help_request)
00351
00352
00353 if settings is not None:
00354 perspective_manager = PerspectiveManager(settings, context)
00355
00356 if self._options.list_perspectives:
00357
00358 print('\n'.join(sorted(perspective_manager.perspectives)))
00359 return 0
00360 else:
00361 perspective_manager = None
00362
00363 if main_window is not None:
00364 plugin_manager.set_main_window(main_window, menu_bar)
00365
00366 if settings is not None and menu_bar is not None:
00367 perspective_menu = menu_bar.addMenu(menu_bar.tr('Perspectives'))
00368 perspective_manager.set_menu(perspective_menu)
00369
00370
00371 if perspective_manager is not None and main_window is not None:
00372
00373 perspective_manager.perspective_changed_signal.connect(main_window.perspective_changed)
00374
00375 perspective_manager.save_settings_signal.connect(main_window.save_settings)
00376 perspective_manager.restore_settings_signal.connect(main_window.restore_settings)
00377 perspective_manager.restore_settings_without_plugin_changes_signal.connect(main_window.restore_settings)
00378
00379 if perspective_manager is not None and plugin_manager is not None:
00380 perspective_manager.save_settings_signal.connect(plugin_manager.save_settings)
00381 plugin_manager.save_settings_completed_signal.connect(perspective_manager.save_settings_completed)
00382 perspective_manager.restore_settings_signal.connect(plugin_manager.restore_settings)
00383 perspective_manager.restore_settings_without_plugin_changes_signal.connect(plugin_manager.restore_settings_without_plugins)
00384
00385 if plugin_manager is not None and main_window is not None:
00386
00387 plugin_manager.plugins_about_to_change_signal.connect(main_window.save_setup)
00388
00389 plugin_manager.plugins_changed_signal.connect(main_window.restore_state)
00390
00391 main_window.save_settings_before_close_signal.connect(plugin_manager.close_application)
00392
00393 plugin_manager.close_application_signal.connect(main_window.close, type=Qt.QueuedConnection)
00394
00395 if main_window is not None and menu_bar is not None:
00396 about_handler = AboutHandler(main_window)
00397 help_menu = menu_bar.addMenu(menu_bar.tr('Help'))
00398 action = QAction(file_menu.tr('About'), help_menu)
00399 action.setIcon(QIcon.fromTheme('help-about'))
00400 action.triggered.connect(about_handler.show)
00401 help_menu.addAction(action)
00402
00403
00404 if main_window is not None:
00405 main_window.resize(600, 450)
00406 main_window.move(100, 100)
00407
00408
00409 src_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
00410 if src_path not in sys.path:
00411 sys.path.append(src_path)
00412
00413
00414 plugin = None
00415 plugin_serial = None
00416 if self._options.embed_plugin is not None:
00417 plugin = self._options.embed_plugin
00418 plugin_serial = self._options.embed_plugin_serial
00419 elif self._options.standalone_plugin is not None:
00420 plugin = self._options.standalone_plugin
00421 plugin_serial = 0
00422 if plugin is not None:
00423 plugins = plugin_manager.find_plugins_by_name(plugin)
00424 if len(plugins) == 0:
00425 print('qt_gui_main() found no plugin matching "%s"' % plugin)
00426 return 1
00427 elif len(plugins) > 1:
00428 print('qt_gui_main() found multiple plugins matching "%s"\n%s' % (plugin, '\n'.join(plugins.values())))
00429 return 1
00430 plugin = plugins.keys()[0]
00431
00432 qDebug('QtBindingHelper using %s' % QT_BINDING)
00433
00434 plugin_manager.discover()
00435
00436 self._caching_hook()
00437
00438 if self._options.reload_import:
00439 qDebug('ReloadImporter() automatically reload all subsequent imports')
00440 from .reload_importer import ReloadImporter
00441 _reload_importer = ReloadImporter()
00442 self._add_reload_paths(_reload_importer)
00443 _reload_importer.enable()
00444
00445
00446 if perspective_manager is not None:
00447 if not plugin:
00448 perspective_manager.set_perspective(self._options.perspective)
00449 else:
00450 perspective_manager.set_perspective(plugin, hide_and_without_plugin_changes=True)
00451
00452
00453 if plugin:
00454 plugin_manager.load_plugin(plugin, plugin_serial, self._options.args)
00455
00456 if main_window is not None:
00457 main_window.show()
00458
00459 return app.exec_()
00460
00461
00462 if __name__ == '__main__':
00463 main = Main()
00464 sys.exit(main.main())