def add_plugin(self, plugin_descriptor): base_path = plugin_descriptor.attributes().get('plugin_path') menu_manager = self._plugin_menu_manager # create submenus for group in plugin_descriptor.groups(): label = group['label'] if menu_manager.contains_menu(label): submenu = menu_manager.get_menu(label) else: submenu = QMenu(label, menu_manager.menu) menu_action = submenu.menuAction() self._enrich_action(menu_action, group, base_path) menu_manager.add_item(submenu) menu_manager = MenuManager(submenu) # create action action_attributes = plugin_descriptor.action_attributes() action = QAction(action_attributes['label'], menu_manager.menu) self._enrich_action(action, action_attributes, base_path) self._plugin_mapper.setMapping(action, plugin_descriptor.plugin_id()) action.triggered.connect(self._plugin_mapper.map) not_available = plugin_descriptor.attributes().get('not_available') if not_available: action.setEnabled(False) action.setStatusTip(self.tr('Plugin is not available: %s') % not_available) # add action to menu menu_manager.add_item(action)
def _rightclick_menu(self, event): menu = QMenu() text_action = QAction(self.tr('View Text'), menu) menu.addAction(text_action) raw_action = QAction(self.tr('View Raw'), menu) menu.addAction(raw_action) action = menu.exec_(event.globalPos()) if action == raw_action or action == text_action: selected = self.messages_tree.selectedIndexes() selected_type = selected[1].data() if selected_type[-2:] == '[]': selected_type = selected_type[:-2] browsetext = None try: if self._mode == rosmsg.MODE_MSG: browsetext = rosmsg.get_msg_text(selected_type, action == raw_action) elif self._mode == rosmsg.MODE_SRV: browsetext = rosmsg.get_srv_text(selected_type, action == raw_action) else: raise except rosmsg.ROSMsgException, e: QMessageBox.warning( self, self.tr('Warning'), self. tr('The selected item component does not have text to view.' )) if browsetext is not None: self._browsers.append(TextBrowseDialog(browsetext)) self._browsers[-1].show()
def __init__(self, timeline, parent, topic): MessageView.__init__(self, timeline, topic) self._parent = parent self._stamp = None self._name = parent.objectName() self.toolbar = QToolBar() self._first_action = QAction(QIcon.fromTheme('go-first'), '', self.toolbar) self._first_action.triggered.connect(self.navigate_first) self.toolbar.addAction(self._first_action) self._prev_action = QAction(QIcon.fromTheme('go-previous'), '', self.toolbar) self._prev_action.triggered.connect(self.navigate_previous) self.toolbar.addAction(self._prev_action) self._next_action = QAction(QIcon.fromTheme('go-next'), '', self.toolbar) self._next_action.triggered.connect(self.navigate_next) self.toolbar.addAction(self._next_action) self._last_action = QAction(QIcon.fromTheme('go-last'), '', self.toolbar) self._last_action.triggered.connect(self.navigate_last) self.toolbar.addAction(self._last_action) parent.layout().addWidget(self.toolbar)
def set_menu(self, menu): self._menu_manager = MenuManager(menu) self._perspective_mapper = QSignalMapper(menu) self._perspective_mapper.mapped[str].connect(self.switch_perspective) # generate menu create_action = QAction('&Create perspective...', self._menu_manager.menu) create_action.setIcon(QIcon.fromTheme('list-add')) create_action.triggered.connect(self._on_create_perspective) self._menu_manager.add_suffix(create_action) self._remove_action = QAction('&Remove perspective...', self._menu_manager.menu) self._remove_action.setEnabled(False) self._remove_action.setIcon(QIcon.fromTheme('list-remove')) self._remove_action.triggered.connect(self._on_remove_perspective) self._menu_manager.add_suffix(self._remove_action) self._menu_manager.add_suffix(None) import_action = QAction('&Import...', self._menu_manager.menu) import_action.setIcon(QIcon.fromTheme('document-open')) import_action.triggered.connect(self._on_import_perspective) self._menu_manager.add_suffix(import_action) export_action = QAction('&Export...', self._menu_manager.menu) export_action.setIcon(QIcon.fromTheme('document-save-as')) export_action.triggered.connect(self._on_export_perspective) self._menu_manager.add_suffix(export_action) # add perspectives to menu for name in self.perspectives: if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name)
def __init__(self, parent=None): super(PublisherTreeWidget, self).__init__(parent) self.setModel(PublisherTreeModel(self)) self._action_remove_publisher = QAction(QIcon.fromTheme('remove'), 'Remove Selected', self) self._action_remove_publisher.triggered[bool].connect(self._handle_action_remove_publisher) self._action_publish_once = QAction(QIcon.fromTheme('media-playback-start'), 'Publish Selected Once', self) self._action_publish_once.triggered[bool].connect(self._handle_action_publish_once) self.setItemDelegateForColumn(self.model()._column_index['rate'], SpinBoxDelegate(min_value=0, max_value=1000000, decimals=2))
def _add_perspective_action(self, name): if self._menu_manager is not None: # create action action = QAction(name, self._menu_manager.menu) action.setCheckable(True) self._perspective_mapper.setMapping(action, name) action.triggered.connect(self._perspective_mapper.map) # add action to menu self._menu_manager.add_item(action) # enable remove-action if self._menu_manager.count_items() > 1: self._remove_action.setEnabled(True)
def _rightclick_menu(self, event): """ :type event: QEvent """ # QTreeview.selectedIndexes() returns 0 when no node is selected. # This can happen when after booting no left-click has been made yet # (ie. looks like right-click doesn't count). These lines are the # workaround for that problem. selected = self._messages_tree.selectedIndexes() if len(selected) == 0: return menu = QMenu() text_action = QAction(self.tr('View Text'), menu) menu.addAction(text_action) raw_action = QAction(self.tr('View Raw'), menu) menu.addAction(raw_action) action = menu.exec_(event.globalPos()) if action == raw_action or action == text_action: rospy.logdebug('_rightclick_menu selected={}'.format(selected)) selected_type = selected[1].data() if selected_type[-2:] == '[]': selected_type = selected_type[:-2] browsetext = None try: if (self._mode == rosmsg.MODE_MSG or self._mode == rosaction.MODE_ACTION): browsetext = rosmsg.get_msg_text(selected_type, action == raw_action) elif self._mode == rosmsg.MODE_SRV: browsetext = rosmsg.get_srv_text(selected_type, action == raw_action) else: raise except rosmsg.ROSMsgException, e: QMessageBox.warning( self, self.tr('Warning'), self.tr('The selected item component ' + 'does not have text to view.')) if browsetext is not None: self._browsers.append( TextBrowseDialog(browsetext, self._rospack)) self._browsers[-1].show()
def _update_remove_topic_menu(self): def make_remove_topic_function(x): return lambda: self.remove_topic(x) self._remove_topic_menu.clear() for topic_name in sorted(self._rosdata.keys()): action = QAction(topic_name, self._remove_topic_menu) action.triggered.connect(make_remove_topic_function(topic_name)) self._remove_topic_menu.addAction(action) if len(self._rosdata) > 1: all_action = QAction('All', self._remove_topic_menu) all_action.triggered.connect(self.clean_up_subscribers) self._remove_topic_menu.addAction(all_action) self.remove_topic_button.setMenu(self._remove_topic_menu)
def _gl_view_mouseReleaseEvent(self, event): if event.button() == Qt.RightButton: menu = QMenu(self._gl_view) action = QAction(self._gl_view.tr("Reset view"), self._gl_view) menu.addAction(action) action.triggered.connect(self._set_default_view) menu.exec_(self._gl_view.mapToGlobal(event.pos()))
def add_plugin_prefix(self, plugin_descriptor): action_attributes = plugin_descriptor.action_attributes() action = QAction(action_attributes['label'], self._plugin_menu_manager.menu) self._enrich_action(action, action_attributes) self._plugin_mapper.setMapping(action, plugin_descriptor.plugin_id()) action.triggered.connect(self._plugin_mapper.map) self._plugin_menu_manager.add_prefix(action)
def __contextual_menu(self, point): index = self.item_tree_view.indexAt(point) src_index = index.model().mapToSource(index) self.selected_item = src_index.internalPointer() global_pos = self.item_tree_view.header().mapToGlobal(point) action = QAction(self) action.setText("Mark for Recording") action.setCheckable(True) if self.selected_item.marked: action.setChecked(True) else: action.setChecked(False) action.triggered.connect(self.__item_marked) menu = QMenu() menu.addAction(action) menu.exec_(global_pos)
def __init__(self, parent=None): super(MessageTreeWidget, self).__init__(parent) self.setDragEnabled(True) self.sortByColumn(0, Qt.AscendingOrder) self.header().setResizeMode(QHeaderView.ResizeToContents) self.header().setContextMenuPolicy(Qt.CustomContextMenu) self.header().customContextMenuRequested.connect( self.handle_header_view_customContextMenuRequested) self._action_item_expand = QAction(QIcon.fromTheme('zoom-in'), 'Expand Selected', self) self._action_item_expand.triggered.connect( self._handle_action_item_expand) self._action_item_collapse = QAction(QIcon.fromTheme('zoom-out'), 'Collapse Selected', self) self._action_item_collapse.triggered.connect( self._handle_action_item_collapse) self.customContextMenuRequested.connect( self.handle_customContextMenuRequested)
def _update_remove_topic_menu(self): def make_remove_topic_function(x): return lambda: self.remove_topic(x) self._remove_topic_menu.clear() for topic_name in sorted(self._rosdata.keys()): action = QAction(topic_name, self._remove_topic_menu) action.triggered.connect(make_remove_topic_function(topic_name)) self._remove_topic_menu.addAction(action) self.remove_topic_button.setMenu(self._remove_topic_menu)
def add_instance(self, plugin_descriptor, instance_id): action_attributes = plugin_descriptor.action_attributes() action = QAction(self._get_instance_label(str(instance_id)), self._running_menu_manager.menu) base_path = plugin_descriptor.attributes().get('plugin_path') self._enrich_action(action, action_attributes, base_path) self._running_mapper.setMapping(action, str(instance_id)) action.triggered.connect(self._running_mapper.map) self._running_menu_manager.add_item(action) self._instances[instance_id] = action
def add_plugin(self, plugin_descriptor): base_path = plugin_descriptor.attributes().get('plugin_path') menu_manager = self._plugin_menu_manager # create submenus for group in plugin_descriptor.groups(): label = group['label'] if menu_manager.contains_menu(label): submenu = menu_manager.get_menu(label) else: submenu = QMenu(label, menu_manager.menu) menu_action = submenu.menuAction() self._enrich_action(menu_action, group, base_path) menu_manager.add_item(submenu) menu_manager = MenuManager(submenu) # create action action_attributes = plugin_descriptor.action_attributes() action = QAction(action_attributes['label'], menu_manager.menu) self._enrich_action(action, action_attributes, base_path) self._plugin_mapper.setMapping(action, plugin_descriptor.plugin_id()) action.triggered.connect(self._plugin_mapper.map) not_available = plugin_descriptor.attributes().get('not_available') if not_available: action.setEnabled(False) action.setStatusTip( self.tr('Plugin is not available: %s') % not_available) # add action to menu menu_manager.add_item(action)
def show_custom_context_menu(self, pos): if self.isReadOnly(): return menu = QTextEdit.createStandardContextMenu(self) formattext_action = None if isinstance(self.hl, XmlHighlighter): formattext_action = QAction("Format XML", self, statusTip="", triggered=self.toprettyxml) else: formattext_action = QAction("Format as YAML", self, statusTip="", triggered=self.toprettyyaml) formattext_action.setShortcuts(QKeySequence("Ctrl+Shift+F")) menu.addAction(formattext_action) # if not self.textCursor().selectedText(): # self.setTextCursor(self.cursorForPosition(pos)) submenu = self._create_context_menu_for_tag() if submenu is not None: menu.addMenu(submenu) argmenu = self._create_context_substitution_menu() if argmenu is not None: menu.addMenu(argmenu) menu.exec_(self.mapToGlobal(pos))
def __init__(self, parent=None, logger=Logger()): QTreeWidget.__init__(self, parent) self.set_logger(logger) # init tree self.setHeaderLabels(["Name", "Type", "Value"]) self.sortItems(0, Qt.AscendingOrder) #self.setSelectionMode(QAbstractItemView.NoSelection) self.setContextMenuPolicy(Qt.CustomContextMenu) self.itemActivated.connect(self.edit_item) self.currentItemChanged.connect(self.current_item_changed) # context menu self.customContextMenuRequested.connect(self.context_menu_request) self._action_item_expand = QAction(QIcon.fromTheme('zoom-in'), 'Expand Selected', self) self._action_item_expand.triggered.connect( self._handle_action_item_expand) self._action_item_collapse = QAction(QIcon.fromTheme('zoom-out'), 'Collapse Selected', self) self._action_item_collapse.triggered.connect( self._handle_action_item_collapse) self._action_item_add = QAction(QIcon.fromTheme('list-add'), 'Add', self) self._action_item_add.setEnabled(False) # TODO self._action_item_remove = QAction(QIcon.fromTheme('list-remove'), 'Remove', self) self._action_item_remove.setEnabled(False) # TODO
def add_instance(self, plugin_descriptor, instance_id): action_attributes = plugin_descriptor.action_attributes() # create action label = self.tr('Close:') + ' ' + action_attributes['label'] if instance_id.serial_number != 1: label = label + ' (%s)' % str(instance_id.serial_number) action = QAction(label, self._running_menu_manager.menu) base_path = plugin_descriptor.attributes().get('plugin_path') self._enrich_action(action, action_attributes, base_path) self._running_mapper.setMapping(action, str(instance_id)) action.triggered.connect(self._running_mapper.map) self._running_menu_manager.add_item(action) self._instances[instance_id] = action
def eventFilter(self, obj, event): if event.type() in self._event_callbacks: ret_val = self._event_callbacks[event.type()](obj, event) if ret_val is not None: return ret_val if event.type() == event.ContextMenu and obj == self.title_label: menu = QMenu(self) rename_action = menu.addAction(self.tr('Rename dock widget')) hide_action = QAction("Hide title bar", self) hide_action.setCheckable(True) hide_action.setChecked(self.hide_title_bar) if self._dock_widget.features() & QDockWidget.DockWidgetFloatable and \ self._dock_widget.features() & QDockWidget.DockWidgetMovable: menu.addAction(hide_action) action = menu.exec_(self.mapToGlobal(event.pos())) if action == rename_action: self.title_label.hide() self.title_edit.setText(self.title_label.text()) self.title_edit.show() self.title_edit.setFocus() if action == hide_action: self.hide_title_bar = not self.hide_title_bar return True return QObject.eventFilter(self, obj, event)
def _open_context_menu(self, position): indexes = self.plugin_manager_widget.plugin_tree_view.selectedIndexes() level = -1 if len(indexes) > 0: level = 0 index = indexes[0] while index.parent().isValid(): index = index.parent() level += 1 menu = QMenu() if level == 0: expand_action = QAction(self.tr('Expand'), None) expand_action.triggered.connect( self.plugin_manager_widget.plugin_tree_view.expandAll) menu.addAction(expand_action) if level == 0 or level == 1: remove_action = QAction(self.tr('Remove'), None) remove_action.triggered.connect(self.remove_plugins) menu.addAction(remove_action) menu.exec_( self.plugin_manager_widget.plugin_tree_view.viewport().mapToGlobal( position))
def __init__(self, parent=None): ''' Creates the window, connects the signals and init the class. ''' QDockWidget.__init__(self, parent) # initialize parameter self.__current_path = os.path.expanduser('~') # load the UI file ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'LaunchFilesDockWidget.ui') loadUi(ui_file, self) # initialize the view for the launch files self.launchlist_model = LaunchListModel() self.launchlist_proxyModel = QSortFilterProxyModel(self) self.launchlist_proxyModel.setSourceModel(self.launchlist_model) self.xmlFileView.setModel(self.launchlist_proxyModel) self.xmlFileView.setAlternatingRowColors(True) self.xmlFileView.activated.connect(self.on_launch_selection_activated) self.xmlFileView.setDragDropMode(QAbstractItemView.DragOnly) self.xmlFileView.setDragEnabled(True) sm = self.xmlFileView.selectionModel() sm.selectionChanged.connect(self.on_xmlFileView_selection_changed) # self.searchPackageLine.setVisible(False) self.searchPackageLine.textChanged.connect(self.set_package_filter) self.searchPackageLine.focusInEvent = self._searchline_focusInEvent # connect to the button signals self.refreshXmlButton.clicked.connect(self.on_refresh_xml_clicked) self.editXmlButton.clicked.connect(self.on_edit_xml_clicked) self.newXmlButton.clicked.connect(self.on_new_xml_clicked) self.openXmlButton.clicked.connect(self.on_open_xml_clicked) self.transferButton.clicked.connect(self.on_transfer_file_clicked) self.loadXmlButton.clicked.connect(self.on_load_xml_clicked) self.loadXmlAsDefaultButton.clicked.connect(self.on_load_as_default) # creates a default config menu start_menu = QMenu(self) self.loadDeafaultAtHostAct = QAction( "&Load default config on host", self, statusTip="Loads the default config at given host", triggered=self.on_load_as_default_at_host) start_menu.addAction(self.loadDeafaultAtHostAct) self.loadXmlAsDefaultButton.setMenu(start_menu) # initialize the progress queue self.progress_queue = ProgressQueue(self.progressFrame_cfg, self.progressBar_cfg, self.progressCancelButton_cfg, 'Launch File')
def addDockWidget(self, dock_widget): # remove action for same dock widget if exists self.removeDockWidget(dock_widget) icon = dock_widget.windowIcon() if icon.isNull(): icon = QIcon.fromTheme('folder') title = dock_widget.windowTitle() action = QAction(icon, title, self) # truncate label if necessary if len(title) > MinimizedDockWidgetsToolbar.max_label_length: action.setToolTip(title) action.setIconText(title[0:MinimizedDockWidgetsToolbar.max_label_length] + '...') self._signal_mapper.setMapping(action, dock_widget) action.triggered.connect(self._signal_mapper.map) self._dock_widgets[dock_widget] = action self.addAction(action) self.show()
class PerspectiveManager(QObject): """Manager for perspectives associated with specific sets of `Settings`.""" perspective_changed_signal = Signal(basestring) save_settings_signal = Signal(Settings, Settings) restore_settings_signal = Signal(Settings, Settings) restore_settings_without_plugin_changes_signal = Signal(Settings, Settings) HIDDEN_PREFIX = '@' def __init__(self, settings, application_context): super(PerspectiveManager, self).__init__() self.setObjectName('PerspectiveManager') self._qtgui_path = application_context.qtgui_path self._settings_proxy = SettingsProxy(settings) self._global_settings = Settings(self._settings_proxy, 'global') self._perspective_settings = None self._create_perspective_dialog = None self._menu_manager = None self._perspective_mapper = None # get perspective list from settings self.perspectives = self._settings_proxy.value('', 'perspectives', []) if isinstance(self.perspectives, basestring): self.perspectives = [self.perspectives] self._current_perspective = None self._remove_action = None self._callback = None self._callback_args = [] if application_context.provide_app_dbus_interfaces: from .perspective_manager_dbus_interface import PerspectiveManagerDBusInterface self._dbus_server = PerspectiveManagerDBusInterface(self, application_context) def set_menu(self, menu): self._menu_manager = MenuManager(menu) self._perspective_mapper = QSignalMapper(menu) self._perspective_mapper.mapped[str].connect(self.switch_perspective) # generate menu create_action = QAction('&Create perspective...', self._menu_manager.menu) create_action.setIcon(QIcon.fromTheme('list-add')) create_action.triggered.connect(self._on_create_perspective) self._menu_manager.add_suffix(create_action) self._remove_action = QAction('&Remove perspective...', self._menu_manager.menu) self._remove_action.setEnabled(False) self._remove_action.setIcon(QIcon.fromTheme('list-remove')) self._remove_action.triggered.connect(self._on_remove_perspective) self._menu_manager.add_suffix(self._remove_action) self._menu_manager.add_suffix(None) import_action = QAction('&Import...', self._menu_manager.menu) import_action.setIcon(QIcon.fromTheme('document-open')) import_action.triggered.connect(self._on_import_perspective) self._menu_manager.add_suffix(import_action) export_action = QAction('&Export...', self._menu_manager.menu) export_action.setIcon(QIcon.fromTheme('document-save-as')) export_action.triggered.connect(self._on_export_perspective) self._menu_manager.add_suffix(export_action) # add perspectives to menu for name in self.perspectives: if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def set_perspective(self, name, hide_and_without_plugin_changes=False): if name is None: name = self._settings_proxy.value('', 'current-perspective', 'Default') elif hide_and_without_plugin_changes: name = self.HIDDEN_PREFIX + name self.switch_perspective(name, save_before=not hide_and_without_plugin_changes, without_plugin_changes=hide_and_without_plugin_changes) @Slot(str) @Slot(str, bool) @Slot(str, bool, bool) def switch_perspective(self, name, settings_changed=True, save_before=True, without_plugin_changes=False): if save_before and self._global_settings is not None and self._perspective_settings is not None: self._callback = self._switch_perspective self._callback_args = [name, settings_changed, save_before] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._switch_perspective(name, settings_changed, save_before, without_plugin_changes) def _switch_perspective(self, name, settings_changed, save_before, without_plugin_changes=False): # convert from unicode name = str(name.replace('/', '__')) qDebug('PerspectiveManager.switch_perspective() switching to perspective "%s"' % name) if self._current_perspective is not None and self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, False) self._menu_manager.set_item_disabled(self._current_perspective, False) # create perspective if necessary if name not in self.perspectives: self._create_perspective(name, clone_perspective=False) # update current perspective self._current_perspective = name if self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, True) self._menu_manager.set_item_disabled(self._current_perspective, True) if not self._current_perspective.startswith(self.HIDDEN_PREFIX): self._settings_proxy.set_value('', 'current-perspective', self._current_perspective) self._perspective_settings = self._get_perspective_settings(self._current_perspective) # emit signals self.perspective_changed_signal.emit(self._current_perspective.lstrip(self.HIDDEN_PREFIX)) if settings_changed: if not without_plugin_changes: self.restore_settings_signal.emit(self._global_settings, self._perspective_settings) else: self.restore_settings_without_plugin_changes_signal.emit(self._global_settings, self._perspective_settings) def save_settings_completed(self): if self._callback is not None: callback = self._callback callback_args = self._callback_args self._callback = None self._callback_args = [] callback(*callback_args) def _get_perspective_settings(self, perspective_name): return Settings(self._settings_proxy, 'perspective/%s' % perspective_name) def _on_create_perspective(self): name = self._choose_new_perspective_name() if name is not None: clone_perspective = self._create_perspective_dialog.clone_checkbox.isChecked() self._create_perspective(name, clone_perspective) self.switch_perspective(name, settings_changed=not clone_perspective, save_before=False) def _choose_new_perspective_name(self, show_cloning=True): # input dialog for new perspective name if self._create_perspective_dialog is None: ui_file = os.path.join(self._qtgui_path, 'resource', 'perspective_create.ui') self._create_perspective_dialog = loadUi(ui_file) # custom validator preventing forward slashs class CustomValidator(QValidator): def __init__(self, parent=None): super(CustomValidator, self).__init__(parent) def fixup(self, value): value = value.replace('/', '') def validate(self, value, pos): if value.find('/') != -1: pos = value.find('/') return (QValidator.Invalid, value, pos) if value == '': return (QValidator.Intermediate, value, pos) return (QValidator.Acceptable, value, pos) self._create_perspective_dialog.perspective_name_edit.setValidator(CustomValidator()) # set default values self._create_perspective_dialog.perspective_name_edit.setText('') self._create_perspective_dialog.clone_checkbox.setChecked(True) self._create_perspective_dialog.clone_checkbox.setVisible(show_cloning) # show dialog and wait for it's return value return_value = self._create_perspective_dialog.exec_() if return_value == self._create_perspective_dialog.Rejected: return name = str(self._create_perspective_dialog.perspective_name_edit.text()).lstrip(self.HIDDEN_PREFIX) if name == '': QMessageBox.warning(self._menu_manager.menu, self.tr('Empty perspective name'), self.tr('The name of the perspective must be non-empty.')) return if name in self.perspectives: QMessageBox.warning(self._menu_manager.menu, self.tr('Duplicate perspective name'), self.tr('A perspective with the same name already exists.')) return return name def _create_perspective(self, name, clone_perspective=True): # convert from unicode name = str(name) if name.find('/') != -1: raise RuntimeError('PerspectiveManager._create_perspective() name must not contain forward slashs (/)') qDebug('PerspectiveManager._create_perspective(%s, %s)' % (name, clone_perspective)) # add to list of perspectives self.perspectives.append(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # save current settings if self._global_settings is not None and self._perspective_settings is not None: self._callback = self._create_perspective_continued self._callback_args = [name, clone_perspective] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._create_perspective_continued(name, clone_perspective) def _create_perspective_continued(self, name, clone_perspective): # clone settings if clone_perspective: new_settings = self._get_perspective_settings(name) keys = self._perspective_settings.all_keys() for key in keys: value = self._perspective_settings.value(key) new_settings.set_value(key, value) # add and switch to perspective if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def _add_perspective_action(self, name): if self._menu_manager is not None: # create action action = QAction(name, self._menu_manager.menu) action.setCheckable(True) self._perspective_mapper.setMapping(action, name) action.triggered.connect(self._perspective_mapper.map) # add action to menu self._menu_manager.add_item(action) # enable remove-action if self._menu_manager.count_items() > 1: self._remove_action.setEnabled(True) def _on_remove_perspective(self): # input dialog to choose perspective to be removed names = list(self.perspectives) names.remove(self._current_perspective) name, return_value = QInputDialog.getItem(self._menu_manager.menu, self._menu_manager.tr('Remove perspective'), self._menu_manager.tr('Select the perspective'), names, 0, False) # convert from unicode name = str(name) if return_value == QInputDialog.Rejected: return self._remove_perspective(name) def _remove_perspective(self, name): if name not in self.perspectives: raise UserWarning('unknown perspective: %s' % name) qDebug('PerspectiveManager._remove_perspective(%s)' % str(name)) # remove from list of perspectives self.perspectives.remove(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # remove settings settings = self._get_perspective_settings(name) settings.remove('') # remove from menu self._menu_manager.remove_item(name) # disable remove-action if self._menu_manager.count_items() < 2: self._remove_action.setEnabled(False) def _on_import_perspective(self): file_name, _ = QFileDialog.getOpenFileName(self._menu_manager.menu, self.tr('Import perspective from file'), None, self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return perspective_name = os.path.basename(file_name) suffix = '.perspective' if perspective_name.endswith(suffix): perspective_name = perspective_name[:-len(suffix)] if perspective_name in self.perspectives: perspective_name = self._choose_new_perspective_name(False) if perspective_name is None: return self.import_perspective_from_file(file_name, perspective_name) def import_perspective_from_file(self, path, perspective_name): # create clean perspective if perspective_name in self.perspectives: self._remove_perspective(perspective_name) self._create_perspective(perspective_name, clone_perspective=False) # read perspective from file file_handle = open(path, 'r') #data = eval(file_handle.read()) data = json.loads(file_handle.read()) self._convert_values(data, self._import_value) new_settings = self._get_perspective_settings(perspective_name) self._set_dict_on_settings(data, new_settings) self.switch_perspective(perspective_name, settings_changed=True, save_before=True) def _set_dict_on_settings(self, data, settings): """Set dictionary key-value pairs on Settings instance.""" keys = data.get('keys', {}) for key in keys: settings.set_value(key, keys[key]) groups = data.get('groups', {}) for group in groups: sub = settings.get_settings(group) self._set_dict_on_settings(groups[group], sub) def _on_export_perspective(self): file_name, _ = QFileDialog.getSaveFileName(self._menu_manager.menu, self.tr('Export perspective to file'), self._current_perspective + '.perspective', self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return # trigger save of perspective before export self._callback = self._on_export_perspective_continued self._callback_args = [file_name] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) def _on_export_perspective_continued(self, file_name): # convert every value data = self._get_dict_from_settings(self._perspective_settings) self._convert_values(data, self._export_value) # write perspective data to file file_handle = open(file_name, 'w') file_handle.write(json.dumps(data, indent=2)) file_handle.close() def _get_dict_from_settings(self, settings): """Convert data of Settings instance to dictionary.""" keys = {} for key in settings.child_keys(): keys[str(key)] = settings.value(key) groups = {} for group in settings.child_groups(): sub = settings.get_settings(group) groups[str(group)] = self._get_dict_from_settings(sub) return {'keys': keys, 'groups': groups} def _convert_values(self, data, convert_function): keys = data.get('keys', {}) for key in keys: keys[key] = convert_function(keys[key]) groups = data.get('groups', {}) for group in groups: self._convert_values(groups[group], convert_function) def _import_value(self, value): import QtCore # @UnusedImport if value['type'] == 'repr': return eval(value['repr']) elif value['type'] == 'repr(QByteArray.hex)': return QByteArray.fromHex(eval(value['repr(QByteArray.hex)'])) raise RuntimeError('PerspectiveManager._import_value() unknown serialization type (%s)' % value['type']) def _export_value(self, value): data = {} if value.__class__.__name__ == 'QByteArray': hex_value = value.toHex() data['repr(QByteArray.hex)'] = self._strip_qt_binding_prefix(hex_value, repr(hex_value)) data['type'] = 'repr(QByteArray.hex)' # add pretty print for better readability characters = '' for i in range(1, value.size(), 2): character = value.at(i) # output all non-control characters if character >= ' ' and character <= '~': characters += character else: characters += ' ' data['pretty-print'] = characters else: data['repr'] = self._strip_qt_binding_prefix(value, repr(value)) data['type'] = 'repr' # verify that serialized data can be deserialized correctly reimported = self._import_value(data) if reimported != value: raise RuntimeError('PerspectiveManager._export_value() stored value can not be restored (%s)' % type(value)) return data def _strip_qt_binding_prefix(self, obj, data): """Strip binding specific prefix from type string.""" parts = obj.__class__.__module__.split('.') if len(parts) > 1 and parts[1] == 'QtCore': prefix = '.'.join(parts[:2]) data = data.replace(prefix, 'QtCore', 1) return data
def main(self, argv=None, standalone=None, plugin_argument_provider=None, plugin_manager_settings_prefix=''): if argv is None: argv = sys.argv # extract --args and everything behind manually since argparse can not handle that arguments = argv[1:] # extract plugin specific args when not being invoked in standalone mode programmatically if not standalone: plugin_args = [] if '--args' in arguments: index = arguments.index('--args') plugin_args = arguments[index + 1:] arguments = arguments[0:index + 1] parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=False) self.add_arguments(parser, standalone=bool(standalone), plugin_argument_provider=plugin_argument_provider) self._options = parser.parse_args(arguments) if standalone: # rerun parsing to separate common arguments from plugin specific arguments parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=False) self.add_arguments(parser, standalone=bool(standalone)) self._options, plugin_args = parser.parse_known_args(arguments) self._options.plugin_args = plugin_args # set default values for options not available in standalone mode if standalone: self._options.freeze_layout = False self._options.lock_perspective = False self._options.multi_process = False self._options.perspective = None self._options.perspective_file = None self._options.standalone_plugin = standalone self._options.list_perspectives = False self._options.list_plugins = False self._options.command_pid = None self._options.command_start_plugin = None self._options.command_switch_perspective = None self._options.embed_plugin = None self._options.embed_plugin_serial = None self._options.embed_plugin_address = None # check option dependencies try: if self._options.plugin_args and not self._options.standalone_plugin and not self._options.command_start_plugin and not self._options.embed_plugin: raise RuntimeError('Option --args can only be used together with either --standalone, --command-start-plugin or --embed-plugin option') if self._options.freeze_layout and not self._options.lock_perspective: raise RuntimeError('Option --freeze_layout can only be used together with the --lock_perspective option') list_options = (self._options.list_perspectives, self._options.list_plugins) list_options_set = [opt for opt in list_options if opt is not False] if len(list_options_set) > 1: raise RuntimeError('Only one --list-* option can be used at a time') command_options = (self._options.command_start_plugin, self._options.command_switch_perspective) command_options_set = [opt for opt in command_options if opt is not None] if len(command_options_set) > 0 and not self._dbus_available: raise RuntimeError('Without DBus support the --command-* options are not available') if len(command_options_set) > 1: raise RuntimeError('Only one --command-* option can be used at a time (except --command-pid which is optional)') if len(command_options_set) == 0 and self._options.command_pid is not None: raise RuntimeError('Option --command_pid can only be used together with an other --command-* option') embed_options = (self._options.embed_plugin, self._options.embed_plugin_serial, self._options.embed_plugin_address) embed_options_set = [opt for opt in embed_options if opt is not None] if len(command_options_set) > 0 and not self._dbus_available: raise RuntimeError('Without DBus support the --embed-* options are not available') if len(embed_options_set) > 0 and len(embed_options_set) < len(embed_options): raise RuntimeError('Missing option(s) - all \'--embed-*\' options must be set') if len(embed_options_set) > 0 and self._options.clear_config: raise RuntimeError('Option --clear-config can only be used without any --embed-* option') groups = (list_options_set, command_options_set, embed_options_set) groups_set = [opt for opt in groups if len(opt) > 0] if len(groups_set) > 1: raise RuntimeError('Options from different groups (--list, --command, --embed) can not be used together') perspective_options = (self._options.perspective, self._options.perspective_file) perspective_options_set = [opt for opt in perspective_options if opt is not None] if len(perspective_options_set) > 1: raise RuntimeError('Only one --perspective-* option can be used at a time') if self._options.perspective_file is not None and not os.path.isfile(self._options.perspective_file): raise RuntimeError('Option --perspective-file must reference existing file') except RuntimeError as e: print(str(e)) #parser.parse_args(['--help']) # calling --help will exit return 1 # set implicit option dependencies if self._options.standalone_plugin is not None: self._options.lock_perspective = True # create application context containing various relevant information from .application_context import ApplicationContext context = ApplicationContext() context.qtgui_path = self._qtgui_path context.options = self._options if self._dbus_available: from dbus import DBusException, Interface, SessionBus # non-special applications provide various dbus interfaces if self._dbus_available: context.provide_app_dbus_interfaces = len(groups_set) == 0 context.dbus_base_bus_name = 'org.ros.qt_gui' if context.provide_app_dbus_interfaces: context.dbus_unique_bus_name = context.dbus_base_bus_name + '.pid%d' % os.getpid() # provide pid of application via dbus from .application_dbus_interface import ApplicationDBusInterface _dbus_server = ApplicationDBusInterface(context.dbus_base_bus_name) # determine host bus name, either based on pid given on command line or via dbus application interface if any other instance is available if len(command_options_set) > 0 or len(embed_options_set) > 0: host_pid = None if self._options.command_pid is not None: host_pid = self._options.command_pid else: try: remote_object = SessionBus().get_object(context.dbus_base_bus_name, '/Application') except DBusException: pass else: remote_interface = Interface(remote_object, context.dbus_base_bus_name + '.Application') host_pid = remote_interface.get_pid() if host_pid is not None: context.dbus_host_bus_name = context.dbus_base_bus_name + '.pid%d' % host_pid # execute command on host application instance if len(command_options_set) > 0: if self._options.command_start_plugin is not None: try: remote_object = SessionBus().get_object(context.dbus_host_bus_name, '/PluginManager') except DBusException: (rc, msg) = (1, 'unable to communicate with GUI instance "%s"' % context.dbus_host_bus_name) else: remote_interface = Interface(remote_object, context.dbus_base_bus_name + '.PluginManager') (rc, msg) = remote_interface.start_plugin(self._options.command_start_plugin, ' '.join(self._options.plugin_args)) if rc == 0: print('qt_gui_main() started plugin "%s" in GUI "%s"' % (msg, context.dbus_host_bus_name)) else: print('qt_gui_main() could not start plugin "%s" in GUI "%s": %s' % (self._options.command_start_plugin, context.dbus_host_bus_name, msg)) return rc elif self._options.command_switch_perspective is not None: remote_object = SessionBus().get_object(context.dbus_host_bus_name, '/PerspectiveManager') remote_interface = Interface(remote_object, context.dbus_base_bus_name + '.PerspectiveManager') remote_interface.switch_perspective(self._options.command_switch_perspective) print('qt_gui_main() switched to perspective "%s" in GUI "%s"' % (self._options.command_switch_perspective, context.dbus_host_bus_name)) return 0 raise RuntimeError('Unknown command not handled') # choose selected or default qt binding setattr(sys, 'SELECT_QT_BINDING', self._options.qt_binding) from python_qt_binding import QT_BINDING from python_qt_binding.QtCore import qDebug, qInstallMsgHandler, QSettings, Qt, QtCriticalMsg, QtDebugMsg, QtFatalMsg, QTimer, QtWarningMsg from python_qt_binding.QtGui import QAction, QIcon, QMenuBar from .about_handler import AboutHandler from .composite_plugin_provider import CompositePluginProvider from .container_manager import ContainerManager from .help_provider import HelpProvider from .icon_loader import get_icon from .main_window import MainWindow from .minimized_dock_widgets_toolbar import MinimizedDockWidgetsToolbar from .perspective_manager import PerspectiveManager from .plugin_manager import PluginManager def message_handler(type_, msg): colored_output = 'TERM' in os.environ and 'ANSI_COLORS_DISABLED' not in os.environ cyan_color = '\033[36m' if colored_output else '' red_color = '\033[31m' if colored_output else '' reset_color = '\033[0m' if colored_output else '' if type_ == QtDebugMsg and self._options.verbose: print(msg, file=sys.stderr) elif type_ == QtWarningMsg: print(cyan_color + msg + reset_color, file=sys.stderr) elif type_ == QtCriticalMsg: print(red_color + msg + reset_color, file=sys.stderr) elif type_ == QtFatalMsg: print(red_color + msg + reset_color, file=sys.stderr) sys.exit(1) qInstallMsgHandler(message_handler) app = self.create_application(argv) self._check_icon_theme_compliance() settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'ros.org', self._settings_filename) if len(embed_options_set) == 0: if self._options.clear_config: settings.clear() main_window = MainWindow() if self._options.on_top: main_window.setWindowFlags(Qt.WindowStaysOnTopHint) main_window.statusBar() def sigint_handler(*args): qDebug('\nsigint_handler()') main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler timer = QTimer() timer.start(500) timer.timeout.connect(lambda: None) # create own menu bar to share one menu bar on Mac menu_bar = QMenuBar() if 'darwin' in platform.platform().lower(): menu_bar.setNativeMenuBar(True) else: menu_bar.setNativeMenuBar(False) if not self._options.lock_perspective: main_window.setMenuBar(menu_bar) file_menu = menu_bar.addMenu(menu_bar.tr('&File')) action = QAction(file_menu.tr('&Quit'), file_menu) action.setIcon(QIcon.fromTheme('application-exit')) action.triggered.connect(main_window.close) file_menu.addAction(action) else: app.setQuitOnLastWindowClosed(False) main_window = None menu_bar = None self._add_plugin_providers() # setup plugin manager plugin_provider = CompositePluginProvider(self.plugin_providers) plugin_manager = PluginManager(plugin_provider, settings, context, settings_prefix=plugin_manager_settings_prefix) if self._options.list_plugins: # output available plugins print('\n'.join(sorted(plugin_manager.get_plugins().values()))) return 0 help_provider = HelpProvider() plugin_manager.plugin_help_signal.connect(help_provider.plugin_help_request) # setup perspective manager if main_window is not None: perspective_manager = PerspectiveManager(settings, context) if self._options.list_perspectives: # output available perspectives print('\n'.join(sorted(perspective_manager.perspectives))) return 0 else: perspective_manager = None if main_window is not None: container_manager = ContainerManager(main_window, plugin_manager) plugin_manager.set_main_window(main_window, menu_bar, container_manager) if not self._options.freeze_layout: minimized_dock_widgets_toolbar = MinimizedDockWidgetsToolbar(container_manager, main_window) main_window.addToolBar(Qt.BottomToolBarArea, minimized_dock_widgets_toolbar) plugin_manager.set_minimized_dock_widgets_toolbar(minimized_dock_widgets_toolbar) if menu_bar is not None: perspective_menu = menu_bar.addMenu(menu_bar.tr('P&erspectives')) perspective_manager.set_menu(perspective_menu) # connect various signals and slots if perspective_manager is not None and main_window is not None: # signal changed perspective to update window title perspective_manager.perspective_changed_signal.connect(main_window.perspective_changed) # signal new settings due to changed perspective perspective_manager.save_settings_signal.connect(main_window.save_settings) perspective_manager.restore_settings_signal.connect(main_window.restore_settings) perspective_manager.restore_settings_without_plugin_changes_signal.connect(main_window.restore_settings) if perspective_manager is not None and plugin_manager is not None: perspective_manager.save_settings_signal.connect(plugin_manager.save_settings) plugin_manager.save_settings_completed_signal.connect(perspective_manager.save_settings_completed) perspective_manager.restore_settings_signal.connect(plugin_manager.restore_settings) perspective_manager.restore_settings_without_plugin_changes_signal.connect(plugin_manager.restore_settings_without_plugins) if plugin_manager is not None and main_window is not None: # signal before changing plugins to save window state plugin_manager.plugins_about_to_change_signal.connect(main_window.save_setup) # signal changed plugins to restore window state plugin_manager.plugins_changed_signal.connect(main_window.restore_state) # signal save settings to store plugin setup on close main_window.save_settings_before_close_signal.connect(plugin_manager.close_application) # signal save and shutdown called for all plugins, trigger closing main window again plugin_manager.close_application_signal.connect(main_window.close, type=Qt.QueuedConnection) if main_window is not None and menu_bar is not None: about_handler = AboutHandler(context.qtgui_path, main_window) help_menu = menu_bar.addMenu(menu_bar.tr('&Help')) action = QAction(file_menu.tr('&About'), help_menu) action.setIcon(QIcon.fromTheme('help-about')) action.triggered.connect(about_handler.show) help_menu.addAction(action) # set initial size - only used without saved configuration if main_window is not None: main_window.resize(600, 450) main_window.move(100, 100) # ensure that qt_gui/src is in sys.path src_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) if src_path not in sys.path: sys.path.append(src_path) # load specific plugin plugin = None plugin_serial = None if self._options.embed_plugin is not None: plugin = self._options.embed_plugin plugin_serial = self._options.embed_plugin_serial elif self._options.standalone_plugin is not None: plugin = self._options.standalone_plugin plugin_serial = 0 if plugin is not None: plugins = plugin_manager.find_plugins_by_name(plugin) if len(plugins) == 0: print('qt_gui_main() found no plugin matching "%s"' % plugin) return 1 elif len(plugins) > 1: print('qt_gui_main() found multiple plugins matching "%s"\n%s' % (plugin, '\n'.join(plugins.values()))) return 1 plugin = plugins.keys()[0] qDebug('QtBindingHelper using %s' % QT_BINDING) plugin_manager.discover() if self._options.reload_import: qDebug('ReloadImporter() automatically reload all subsequent imports') from .reload_importer import ReloadImporter _reload_importer = ReloadImporter() self._add_reload_paths(_reload_importer) _reload_importer.enable() # switch perspective if perspective_manager is not None: if plugin: perspective_manager.set_perspective(plugin, hide_and_without_plugin_changes=True) elif self._options.perspective_file: perspective_manager.import_perspective_from_file(self._options.perspective_file, perspective_manager.HIDDEN_PREFIX + '__cli_perspective_from_file') else: perspective_manager.set_perspective(self._options.perspective) # load specific plugin if plugin: plugin_manager.load_plugin(plugin, plugin_serial, self._options.plugin_args) running = plugin_manager.is_plugin_running(plugin, plugin_serial) if not running: return 1 if self._options.standalone_plugin: # use icon of standalone plugin (if available) for application plugin_descriptor = plugin_manager.get_plugin_descriptor(plugin) action_attributes = plugin_descriptor.action_attributes() if 'icon' in action_attributes and action_attributes['icon'] is not None: base_path = plugin_descriptor.attributes().get('plugin_path') try: icon = get_icon(action_attributes['icon'], action_attributes.get('icontype', None), base_path) except UserWarning: pass else: app.setWindowIcon(icon) if main_window is not None: main_window.show() if sys.platform == 'darwin': main_window.raise_() return app.exec_()
def main(self, argv=None, standalone=None, plugin_argument_provider=None, plugin_manager_settings_prefix=''): if argv is None: argv = sys.argv # extract --args and everything behind manually since argparse can not handle that arguments = argv[1:] # extract plugin specific args when not being invoked in standalone mode programmatically if not standalone: plugin_args = [] if '--args' in arguments: index = arguments.index('--args') plugin_args = arguments[index + 1:] arguments = arguments[0:index + 1] parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=False) self.add_arguments(parser, standalone=bool(standalone), plugin_argument_provider=plugin_argument_provider) self._options = parser.parse_args(arguments) if standalone: # rerun parsing to separate common arguments from plugin specific arguments parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=False) self.add_arguments(parser, standalone=bool(standalone)) self._options, plugin_args = parser.parse_known_args(arguments) self._options.plugin_args = plugin_args # set default values for options not available in standalone mode if standalone: self._options.freeze_layout = False self._options.lock_perspective = False self._options.multi_process = False self._options.perspective = None self._options.perspective_file = None self._options.standalone_plugin = standalone self._options.list_perspectives = False self._options.list_plugins = False self._options.command_pid = None self._options.command_start_plugin = None self._options.command_switch_perspective = None self._options.embed_plugin = None self._options.embed_plugin_serial = None self._options.embed_plugin_address = None # check option dependencies try: if self._options.plugin_args and not self._options.standalone_plugin and not self._options.command_start_plugin and not self._options.embed_plugin: raise RuntimeError( 'Option --args can only be used together with either --standalone, --command-start-plugin or --embed-plugin option' ) if self._options.freeze_layout and not self._options.lock_perspective: raise RuntimeError( 'Option --freeze_layout can only be used together with the --lock_perspective option' ) list_options = (self._options.list_perspectives, self._options.list_plugins) list_options_set = [ opt for opt in list_options if opt is not False ] if len(list_options_set) > 1: raise RuntimeError( 'Only one --list-* option can be used at a time') command_options = (self._options.command_start_plugin, self._options.command_switch_perspective) command_options_set = [ opt for opt in command_options if opt is not None ] if len(command_options_set) > 0 and not self._dbus_available: raise RuntimeError( 'Without DBus support the --command-* options are not available' ) if len(command_options_set) > 1: raise RuntimeError( 'Only one --command-* option can be used at a time (except --command-pid which is optional)' ) if len(command_options_set ) == 0 and self._options.command_pid is not None: raise RuntimeError( 'Option --command_pid can only be used together with an other --command-* option' ) embed_options = (self._options.embed_plugin, self._options.embed_plugin_serial, self._options.embed_plugin_address) embed_options_set = [ opt for opt in embed_options if opt is not None ] if len(command_options_set) > 0 and not self._dbus_available: raise RuntimeError( 'Without DBus support the --embed-* options are not available' ) if len(embed_options_set) > 0 and len(embed_options_set) < len( embed_options): raise RuntimeError( 'Missing option(s) - all \'--embed-*\' options must be set' ) if len(embed_options_set) > 0 and self._options.clear_config: raise RuntimeError( 'Option --clear-config can only be used without any --embed-* option' ) groups = (list_options_set, command_options_set, embed_options_set) groups_set = [opt for opt in groups if len(opt) > 0] if len(groups_set) > 1: raise RuntimeError( 'Options from different groups (--list, --command, --embed) can not be used together' ) perspective_options = (self._options.perspective, self._options.perspective_file) perspective_options_set = [ opt for opt in perspective_options if opt is not None ] if len(perspective_options_set) > 1: raise RuntimeError( 'Only one --perspective-* option can be used at a time') if self._options.perspective_file is not None and not os.path.isfile( self._options.perspective_file): raise RuntimeError( 'Option --perspective-file must reference existing file') except RuntimeError as e: print(str(e)) #parser.parse_args(['--help']) # calling --help will exit return 1 # set implicit option dependencies if self._options.standalone_plugin is not None: self._options.lock_perspective = True # create application context containing various relevant information from .application_context import ApplicationContext context = ApplicationContext() context.qtgui_path = self._qtgui_path context.options = self._options if self._dbus_available: from dbus import DBusException, Interface, SessionBus # non-special applications provide various dbus interfaces if self._dbus_available: context.provide_app_dbus_interfaces = len(groups_set) == 0 context.dbus_base_bus_name = 'org.ros.qt_gui' if context.provide_app_dbus_interfaces: context.dbus_unique_bus_name = context.dbus_base_bus_name + '.pid%d' % os.getpid( ) # provide pid of application via dbus from .application_dbus_interface import ApplicationDBusInterface _dbus_server = ApplicationDBusInterface( context.dbus_base_bus_name) # determine host bus name, either based on pid given on command line or via dbus application interface if any other instance is available if len(command_options_set) > 0 or len(embed_options_set) > 0: host_pid = None if self._options.command_pid is not None: host_pid = self._options.command_pid else: try: remote_object = SessionBus().get_object( context.dbus_base_bus_name, '/Application') except DBusException: pass else: remote_interface = Interface( remote_object, context.dbus_base_bus_name + '.Application') host_pid = remote_interface.get_pid() if host_pid is not None: context.dbus_host_bus_name = context.dbus_base_bus_name + '.pid%d' % host_pid # execute command on host application instance if len(command_options_set) > 0: if self._options.command_start_plugin is not None: try: remote_object = SessionBus().get_object( context.dbus_host_bus_name, '/PluginManager') except DBusException: (rc, msg) = (1, 'unable to communicate with GUI instance "%s"' % context.dbus_host_bus_name) else: remote_interface = Interface( remote_object, context.dbus_base_bus_name + '.PluginManager') (rc, msg) = remote_interface.start_plugin( self._options.command_start_plugin, ' '.join(self._options.plugin_args)) if rc == 0: print('qt_gui_main() started plugin "%s" in GUI "%s"' % (msg, context.dbus_host_bus_name)) else: print( 'qt_gui_main() could not start plugin "%s" in GUI "%s": %s' % (self._options.command_start_plugin, context.dbus_host_bus_name, msg)) return rc elif self._options.command_switch_perspective is not None: remote_object = SessionBus().get_object( context.dbus_host_bus_name, '/PerspectiveManager') remote_interface = Interface( remote_object, context.dbus_base_bus_name + '.PerspectiveManager') remote_interface.switch_perspective( self._options.command_switch_perspective) print( 'qt_gui_main() switched to perspective "%s" in GUI "%s"' % (self._options.command_switch_perspective, context.dbus_host_bus_name)) return 0 raise RuntimeError('Unknown command not handled') # choose selected or default qt binding setattr(sys, 'SELECT_QT_BINDING', self._options.qt_binding) from python_qt_binding import QT_BINDING from python_qt_binding.QtCore import qDebug, qInstallMsgHandler, QSettings, Qt, QtCriticalMsg, QtDebugMsg, QtFatalMsg, QTimer, QtWarningMsg from python_qt_binding.QtGui import QAction, QIcon, QMenuBar from .about_handler import AboutHandler from .composite_plugin_provider import CompositePluginProvider from .container_manager import ContainerManager from .help_provider import HelpProvider from .main_window import MainWindow from .minimized_dock_widgets_toolbar import MinimizedDockWidgetsToolbar from .perspective_manager import PerspectiveManager from .plugin_manager import PluginManager def message_handler(type_, msg): colored_output = 'TERM' in os.environ and 'ANSI_COLORS_DISABLED' not in os.environ cyan_color = '\033[36m' if colored_output else '' red_color = '\033[31m' if colored_output else '' reset_color = '\033[0m' if colored_output else '' if type_ == QtDebugMsg and self._options.verbose: print(msg, file=sys.stderr) elif type_ == QtWarningMsg: print(cyan_color + msg + reset_color, file=sys.stderr) elif type_ == QtCriticalMsg: print(red_color + msg + reset_color, file=sys.stderr) elif type_ == QtFatalMsg: print(red_color + msg + reset_color, file=sys.stderr) sys.exit(1) qInstallMsgHandler(message_handler) app = self.create_application(argv) self._check_icon_theme_compliance() settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'ros.org', self._settings_filename) if len(embed_options_set) == 0: if self._options.clear_config: settings.clear() main_window = MainWindow() if self._options.on_top: main_window.setWindowFlags(Qt.WindowStaysOnTopHint) main_window.statusBar() def sigint_handler(*args): qDebug('\nsigint_handler()') main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler timer = QTimer() timer.start(500) timer.timeout.connect(lambda: None) # create own menu bar to share one menu bar on Mac menu_bar = QMenuBar() if 'darwin' in platform.platform().lower(): menu_bar.setNativeMenuBar(True) else: menu_bar.setNativeMenuBar(False) if not self._options.lock_perspective: main_window.setMenuBar(menu_bar) file_menu = menu_bar.addMenu(menu_bar.tr('&File')) action = QAction(file_menu.tr('&Quit'), file_menu) action.setIcon(QIcon.fromTheme('application-exit')) action.triggered.connect(main_window.close) file_menu.addAction(action) else: app.setQuitOnLastWindowClosed(False) main_window = None menu_bar = None self._add_plugin_providers() # setup plugin manager plugin_provider = CompositePluginProvider(self.plugin_providers) plugin_manager = PluginManager( plugin_provider, settings, context, settings_prefix=plugin_manager_settings_prefix) if self._options.list_plugins: # output available plugins print('\n'.join(sorted(plugin_manager.get_plugins().values()))) return 0 help_provider = HelpProvider() plugin_manager.plugin_help_signal.connect( help_provider.plugin_help_request) # setup perspective manager if main_window is not None: perspective_manager = PerspectiveManager(settings, context) if self._options.list_perspectives: # output available perspectives print('\n'.join(sorted(perspective_manager.perspectives))) return 0 else: perspective_manager = None if main_window is not None: container_manager = ContainerManager(main_window, plugin_manager) plugin_manager.set_main_window(main_window, menu_bar, container_manager) if not self._options.freeze_layout: minimized_dock_widgets_toolbar = MinimizedDockWidgetsToolbar( container_manager, main_window) main_window.addToolBar(Qt.BottomToolBarArea, minimized_dock_widgets_toolbar) plugin_manager.set_minimized_dock_widgets_toolbar( minimized_dock_widgets_toolbar) if menu_bar is not None: perspective_menu = menu_bar.addMenu(menu_bar.tr('P&erspectives')) perspective_manager.set_menu(perspective_menu) # connect various signals and slots if perspective_manager is not None and main_window is not None: # signal changed perspective to update window title perspective_manager.perspective_changed_signal.connect( main_window.perspective_changed) # signal new settings due to changed perspective perspective_manager.save_settings_signal.connect( main_window.save_settings) perspective_manager.restore_settings_signal.connect( main_window.restore_settings) perspective_manager.restore_settings_without_plugin_changes_signal.connect( main_window.restore_settings) if perspective_manager is not None and plugin_manager is not None: perspective_manager.save_settings_signal.connect( plugin_manager.save_settings) plugin_manager.save_settings_completed_signal.connect( perspective_manager.save_settings_completed) perspective_manager.restore_settings_signal.connect( plugin_manager.restore_settings) perspective_manager.restore_settings_without_plugin_changes_signal.connect( plugin_manager.restore_settings_without_plugins) if plugin_manager is not None and main_window is not None: # signal before changing plugins to save window state plugin_manager.plugins_about_to_change_signal.connect( main_window.save_setup) # signal changed plugins to restore window state plugin_manager.plugins_changed_signal.connect( main_window.restore_state) # signal save settings to store plugin setup on close main_window.save_settings_before_close_signal.connect( plugin_manager.close_application) # signal save and shutdown called for all plugins, trigger closing main window again plugin_manager.close_application_signal.connect( main_window.close, type=Qt.QueuedConnection) if main_window is not None and menu_bar is not None: about_handler = AboutHandler(context.qtgui_path, main_window) help_menu = menu_bar.addMenu(menu_bar.tr('&Help')) action = QAction(file_menu.tr('&About'), help_menu) action.setIcon(QIcon.fromTheme('help-about')) action.triggered.connect(about_handler.show) help_menu.addAction(action) # set initial size - only used without saved configuration if main_window is not None: main_window.resize(600, 450) main_window.move(100, 100) # ensure that qt_gui/src is in sys.path src_path = os.path.realpath( os.path.join(os.path.dirname(__file__), '..')) if src_path not in sys.path: sys.path.append(src_path) # load specific plugin plugin = None plugin_serial = None if self._options.embed_plugin is not None: plugin = self._options.embed_plugin plugin_serial = self._options.embed_plugin_serial elif self._options.standalone_plugin is not None: plugin = self._options.standalone_plugin plugin_serial = 0 if plugin is not None: plugins = plugin_manager.find_plugins_by_name(plugin) if len(plugins) == 0: print('qt_gui_main() found no plugin matching "%s"' % plugin) return 1 elif len(plugins) > 1: print( 'qt_gui_main() found multiple plugins matching "%s"\n%s' % (plugin, '\n'.join(plugins.values()))) return 1 plugin = plugins.keys()[0] qDebug('QtBindingHelper using %s' % QT_BINDING) plugin_manager.discover() if self._options.reload_import: qDebug( 'ReloadImporter() automatically reload all subsequent imports') from .reload_importer import ReloadImporter _reload_importer = ReloadImporter() self._add_reload_paths(_reload_importer) _reload_importer.enable() # switch perspective if perspective_manager is not None: if plugin: perspective_manager.set_perspective( plugin, hide_and_without_plugin_changes=True) elif self._options.perspective_file: perspective_manager.import_perspective_from_file( self._options.perspective_file, perspective_manager.HIDDEN_PREFIX + '__cli_perspective_from_file') else: perspective_manager.set_perspective(self._options.perspective) # load specific plugin if plugin: plugin_manager.load_plugin(plugin, plugin_serial, self._options.plugin_args) running = plugin_manager.is_plugin_running(plugin, plugin_serial) if not running: return 1 if main_window is not None: main_window.show() if sys.platform == 'darwin': main_window.raise_() return app.exec_()
count_dock_widgets = 0 def add_dock_widget(orientation): global count_dock_widgets count_dock_widgets += 1 dw = QDockWidget('dockwidget%d' % count_dock_widgets, mw) dw.setObjectName('dockwidget%d' % count_dock_widgets) mw.addDockWidget(Qt.BottomDockWidgetArea, dw, orientation) def add_horizontal(self): add_dock_widget(Qt.Horizontal) def add_vertical(self): add_dock_widget(Qt.Vertical) a1 = QAction('add h', tb) a1.do = add_horizontal a1.triggered.connect(a1.do) tb.addAction(a1) a2 = QAction('add v', tb) a2.do = add_vertical a2.triggered.connect(a2.do) tb.addAction(a2) def save(self): global mw, settings settings.setValue('state', mw.saveState()) print('saved') def restore(self):
class PerspectiveManager(QObject): """Manager for perspectives associated with specific sets of `Settings`.""" perspective_changed_signal = Signal(basestring) save_settings_signal = Signal(Settings, Settings) restore_settings_signal = Signal(Settings, Settings) restore_settings_without_plugin_changes_signal = Signal(Settings, Settings) HIDDEN_PREFIX = '@' def __init__(self, settings, application_context): super(PerspectiveManager, self).__init__() self.setObjectName('PerspectiveManager') self._qtgui_path = application_context.qtgui_path self._settings_proxy = SettingsProxy(settings) self._global_settings = Settings(self._settings_proxy, 'global') self._perspective_settings = None self._create_perspective_dialog = None self._menu_manager = None self._perspective_mapper = None # get perspective list from settings self.perspectives = self._settings_proxy.value('', 'perspectives', []) if isinstance(self.perspectives, basestring): self.perspectives = [self.perspectives] self._current_perspective = None self._remove_action = None self._callback = None self._callback_args = [] if application_context.provide_app_dbus_interfaces: from .perspective_manager_dbus_interface import PerspectiveManagerDBusInterface self._dbus_server = PerspectiveManagerDBusInterface( self, application_context) def set_menu(self, menu): self._menu_manager = MenuManager(menu) self._perspective_mapper = QSignalMapper(menu) self._perspective_mapper.mapped[str].connect(self.switch_perspective) # generate menu create_action = QAction('&Create perspective...', self._menu_manager.menu) create_action.setIcon(QIcon.fromTheme('list-add')) create_action.triggered.connect(self._on_create_perspective) self._menu_manager.add_suffix(create_action) self._remove_action = QAction('&Remove perspective...', self._menu_manager.menu) self._remove_action.setEnabled(False) self._remove_action.setIcon(QIcon.fromTheme('list-remove')) self._remove_action.triggered.connect(self._on_remove_perspective) self._menu_manager.add_suffix(self._remove_action) self._menu_manager.add_suffix(None) import_action = QAction('&Import...', self._menu_manager.menu) import_action.setIcon(QIcon.fromTheme('document-open')) import_action.triggered.connect(self._on_import_perspective) self._menu_manager.add_suffix(import_action) export_action = QAction('&Export...', self._menu_manager.menu) export_action.setIcon(QIcon.fromTheme('document-save-as')) export_action.triggered.connect(self._on_export_perspective) self._menu_manager.add_suffix(export_action) # add perspectives to menu for name in self.perspectives: if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def set_perspective(self, name, hide_and_without_plugin_changes=False): if name is None: name = self._settings_proxy.value('', 'current-perspective', 'Default') elif hide_and_without_plugin_changes: name = self.HIDDEN_PREFIX + name self.switch_perspective( name, save_before=not hide_and_without_plugin_changes, without_plugin_changes=hide_and_without_plugin_changes) @Slot(str) @Slot(str, bool) @Slot(str, bool, bool) def switch_perspective(self, name, settings_changed=True, save_before=True, without_plugin_changes=False): if save_before and self._global_settings is not None and self._perspective_settings is not None: self._callback = self._switch_perspective self._callback_args = [name, settings_changed, save_before] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._switch_perspective(name, settings_changed, save_before, without_plugin_changes) def _switch_perspective(self, name, settings_changed, save_before, without_plugin_changes=False): # convert from unicode name = str(name.replace('/', '__')) qDebug( 'PerspectiveManager.switch_perspective() switching to perspective "%s"' % name) if self._current_perspective is not None and self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, False) self._menu_manager.set_item_disabled(self._current_perspective, False) # create perspective if necessary if name not in self.perspectives: self._create_perspective(name, clone_perspective=False) # update current perspective self._current_perspective = name if self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, True) self._menu_manager.set_item_disabled(self._current_perspective, True) if not self._current_perspective.startswith(self.HIDDEN_PREFIX): self._settings_proxy.set_value('', 'current-perspective', self._current_perspective) self._perspective_settings = self._get_perspective_settings( self._current_perspective) # emit signals self.perspective_changed_signal.emit( self._current_perspective.lstrip(self.HIDDEN_PREFIX)) if settings_changed: if not without_plugin_changes: self.restore_settings_signal.emit(self._global_settings, self._perspective_settings) else: self.restore_settings_without_plugin_changes_signal.emit( self._global_settings, self._perspective_settings) def save_settings_completed(self): if self._callback is not None: callback = self._callback callback_args = self._callback_args self._callback = None self._callback_args = [] callback(*callback_args) def _get_perspective_settings(self, perspective_name): return Settings(self._settings_proxy, 'perspective/%s' % perspective_name) def _on_create_perspective(self): name = self._choose_new_perspective_name() if name is not None: clone_perspective = self._create_perspective_dialog.clone_checkbox.isChecked( ) self._create_perspective(name, clone_perspective) self.switch_perspective(name, settings_changed=not clone_perspective, save_before=False) def _choose_new_perspective_name(self, show_cloning=True): # input dialog for new perspective name if self._create_perspective_dialog is None: ui_file = os.path.join(self._qtgui_path, 'resource', 'perspective_create.ui') self._create_perspective_dialog = loadUi(ui_file) # custom validator preventing forward slashs class CustomValidator(QValidator): def __init__(self, parent=None): super(CustomValidator, self).__init__(parent) def fixup(self, value): value = value.replace('/', '') def validate(self, value, pos): if value.find('/') != -1: pos = value.find('/') return (QValidator.Invalid, value, pos) if value == '': return (QValidator.Intermediate, value, pos) return (QValidator.Acceptable, value, pos) self._create_perspective_dialog.perspective_name_edit.setValidator( CustomValidator()) # set default values self._create_perspective_dialog.perspective_name_edit.setText('') self._create_perspective_dialog.clone_checkbox.setChecked(True) self._create_perspective_dialog.clone_checkbox.setVisible(show_cloning) # show dialog and wait for it's return value return_value = self._create_perspective_dialog.exec_() if return_value == self._create_perspective_dialog.Rejected: return name = str(self._create_perspective_dialog.perspective_name_edit.text( )).lstrip(self.HIDDEN_PREFIX) if name == '': QMessageBox.warning( self._menu_manager.menu, self.tr('Empty perspective name'), self.tr('The name of the perspective must be non-empty.')) return if name in self.perspectives: QMessageBox.warning( self._menu_manager.menu, self.tr('Duplicate perspective name'), self.tr('A perspective with the same name already exists.')) return return name def _create_perspective(self, name, clone_perspective=True): # convert from unicode name = str(name) if name.find('/') != -1: raise RuntimeError( 'PerspectiveManager._create_perspective() name must not contain forward slashs (/)' ) qDebug('PerspectiveManager._create_perspective(%s, %s)' % (name, clone_perspective)) # add to list of perspectives self.perspectives.append(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # save current settings if self._global_settings is not None and self._perspective_settings is not None: self._callback = self._create_perspective_continued self._callback_args = [name, clone_perspective] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._create_perspective_continued(name, clone_perspective) def _create_perspective_continued(self, name, clone_perspective): # clone settings if clone_perspective: new_settings = self._get_perspective_settings(name) keys = self._perspective_settings.all_keys() for key in keys: value = self._perspective_settings.value(key) new_settings.set_value(key, value) # add and switch to perspective if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def _add_perspective_action(self, name): if self._menu_manager is not None: # create action action = QAction(name, self._menu_manager.menu) action.setCheckable(True) self._perspective_mapper.setMapping(action, name) action.triggered.connect(self._perspective_mapper.map) # add action to menu self._menu_manager.add_item(action) # enable remove-action if self._menu_manager.count_items() > 1: self._remove_action.setEnabled(True) def _on_remove_perspective(self): # input dialog to choose perspective to be removed names = list(self.perspectives) names.remove(self._current_perspective) name, return_value = QInputDialog.getItem( self._menu_manager.menu, self._menu_manager.tr('Remove perspective'), self._menu_manager.tr('Select the perspective'), names, 0, False) # convert from unicode name = str(name) if return_value == QInputDialog.Rejected: return self._remove_perspective(name) def _remove_perspective(self, name): if name not in self.perspectives: raise UserWarning('unknown perspective: %s' % name) qDebug('PerspectiveManager._remove_perspective(%s)' % str(name)) # remove from list of perspectives self.perspectives.remove(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # remove settings settings = self._get_perspective_settings(name) settings.remove('') # remove from menu self._menu_manager.remove_item(name) # disable remove-action if self._menu_manager.count_items() < 2: self._remove_action.setEnabled(False) def _on_import_perspective(self): file_name, _ = QFileDialog.getOpenFileName( self._menu_manager.menu, self.tr('Import perspective from file'), None, self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return perspective_name = os.path.basename(file_name) suffix = '.perspective' if perspective_name.endswith(suffix): perspective_name = perspective_name[:-len(suffix)] if perspective_name in self.perspectives: perspective_name = self._choose_new_perspective_name(False) if perspective_name is None: return self.import_perspective_from_file(file_name, perspective_name) def import_perspective_from_file(self, path, perspective_name): # create clean perspective if perspective_name in self.perspectives: self._remove_perspective(perspective_name) self._create_perspective(perspective_name, clone_perspective=False) # read perspective from file file_handle = open(path, 'r') #data = eval(file_handle.read()) data = json.loads(file_handle.read()) self._convert_values(data, self._import_value) new_settings = self._get_perspective_settings(perspective_name) self._set_dict_on_settings(data, new_settings) self.switch_perspective(perspective_name, settings_changed=True, save_before=True) def _set_dict_on_settings(self, data, settings): """Set dictionary key-value pairs on Settings instance.""" keys = data.get('keys', {}) for key in keys: settings.set_value(key, keys[key]) groups = data.get('groups', {}) for group in groups: sub = settings.get_settings(group) self._set_dict_on_settings(groups[group], sub) def _on_export_perspective(self): file_name, _ = QFileDialog.getSaveFileName( self._menu_manager.menu, self.tr('Export perspective to file'), self._current_perspective + '.perspective', self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return # trigger save of perspective before export self._callback = self._on_export_perspective_continued self._callback_args = [file_name] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) def _on_export_perspective_continued(self, file_name): # convert every value data = self._get_dict_from_settings(self._perspective_settings) self._convert_values(data, self._export_value) # write perspective data to file file_handle = open(file_name, 'w') file_handle.write(json.dumps(data, indent=2)) file_handle.close() def _get_dict_from_settings(self, settings): """Convert data of Settings instance to dictionary.""" keys = {} for key in settings.child_keys(): keys[str(key)] = settings.value(key) groups = {} for group in settings.child_groups(): sub = settings.get_settings(group) groups[str(group)] = self._get_dict_from_settings(sub) return {'keys': keys, 'groups': groups} def _convert_values(self, data, convert_function): keys = data.get('keys', {}) for key in keys: keys[key] = convert_function(keys[key]) groups = data.get('groups', {}) for group in groups: self._convert_values(groups[group], convert_function) def _import_value(self, value): import QtCore # @UnusedImport if value['type'] == 'repr': return eval(value['repr']) elif value['type'] == 'repr(QByteArray.hex)': return QByteArray.fromHex(eval(value['repr(QByteArray.hex)'])) raise RuntimeError( 'PerspectiveManager._import_value() unknown serialization type (%s)' % value['type']) def _export_value(self, value): data = {} if value.__class__.__name__ == 'QByteArray': hex_value = value.toHex() data['repr(QByteArray.hex)'] = self._strip_qt_binding_prefix( hex_value, repr(hex_value)) data['type'] = 'repr(QByteArray.hex)' # add pretty print for better readability characters = '' for i in range(1, value.size(), 2): character = value.at(i) # output all non-control characters if character >= ' ' and character <= '~': characters += character else: characters += ' ' data['pretty-print'] = characters else: data['repr'] = self._strip_qt_binding_prefix(value, repr(value)) data['type'] = 'repr' # verify that serialized data can be deserialized correctly reimported = self._import_value(data) if reimported != value: raise RuntimeError( 'PerspectiveManager._export_value() stored value can not be restored (%s)' % type(value)) return data def _strip_qt_binding_prefix(self, obj, data): """Strip binding specific prefix from type string.""" parts = obj.__class__.__module__.split('.') if len(parts) > 1 and parts[1] == 'QtCore': prefix = '.'.join(parts[:2]) data = data.replace(prefix, 'QtCore', 1) return data
def _create_tag_menu(self, parent=None): # creates a tag menu tag_menu = QMenu("ROS Tags", parent) # group tag add_group_tag_action = QAction("<group>", self, statusTip="", triggered=self._on_add_group_tag) add_group_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+g")) tag_menu.addAction(add_group_tag_action) # node tag add_node_tag_action = QAction("<node>", self, statusTip="", triggered=self._on_add_node_tag) add_node_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+n")) tag_menu.addAction(add_node_tag_action) # node tag with all attributes add_node_tag_all_action = QAction("<node all>", self, statusTip="", triggered=self._on_add_node_tag_all) tag_menu.addAction(add_node_tag_all_action) # include tag with all attributes add_include_tag_all_action = QAction("<include>", self, statusTip="", triggered=self._on_add_include_tag_all) add_include_tag_all_action.setShortcuts(QKeySequence("Ctrl+Shift+i")) tag_menu.addAction(add_include_tag_all_action) # remap add_remap_tag_action = QAction("<remap>", self, statusTip="", triggered=self._on_add_remap_tag) add_remap_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+r")) tag_menu.addAction(add_remap_tag_action) # env tag add_env_tag_action = QAction("<env>", self, statusTip="", triggered=self._on_add_env_tag) tag_menu.addAction(add_env_tag_action) # param tag add_param_tag_action = QAction("<param>", self, statusTip="", triggered=self._on_add_param_tag) add_param_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+p")) tag_menu.addAction(add_param_tag_action) # param capability group tag add_param_cap_group_tag_action = QAction("<param capability group>", self, statusTip="", triggered=self._on_add_param_cap_group_tag) add_param_cap_group_tag_action.setShortcuts(QKeySequence("Ctrl+Alt+p")) tag_menu.addAction(add_param_cap_group_tag_action) # param tag with all attributes add_param_tag_all_action = QAction("<param all>", self, statusTip="", triggered=self._on_add_param_tag_all) tag_menu.addAction(add_param_tag_all_action) # rosparam tag with all attributes add_rosparam_tag_all_action = QAction("<rosparam>", self, statusTip="", triggered=self._on_add_rosparam_tag_all) tag_menu.addAction(add_rosparam_tag_all_action) # arg tag with default definition add_arg_tag_default_action = QAction("<arg default>", self, statusTip="", triggered=self._on_add_arg_tag_default) add_arg_tag_default_action.setShortcuts(QKeySequence("Ctrl+Shift+a")) tag_menu.addAction(add_arg_tag_default_action) # arg tag with value definition add_arg_tag_value_action = QAction("<arg value>", self, statusTip="", triggered=self._on_add_arg_tag_value) add_arg_tag_value_action.setShortcuts(QKeySequence("Ctrl+Alt+a")) tag_menu.addAction(add_arg_tag_value_action) # test tag add_test_tag_action = QAction("<test>", self, statusTip="", triggered=self._on_add_test_tag) add_test_tag_action.setShortcuts(QKeySequence("Ctrl+Alt+t")) tag_menu.addAction(add_test_tag_action) # test tag with all attributes add_test_tag_all_action = QAction("<test all>", self, statusTip="", triggered=self._on_add_test_tag_all) tag_menu.addAction(add_test_tag_all_action) return tag_menu
def _create_tag_menu(self, parent=None): # creates a tag menu tag_menu = QMenu("ROS Tags", parent) # group tag add_group_tag_action = QAction("<group>", self, statusTip="", triggered=self._on_add_group_tag) add_group_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+g")) tag_menu.addAction(add_group_tag_action) # node tag add_node_tag_action = QAction("<node>", self, statusTip="", triggered=self._on_add_node_tag) add_node_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+n")) tag_menu.addAction(add_node_tag_action) # node tag with all attributes add_node_tag_all_action = QAction("<node all>", self, statusTip="", triggered=self._on_add_node_tag_all) tag_menu.addAction(add_node_tag_all_action) # include tag with all attributes add_include_tag_all_action = QAction("<include>", self, statusTip="", triggered=self._on_add_include_tag_all) add_include_tag_all_action.setShortcuts(QKeySequence("Ctrl+Shift+i")) tag_menu.addAction(add_include_tag_all_action) # remap add_remap_tag_action = QAction("<remap>", self, statusTip="", triggered=self._on_add_remap_tag) add_remap_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+r")) tag_menu.addAction(add_remap_tag_action) # env tag add_env_tag_action = QAction("<env>", self, statusTip="", triggered=self._on_add_env_tag) tag_menu.addAction(add_env_tag_action) # param tag add_param_clipboard_tag_action = QAction("<param value>", self, statusTip="add value from clipboard", triggered=self._on_add_param_clipboard_tag) add_param_clipboard_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+p")) tag_menu.addAction(add_param_clipboard_tag_action) add_param_tag_action = QAction("<param>", self, statusTip="", triggered=self._on_add_param_tag) add_param_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+Alt+p")) tag_menu.addAction(add_param_tag_action) # param tag with all attributes add_param_tag_all_action = QAction("<param all>", self, statusTip="", triggered=self._on_add_param_tag_all) tag_menu.addAction(add_param_tag_all_action) # rosparam tag with all attributes add_rosparam_tag_all_action = QAction("<rosparam>", self, statusTip="", triggered=self._on_add_rosparam_tag_all) tag_menu.addAction(add_rosparam_tag_all_action) # arg tag with default definition add_arg_tag_default_action = QAction("<arg default>", self, statusTip="", triggered=self._on_add_arg_tag_default) add_arg_tag_default_action.setShortcuts(QKeySequence("Ctrl+Shift+a")) tag_menu.addAction(add_arg_tag_default_action) # arg tag with value definition add_arg_tag_value_action = QAction("<arg value>", self, statusTip="", triggered=self._on_add_arg_tag_value) add_arg_tag_value_action.setShortcuts(QKeySequence("Ctrl+Alt+a")) tag_menu.addAction(add_arg_tag_value_action) # test tag add_test_tag_action = QAction("<test>", self, statusTip="", triggered=self._on_add_test_tag) add_test_tag_action.setShortcuts(QKeySequence("Ctrl+Alt+t")) tag_menu.addAction(add_test_tag_action) # test tag with all attributes add_test_tag_all_action = QAction("<test all>", self, statusTip="", triggered=self._on_add_test_tag_all) tag_menu.addAction(add_test_tag_all_action) sub_cp_menu = QMenu("Custom parameters", parent) show_cp_dialog_action = QAction("Show Dialog", self, statusTip="", triggered=self._show_custom_parameter_dialog) show_cp_dialog_action.setShortcuts(QKeySequence("Ctrl+Shift+d")) sub_cp_menu.addAction(show_cp_dialog_action) add_cp_associations_action = QAction("nm/associations", self, statusTip="", triggered=self._on_add_cp_associations) add_cp_associations_action.setShortcuts(QKeySequence("Ctrl+Alt+a")) sub_cp_menu.addAction(add_cp_associations_action) sub_cp_as_menu = QMenu("Autostart", parent) add_cp_as_delay_action = QAction("delay", self, statusTip="", triggered=self._on_add_cp_as_delay) sub_cp_as_menu.addAction(add_cp_as_delay_action) add_cp_as_exclude_action = QAction("exclude", self, statusTip="", triggered=self._on_add_cp_as_exclude) sub_cp_as_menu.addAction(add_cp_as_exclude_action) add_cp_as_req_publisher_action = QAction("required publisher", self, statusTip="", triggered=self._on_add_cp_as_req_publisher) sub_cp_as_menu.addAction(add_cp_as_req_publisher_action) sub_cp_menu.addMenu(sub_cp_as_menu) sub_cp_r_menu = QMenu("Respawn", parent) add_cp_r_max_action = QAction("max", self, statusTip="", triggered=self._on_add_cp_r_max) sub_cp_r_menu.addAction(add_cp_r_max_action) add_cp_r_min_runtime_action = QAction("min_runtime", self, statusTip="", triggered=self._on_add_cp_r_min_runtime) sub_cp_r_menu.addAction(add_cp_r_min_runtime_action) add_cp_r_delay_action = QAction("delay", self, statusTip="", triggered=self._on_add_cp_r_delay) sub_cp_r_menu.addAction(add_cp_r_delay_action) sub_cp_menu.addMenu(sub_cp_r_menu) add_cp_capability_group_action = QAction("capability_group", self, statusTip="", triggered=self._on_add_cp_capability_group) add_cp_capability_group_action.setShortcuts(QKeySequence("Ctrl+Alt+p")) sub_cp_menu.addAction(add_cp_capability_group_action) add_cp_kill_on_stop_action = QAction("nm/kill_on_stop", self, statusTip="True or time to wait in ms", triggered=self._on_add_cp_kill_on_stop) add_cp_kill_on_stop_action.setShortcuts(QKeySequence("Ctrl+Shift+k")) sub_cp_menu.addAction(add_cp_kill_on_stop_action) tag_menu.addMenu(sub_cp_menu) return tag_menu
def __init__(self, menu_button): QMenu.__init__(self) self.button = menu_button try: rqt_icon_path = roslib.packages.find_resource( 'rqt_gui', 'rqt.png').pop() menu_button.setText('') menu_button.setIcon(QIcon(rqt_icon_path)) # creates a default config menu self.action_rqt_console = QAction( QIcon.fromTheme('mail-message-new'), "&Console", self, statusTip= '"<p>Starts a python GUI plugin for displaying and filtering ' 'ROS log messages that is connected to the selected master.</p>"', triggered=self.on_show_console_clicked) self.addAction(self.action_rqt_console) self.action_rqt_logger_level = QAction( QIcon.fromTheme('format-indent-more'), "&Logger Level", self, statusTip= '"<p>Starts a python GUI plugin for configuring the level of ' 'ROS loggers that is connected to the selected master.</p>"', triggered=self.on_show_logger_level_clicked) self.addAction(self.action_rqt_logger_level) self.action_rqt_tf_tree = QAction( QIcon.fromTheme('preferences-system-network'), "&TF Tree", self, statusTip= '"<p>Starts a python GUI plugin for visualizing the TF tree' 'that is connected to the selected master.</p>"', triggered=self.on_show_tf_tree_clicked) self.addAction(self.action_rqt_tf_tree) self.action_rqt_ros_graph = QAction( QIcon(":/icons/button_graph.png"), "Ros &Graph", self, statusTip= '"<p>Starts a python GUI plugin for visualizing the ROS computation graph' 'that is connected to the selected master</p>"', triggered=self.on_show_ros_graph_clicked) self.addAction(self.action_rqt_ros_graph) self.action_rosbag_record = QAction( QIcon.fromTheme('media-record'), "rosbag record", self, statusTip= '"<p>Starts the rosbag record with selected topics</p>"', triggered=self.on_start_rosbag_clicked) self.addAction(self.action_rosbag_record) self.action_rqt_rviz = QAction(QIcon.fromTheme('image-x-generic'), "R&Viz", self, statusTip='"<p>Starts RViz</p>"', triggered=self.on_show_rviz_clicked) self.addAction(self.action_rqt_rviz) self.addSeparator() self.action_rqt = QAction( QIcon(rqt_icon_path), "&Rqt GUI", self, statusTip='"<p>Start the rqt GUI' 'that is connected to the selected master</p>"', triggered=self.on_start_rqt_clicked) self.addAction(self.action_rqt) self.action_terminal = QAction( QIcon(":/icons/crystal_clear_show_io.png"), "&Terminal", self, statusTip='"<p>Start terminal on selected host</p>"', triggered=self.on_start_terminal) self.action_terminal.setShortcut(QKeySequence("Ctrl+T")) self.addAction(self.action_terminal) menu_button.setMenu(self) except Exception as e: print('%s' % e) menu_button.setEnabled(False) menu_button.setToolTip( 'rqt_gui not found! Please install rqt to use its plugins!')
def __init__(self, parent=None): ''' Creates the window, connects the signals and init the class. ''' QDockWidget.__init__(self, parent) # initialize parameter self.__current_path = os.path.expanduser('~') # load the UI file ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ui', 'LaunchFilesDockWidget.ui') loadUi(ui_file, self, custom_widgets={'EnhancedLineEdit': EnhancedLineEdit}) self.ui_button_progress_cancel_cfg.setIcon( nm.settings().icon('crystal_clear_button_close.png')) self.ui_button_reload.setIcon( nm.settings().icon('oxygen_view_refresh.png')) self.ui_button_edit.setIcon( nm.settings().icon('crystal_clear_edit_launch.png')) self.ui_button_new.setIcon(nm.settings().icon('crystal_clear_add.png')) self.ui_button_transfer.setIcon( nm.settings().icon('crystal_clear_launch_file_transfer.png')) self.ui_button_save_profile.setIcon( nm.settings().icon('crystal_clear_profile_new.png')) self.ui_button_load.setIcon( nm.settings().icon('crystal_clear_launch_file.png')) self._current_search = '' pal = self.palette() self._default_color = pal.color(QPalette.Window) # initialize the progress queue self.progress_queue = ProgressQueue(self.ui_frame_progress_cfg, self.ui_bar_progress_cfg, self.ui_button_progress_cancel_cfg, 'Launch File') # initialize the view for the launch files self.launchlist_model = LaunchListModel( progress_queue=self.progress_queue, viewobj=self.ui_file_view) self.launchlist_proxy_model = QSortFilterProxyModel(self) self.launchlist_proxy_model.setSourceModel(self.launchlist_model) self.name_delegate = HTMLDelegate(check_for_ros_names=False, palette=self.palette()) self.ui_file_view.setItemDelegateForColumn(0, self.name_delegate) self.ui_file_view.setModel(self.launchlist_proxy_model) self.ui_file_view.setAlternatingRowColors(True) self.ui_file_view.activated.connect(self.on_launch_selection_activated) self.ui_file_view.setDragDropMode(QAbstractItemView.DragOnly) self.ui_file_view.setDragEnabled(True) sm = self.ui_file_view.selectionModel() sm.selectionChanged.connect(self.on_ui_file_view_selection_changed) self.launchlist_model.pathlist_handled.connect( self.on_pathlist_handled) self.launchlist_model.error_on_path.connect(self.on_error_on_path) self.ui_search_line.refresh_signal.connect(self.set_package_filter) self.ui_search_line.stop_signal.connect(self.stop) # connect to the button signals self.ui_button_reload.clicked.connect(self.on_reload_clicked) self.ui_button_edit.clicked.connect(self.on_edit_xml_clicked) #self.ui_button_new.clicked.connect(self.on_new_xml_clicked) self.ui_button_transfer.clicked.connect(self.on_transfer_file_clicked) self.ui_button_save_profile.clicked.connect( self.on_save_profile_clicked) self.ui_button_load.clicked.connect(self.on_load_xml_clicked) # add menu to create fiel or directory self._menu_add = QMenu() create_file_action = QAction( nm.settings().icon('crystal_clear_launch_file_new.png'), "create file", self, statusTip="", triggered=self.on_new_xml_clicked) create_dir_action = QAction( nm.settings().icon('crystal_clear_folder.png'), "create directory", self, statusTip="", triggered=self.on_new_dir_clicked) self._menu_add.addAction(create_file_action) self._menu_add.addAction(create_dir_action) self.ui_button_new.setMenu(self._menu_add) self._masteruri2name = {} self._reload_timer = None
class QParameterTreeWidget(QTreeWidget): logger = Logger() def __init__(self, parent=None, logger=Logger()): QTreeWidget.__init__(self, parent) self.set_logger(logger) # init tree self.setHeaderLabels(["Name", "Type", "Value"]) self.sortItems(0, Qt.AscendingOrder) #self.setSelectionMode(QAbstractItemView.NoSelection) self.setContextMenuPolicy(Qt.CustomContextMenu) self.itemActivated.connect(self.edit_item) self.currentItemChanged.connect(self.current_item_changed) # context menu self.customContextMenuRequested.connect(self.context_menu_request) self._action_item_expand = QAction(QIcon.fromTheme('zoom-in'), 'Expand Selected', self) self._action_item_expand.triggered.connect( self._handle_action_item_expand) self._action_item_collapse = QAction(QIcon.fromTheme('zoom-out'), 'Collapse Selected', self) self._action_item_collapse.triggered.connect( self._handle_action_item_collapse) self._action_item_add = QAction(QIcon.fromTheme('list-add'), 'Add', self) self._action_item_add.setEnabled(False) # TODO self._action_item_remove = QAction(QIcon.fromTheme('list-remove'), 'Remove', self) self._action_item_remove.setEnabled(False) # TODO def set_logger(self, logger): self.logger = logger @Slot(QPoint) def context_menu_request(self, point): if self.selectionModel().hasSelection(): menu = QMenu(self) menu.addAction(self._action_item_add) menu.addAction(self._action_item_remove) menu.addSeparator() menu.addAction(self._action_item_expand) menu.addAction(self._action_item_collapse) menu.exec_(self.mapToGlobal(point)) @Slot() def _handle_action_item_collapse(self): self._handle_action_set_expanded(False) @Slot() def _handle_action_item_expand(self): self._handle_action_set_expanded(True) @Slot(bool) def _handle_action_set_expanded(self, expanded): def recursive_set_expanded(index): if (index != QModelIndex()) and (index.column() == 0): self.setExpanded(index, expanded) #for i in range(index.model().childCount()): # index.model().child(i).setExpanded(expanded) #for i in range(index.model().rowCount()): # recursive_set_expanded(index.child(i, 0)) for index in self.selectedIndexes(): recursive_set_expanded(index) @Slot(QTreeWidgetItem, int) def edit_item(self, item, column): if (column == 0) or (item.is_leaf() and (column == 2)): item.setFlags(item.flags() | Qt.ItemIsEditable) self.editItem(item, column) item.setFlags(item.flags() & ~Qt.ItemIsEditable) @Slot(QTreeWidgetItem, QTreeWidgetItem) def current_item_changed(self, prev, current): if prev is not None: if not prev.update_value(): self.logger.log_error("Couldn't update value for '" + prev.get_name() + "' in '" + prev.get_namespace() + "'. Check input syntax!") if current is not None: if not current.update_value(): self.logger.log_error("Couldn't update value for '" + current.get_name() + "' in '" + current.get_namespace() + "'. Check input syntax!") # set parameter set from msg def set_parameter_set(self, param_set_msg): self.clear() self.root = QParameterTreeWidgetItem(self, self.logger, name=param_set_msg.name.data) self.root.setExpanded(True) for p in param_set_msg.params: self.root.add_param(Parameter(msg=p)) # get parameter set as msg def get_parameter_set(self): params = self.root.get_params() # remove top-level namespace top_len = len(self.root.get_name()) + 2 for p in params: p.set_name(p.get_name()[top_len:]) # generate msg param_set_msg = ParameterSet() param_set_msg.name.data = self.root.get_name() for p in params: param_set_msg.parameters.append(p.to_msg()) return param_set_msg
class MenuRqt(QMenu): ''' This creates a menu to start a several rqt plugins. ''' start_rqt_plugin_signal = Signal(str, str) ''' The start_rqt_plugin_signal is emitted to start a rqt plugin (Name, Plugin). The Plugin can be empty, in this case the RQT itself will be start. ''' def __init__(self, menu_button): QMenu.__init__(self) self.button = menu_button try: rqt_icon_path = roslib.packages.find_resource('rqt_gui', 'rqt.png').pop() menu_button.setText('') menu_button.setIcon(QIcon(rqt_icon_path)) # creates a default config menu self.action_rqt_console = QAction(QIcon.fromTheme('mail-message-new'), "&Console", self, statusTip='"<p>Starts a python GUI plugin for displaying and filtering ' 'ROS log messages that is connected to the selected master.</p>"', triggered=self.on_show_console_clicked) self.addAction(self.action_rqt_console) self.action_rqt_logger_level = QAction(QIcon.fromTheme('format-indent-more'), "&Logger Level", self, statusTip='"<p>Starts a python GUI plugin for configuring the level of ' 'ROS loggers that is connected to the selected master.</p>"', triggered=self.on_show_logger_level_clicked) self.addAction(self.action_rqt_logger_level) self.action_rqt_tf_tree = QAction(QIcon.fromTheme('preferences-system-network'), "&TF Tree", self, statusTip='"<p>Starts a python GUI plugin for visualizing the TF tree' 'that is connected to the selected master.</p>"', triggered=self.on_show_tf_tree_clicked) self.addAction(self.action_rqt_tf_tree) self.action_rqt_ros_graph = QAction(nm.settings().icon('button_graph.png'), "Ros &Graph", self, statusTip='"<p>Starts a python GUI plugin for visualizing the ROS computation graph' 'that is connected to the selected master</p>"', triggered=self.on_show_ros_graph_clicked) self.addAction(self.action_rqt_ros_graph) self.action_rosbag_record = QAction(QIcon.fromTheme('media-record'), "rosbag record", self, statusTip='"<p>Starts the rosbag record with selected topics</p>"', triggered=self.on_start_rosbag_clicked) self.addAction(self.action_rosbag_record) self.action_rqt_rviz = QAction(QIcon.fromTheme('image-x-generic'), "R&Viz", self, statusTip='"<p>Starts RViz</p>"', triggered=self.on_show_rviz_clicked) self.addAction(self.action_rqt_rviz) self.addSeparator() self.action_rqt = QAction(QIcon(rqt_icon_path), "&Rqt GUI", self, statusTip='"<p>Start the rqt GUI' 'that is connected to the selected master</p>"', triggered=self.on_start_rqt_clicked) self.addAction(self.action_rqt) self.action_terminal = QAction(nm.settings().icon('crystal_clear_show_io.png'), "&Terminal", self, statusTip='"<p>Start terminal on selected host</p>"', triggered=self.on_start_terminal) self.action_terminal.setShortcut(QKeySequence("Ctrl+T")) self.addAction(self.action_terminal) menu_button.setMenu(self) except Exception as _e: import traceback print(traceback.format_exc()) menu_button.setEnabled(False) menu_button.setToolTip('rqt_gui not found! Please install rqt to use its plugins!') def on_show_console_clicked(self): self.start_rqt_plugin_signal.emit('Console', 'rqt_console.console.Console') def on_show_logger_level_clicked(self): self.start_rqt_plugin_signal.emit('Logger Level', 'rqt_logger_level.logger_level.LoggerLevel') def on_show_tf_tree_clicked(self): self.start_rqt_plugin_signal.emit('TF Tree', 'rqt_tf_tree.tf_tree.RosTfTree') def on_show_ros_graph_clicked(self): self.start_rqt_plugin_signal.emit('Ros Graph', 'rqt_graph.ros_graph.RosGraph') def on_start_rosbag_clicked(self): self.start_rqt_plugin_signal.emit('rosbag record', '') def on_show_rviz_clicked(self): self.start_rqt_plugin_signal.emit('RViz', 'rqt_rviz/RViz') def on_start_rqt_clicked(self): self.start_rqt_plugin_signal.emit('Rqt GUI', '') def on_start_terminal(self): self.start_rqt_plugin_signal.emit('Terminal', '')