def __init__(self, tabwidget, parent=None): QDockWidget.__init__(self, "Find", parent) self.setObjectName('SearchFrame') self.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self._dockwidget = QFrame(self) self.vbox_layout = QVBoxLayout(self._dockwidget) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(1) # frame with two rows for find and replace find_replace_frame = QFrame(self) find_replace_vbox_layout = QVBoxLayout(find_replace_frame) find_replace_vbox_layout.setContentsMargins(0, 0, 0, 0) find_replace_vbox_layout.setSpacing(1) # find_replace_vbox_layout.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Expanding)) # create frame with find row find_frame = self._create_find_frame() find_replace_vbox_layout.addWidget(find_frame) rplc_frame = self._create_replace_frame() find_replace_vbox_layout.addWidget(rplc_frame) # frame for find&replace and search results self.vbox_layout.addWidget(find_replace_frame) self.vbox_layout.addWidget(self._create_found_frame()) # self.vbox_layout.addStretch(2024) self.setWidget(self._dockwidget) # intern search parameters self._tabwidget = tabwidget self.current_search_text = '' self.search_results = [] self.search_results_fileset = set() self._search_result_index = -1 self._search_recursive = False self._search_thread = None
def keyPressEvent(self, event): ''' Defines some of shortcuts for navigation/management in launch list view or topics view. ''' key_mod = QApplication.keyboardModifiers() if not self.xmlFileView.state() == QAbstractItemView.EditingState: # remove history file from list by pressing DEL if event == QKeySequence.Delete: selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) for item in selected: nm.settings().launch_history_remove(item.path) self.launchlist_model.reloadCurrentPath() elif not key_mod and event.key() == Qt.Key_F4 and self.editXmlButton.isEnabled(): # open selected launch file in xml editor by F4 self.on_edit_xml_clicked() elif event == QKeySequence.Find: # set focus to filter box for packages self.searchPackageLine.setFocus(Qt.ActiveWindowFocusReason) elif event == QKeySequence.Paste: # paste files from clipboard self.launchlist_model.paste_from_clipboard() elif event == QKeySequence.Copy: # copy the selected items as file paths into clipboard selected = self.xmlFileView.selectionModel().selectedIndexes() indexes = [] for s in selected: indexes.append(self.launchlist_proxyModel.mapToSource(s)) self.launchlist_model.copy_to_clipboard(indexes) if self.searchPackageLine.hasFocus() and event.key() == Qt.Key_Escape: # cancel package filtering on pressing ESC self.launchlist_model.show_packages(False) self.searchPackageLine.setText('') self.xmlFileView.setFocus(Qt.ActiveWindowFocusReason) QDockWidget.keyReleaseEvent(self, event)
def __init__(self, tabwidget, parent=None): QDockWidget.__init__(self, "LaunchGraph", parent) graph_ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'GraphDockWidget.ui') loadUi(graph_ui_file, self) self.setObjectName('LaunchGraph') self.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self._info_icon = QIcon(":/icons/info.png") self._tabwidget = tabwidget self._current_path = None self._root_path = None self._current_deep = 0 self.graphTreeView.setSelectionBehavior(QAbstractItemView.SelectRows) model = QStandardItemModel() self.graphTreeView.setModel(model) self.graphTreeView.setUniformRowHeights(True) self.graphTreeView.header().hide() self.htmlDelegate = HTMLDelegate(palette=self.palette()) self.graphTreeView.setItemDelegateForColumn(0, self.htmlDelegate) self.graphTreeView.activated.connect(self.on_activated) self.graphTreeView.clicked.connect(self.on_clicked) self._created_tree = False self.has_none_packages = True self.has_warnings = False self._refill_tree([], False) self._fill_graph_thread = None
def __init__(self, parent=None): ''' Creates the window, connects the signals and init the class. ''' QDockWidget.__init__(self, parent) self._log_info_count = 0 self._log_warn_count = 0 self._log_err_count = 0 self._log_fatal_count = 0 # load the UI file log_dock_file = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'LogDockWidget.ui') loadUi(log_dock_file, self) self.setObjectName("LogWidget") self.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable) # connect to the button signals self.clearCloseButton.clicked.connect(self._on_log_clear_close_clicked) self.closeButton.clicked.connect(self.hide) # initialize the listener to the rosout topic self._rosout_listener = RosoutListener() self._rosout_listener.rosinfo_signal.connect(self._on_roslog_info) self._rosout_listener.roswarn_signal.connect(self._on_roslog_warn) self._rosout_listener.roserr_signal.connect(self._on_roslog_err) self._rosout_listener.rosfatal_signal.connect(self._on_roslog_fatal) self._rosout_listener.registerByROS()
def _create_log_bar(self): self.log_dock = QDockWidget(self) self.log_dock.setObjectName('LogFrame') self.log_dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.log_bar = QWidget(self) self.horizontal_layout_log_bar = QHBoxLayout(self.log_bar) self.horizontal_layout_log_bar.setContentsMargins(2, 0, 2, 0) self.horizontal_layout_log_bar.setObjectName("horizontal_layout_log_bar") # add info label self._log_warning_count = 0 self.log_browser = QTextEdit() self.log_browser.setObjectName("log_browser") self.log_browser.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.log_browser.setLineWrapMode(QTextEdit.NoWrap) # self.log_browser.setMaximumHeight(120) color = QColor(255, 255, 235) bg_style = "QTextEdit#log_browser { background-color: %s;}" % color.name() self.log_bar.setStyleSheet("%s" % (bg_style)) self.horizontal_layout_log_bar.addWidget(self.log_browser) # add hide button self.clear_log_button = QPushButton("clear", self) self.clear_log_button.setObjectName("clear_log_button") self.clear_log_button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) self.clear_log_button.clicked.connect(self.on_clear_log_button_clicked) self.clear_log_button.setFlat(True) self.horizontal_layout_log_bar.addWidget(self.clear_log_button) self.log_dock.setWidget(self.log_bar) return self.log_dock
def __init__(self, main_window, parent=None): ''' Creates the window, connects the signals and init the class. ''' QDockWidget.__init__(self, parent) # load the UI file profile_dock_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ProfileWidget.ui') loadUi(profile_dock_file, self) self._main_window = main_window self.setVisible(False) self._current_profile = dict()
def __init__(self, parent=None): ''' Creates the window, connects the signals and init the class. ''' QDockWidget.__init__(self, parent) self._log_debug_count = 0 self._log_info_count = 0 self._log_warn_count = 0 self._log_err_count = 0 self._log_fatal_count = 0 # load the UI file log_dock_file = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'ui', 'LogDockWidget.ui') loadUi(log_dock_file, self) self.setObjectName("LogWidget") self.closeButton.setIcon( nm.settings().icon('crystal_clear_button_close.png')) self.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable) # connect to the button signals self.clearCloseButton.clicked.connect(self._on_log_clear_close_clicked) self.closeButton.clicked.connect(self.hide) # initialize the listener to the rosout topic self._rosout_listener = RosoutListener() self._rosout_listener.rosdebug_signal.connect(self._on_roslog_debug) self._rosout_listener.rosinfo_signal.connect(self._on_roslog_info) self._rosout_listener.roswarn_signal.connect(self._on_roslog_warn) self._rosout_listener.roserr_signal.connect(self._on_roslog_err) self._rosout_listener.rosfatal_signal.connect(self._on_roslog_fatal) self._rosout_listener.registerByROS() self._enable_info_on_start = False if self._enable_info_on_start: try: service_name = "%s/get_loggers" % rospy.get_name() log_level_srvs = rospy.ServiceProxy(service_name, GetLoggers) resp = log_level_srvs() for logger in resp.loggers: if logger.name == 'rosout': if logger.level == 'DEBUG': self.checkBox_debug.setChecked(True) self.checkBox_info.setChecked(True) elif logger.level == 'INFO': self.checkBox_info.setChecked(True) break except rospy.ServiceException as e: err_msg = "Service call '%s' failed: %s" % (service_name, utf8(e)) rospy.logwarn(err_msg) self.checkBox_debug.stateChanged.connect( self._on_checkbox_debug_state_changed)
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, custom_widgets={'EnhancedLineEdit': EnhancedLineEdit}) 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) self._masteruri2name = {} self._reload_timer = None
def __init__(self, parent=None): ''' Creates the window, connects the signals and init the class. ''' QDockWidget.__init__(self, parent) self.setObjectName("ScreenDock") self.setWindowTitle("Screens") self.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) # | QDockWidget.DockWidgetClosable) self._parent_dock = None self._open_dialogs = [] self.tab_widget = DetachableTabWidget(self) self.tab_widget.currentChanged.connect(self.on_tab_changed) self.tab_widget.detach_signal.connect(self.on_detach) self.setWidget(self.tab_widget)
def closeEvent(self, event): rospy.loginfo("Cancel profile loading...") QDockWidget.closeEvent(self, event) ret = MessageBox.warning(self, "Cancel Start?", 'This stops all starting queues!', buttons=MessageBox.Ok | MessageBox.Cancel) if ret == MessageBox.Cancel: return None self._main_window._progress_queue.stop() self._main_window.launch_dock.progress_queue.stop() for muri, _ in self._current_profile.items(): master = self._main_window.getMaster(muri, False) if master is not None: master.start_nodes_after_load_cfg_clear() master._progress_queue.stop() rospy.loginfo("Profile loading canceled!")
def __init__(self, main_window, parent=None): ''' Creates the window, connects the signals and initialize the class. :param main_window: the node manager main windows object to get all current stuff :type main_window: :class:`fkie_node_manager.main_window.MainWindow` ''' QDockWidget.__init__(self, parent) # load the UI file profile_dock_file = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'ProfileWidget.ui') loadUi(profile_dock_file, self) self._main_window = main_window self.setVisible(False) self._current_profile = dict()
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 __init__(self, parent=None): ''' Creates the window, connects the signals and init the class. ''' QDockWidget.__init__(self, parent) # load the UI file settings_dock_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'SettingsDockWidget.ui') loadUi(settings_dock_file, self) # initialize the settings view model self.settings_model = SettingsModel() self.settings_proxyModel = QSortFilterProxyModel(self) self.settings_proxyModel.setSourceModel(self.settings_model) self.settingsTreeView.setModel(self.settings_proxyModel) self.settingsTreeView.setAlternatingRowColors(True) for i, (_, width) in enumerate(SettingsModel.header): self.settingsTreeView.setColumnWidth(i, width) self.item_delegate = ItemDelegate() self.item_delegate.settings_path_changed_signal.connect(self.reload_settings) self.settingsTreeView.setItemDelegateForColumn(1, self.item_delegate) self.reload_settings()
def __init__(self, tabwidget, parent=None): QDockWidget.__init__(self, "LaunchGraph", parent) graph_ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'GraphDockWidget.ui') loadUi(graph_ui_file, self) self.setObjectName('LaunchGraph') self.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self._tabwidget = tabwidget self._current_path = None self._root_path = None self._current_deep = 0 self.graphTreeView.setSelectionBehavior(QAbstractItemView.SelectRows) model = QStandardItemModel() self.graphTreeView.setModel(model) self.graphTreeView.setUniformRowHeights(True) self.graphTreeView.header().hide() self.htmlDelegate = HTMLDelegate() self.graphTreeView.setItemDelegateForColumn(0, self.htmlDelegate) self.graphTreeView.activated.connect(self.on_activated) self.graphTreeView.clicked.connect(self.on_clicked) self._created_tree = False self._refill_tree([], [], False)
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)
def __init__(self, parent=None): ''' Creates the window, connects the signals and init the class. ''' QDockWidget.__init__(self, parent) self._log_info_count = 0 self._log_warn_count = 0 self._log_err_count = 0 self._log_fatal_count = 0 # load the UI file log_dock_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'LogDockWidget.ui') loadUi(log_dock_file, self) self.hide() # connect to the button signals self.clearCloseButton.clicked.connect(self._on_log_clear_close_clicked) self.closeButton.clicked.connect(self.hide) # initialize the listener to the rosout topic self._rosout_listener = RosoutListener() self._rosout_listener.roswarn_signal.connect(self._on_roslog_warn) self._rosout_listener.roserr_signal.connect(self._on_roslog_err) self._rosout_listener.rosfatal_signal.connect(self._on_roslog_fatal) self._rosout_listener.registerByROS()
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)
class Editor(QMainWindow): ''' Creates a dialog to edit a launch file. ''' finished_signal = Signal(list) ''' finished_signal has as parameter the filenames of the initialization and is emitted, if this dialog was closed. ''' def __init__(self, filenames, search_text='', master_name='', parent=None): ''' :param filenames: a list with filenames. The last one will be activated. :type filenames: [str] :param str search_text: if not empty, searches in new document for first occurrence of the given text ''' QMainWindow.__init__(self, parent) self.setObjectName('Editor - %s' % utf8(filenames)) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags(Qt.Window) self.mIcon = nm.settings().icon('crystal_clear_edit_launch.png') self._error_icon = nm.settings().icon('warning.png') self._info_icon = nm.settings().icon('info.png') self._empty_icon = QIcon() self.setWindowIcon(self.mIcon) window_title = "ROSLaunch Editor" if filenames: window_title = self.__getTabName(filenames[0]) self.setWindowTitle('%s @%s' % (window_title, master_name)) self.init_filenames = filenames self._search_node_count = 0 self._search_thread = None self._last_search_request = None # list with all open files self.files = [] # create tabs for files self.main_widget = QWidget(self) self.main_widget.setObjectName("editorMain") self.verticalLayout = QVBoxLayout(self.main_widget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(1) self.verticalLayout.setObjectName("verticalLayout") self.tabWidget = EditorTabWidget(self) self.tabWidget.setTabPosition(QTabWidget.North) self.tabWidget.setDocumentMode(True) self.tabWidget.setTabsClosable(True) self.tabWidget.setMovable(False) self.tabWidget.setObjectName("tabWidget") self.tabWidget.tabCloseRequested.connect(self.on_close_tab) self.tabWidget.currentChanged.connect(self.on_tab_changed) self.verticalLayout.addWidget(self.tabWidget) self.log_dock = self._create_log_bar() self.addDockWidget(Qt.BottomDockWidgetArea, self.log_dock) # self.verticalLayout.addWidget(self.log_bar) self.buttons = self._create_buttons() self.verticalLayout.addWidget(self.buttons) self.setCentralWidget(self.main_widget) self.find_dialog = TextSearchFrame(self.tabWidget, self) self.find_dialog.found_signal.connect(self.on_search_result) self.find_dialog.replace_signal.connect(self.on_replace) self.addDockWidget(Qt.RightDockWidgetArea, self.find_dialog) self.graph_view = GraphViewWidget(self.tabWidget, self) self.graph_view.load_signal.connect(self.on_graph_load_file) self.graph_view.goto_signal.connect(self.on_graph_goto) self.graph_view.search_signal.connect(self.on_load_request) self.graph_view.finished_signal.connect(self.on_graph_finished) self.graph_view.info_signal.connect(self.on_graph_info) self.addDockWidget(Qt.RightDockWidgetArea, self.graph_view) self.readSettings() self.find_dialog.setVisible(False) self.graph_view.setVisible(False) nm.nmd().file.changed_file.connect(self.on_changed_file) nm.nmd().file.packages_available.connect(self._on_new_packages) # open the files for f in filenames: if f: self.on_load_request(f, search_text, only_launch=True) self.log_dock.setVisible(False) try: pal = self.tabWidget.palette() self._default_color = pal.color(QPalette.Window) color = QColor.fromRgb(nm.settings().host_color(master_name, self._default_color.rgb())) bg_style_launch_dock = "QWidget#editorMain { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %s, stop: 0.7 %s);}" % (color.name(), self._default_color.name()) self.setStyleSheet('%s' % (bg_style_launch_dock)) except Exception as _: pass # import traceback # print(traceback.format_exc()) # def __del__(self): # print "******** destroy", self.objectName() def _create_buttons(self): # create the buttons line self.buttons = QWidget(self) self.horizontalLayout = QHBoxLayout(self.buttons) self.horizontalLayout.setContentsMargins(3, 0, 3, 0) self.horizontalLayout.setObjectName("horizontalLayout") # add open upper launchfile button self.upperButton = QPushButton(self) self.upperButton.setObjectName("upperButton") self.upperButton.clicked.connect(self.on_upperButton_clicked) self.upperButton.setIcon(nm.settings().icon('up.png')) self.upperButton.setShortcut("Ctrl+U") self.upperButton.setToolTip('Open the file which include the current file (Ctrl+U)') self.upperButton.setFlat(True) self.horizontalLayout.addWidget(self.upperButton) # add the goto button self.gotoButton = QPushButton(self) self.gotoButton.setObjectName("gotoButton") self.gotoButton.clicked.connect(self.on_shortcut_goto) self.gotoButton.setText(self._translate("&Goto line")) self.gotoButton.setShortcut("Ctrl+G") self.gotoButton.setToolTip('Open a goto dialog (Ctrl+G)') self.gotoButton.setFlat(True) self.horizontalLayout.addWidget(self.gotoButton) # add a tag button self.tagButton = self._create_tag_button(self) self.horizontalLayout.addWidget(self.tagButton) # add save button self.saveButton = QPushButton(self) self.saveButton.setObjectName("saveButton") self.saveButton.setIcon(QIcon.fromTheme("document-save")) self.saveButton.clicked.connect(self.on_saveButton_clicked) self.saveButton.setText(self._translate("&Save")) self.saveButton.setShortcut("Ctrl+S") self.saveButton.setToolTip('Save the changes to the file (Ctrl+S)') self.saveButton.setFlat(True) self.horizontalLayout.addWidget(self.saveButton) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add line number label self.pos_label = QLabel() self.horizontalLayout.addWidget(self.pos_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add show log button self.show_log_button = QPushButton("Log>>", self) self.show_log_button.setObjectName("show_log_button") self.show_log_button.clicked.connect(self.on_toggled_log) self.show_log_button.setFlat(True) self.show_log_button.setCheckable(True) self.horizontalLayout.addWidget(self.show_log_button) # add graph button self.graphButton = QPushButton(self) self.graphButton.setObjectName("graphButton") self.graphButton.toggled.connect(self.on_toggled_graph) self.graphButton.setText("Includ&e Graph >>") self.graphButton.setCheckable(True) self.graphButton.setShortcut("Ctrl+E") self.graphButton.setToolTip('Shows include and include from files (Ctrl+E)') self.graphButton.setFlat(True) self.horizontalLayout.addWidget(self.graphButton) # add the search button self.searchButton = QPushButton(self) self.searchButton.setObjectName("searchButton") # self.searchButton.clicked.connect(self.on_shortcut_find) self.searchButton.toggled.connect(self.on_toggled_find) self.searchButton.setText(self._translate("&Find >>")) self.searchButton.setToolTip('Open a search dialog (Ctrl+F)') self.searchButton.setFlat(True) self.searchButton.setCheckable(True) self.horizontalLayout.addWidget(self.searchButton) # add the replace button self.replaceButton = QPushButton(self) self.replaceButton.setObjectName("replaceButton") # self.replaceButton.clicked.connect(self.on_shortcut_replace) self.replaceButton.toggled.connect(self.on_toggled_replace) self.replaceButton.setText(self._translate("&Replace >>")) self.replaceButton.setToolTip('Open a search&replace dialog (Ctrl+R)') self.replaceButton.setFlat(True) self.replaceButton.setCheckable(True) self.horizontalLayout.addWidget(self.replaceButton) return self.buttons def _create_log_bar(self): self.log_dock = QDockWidget(self) self.log_dock.setObjectName('LogFrame') self.log_dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.log_bar = QWidget(self) self.horizontal_layout_log_bar = QHBoxLayout(self.log_bar) self.horizontal_layout_log_bar.setContentsMargins(2, 0, 2, 0) self.horizontal_layout_log_bar.setObjectName("horizontal_layout_log_bar") # add info label self._log_warning_count = 0 self.log_browser = QTextEdit() self.log_browser.setObjectName("log_browser") self.log_browser.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.log_browser.setLineWrapMode(QTextEdit.NoWrap) # self.log_browser.setMaximumHeight(120) color = QColor(255, 255, 235) bg_style = "QTextEdit#log_browser { background-color: %s;}" % color.name() self.log_bar.setStyleSheet("%s" % (bg_style)) self.horizontal_layout_log_bar.addWidget(self.log_browser) # add hide button self.clear_log_button = QPushButton("clear", self) self.clear_log_button.setObjectName("clear_log_button") self.clear_log_button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) self.clear_log_button.clicked.connect(self.on_clear_log_button_clicked) self.clear_log_button.setFlat(True) self.horizontal_layout_log_bar.addWidget(self.clear_log_button) self.log_dock.setWidget(self.log_bar) return self.log_dock def keyPressEvent(self, event): ''' Enable the shortcats for search and replace ''' if event.key() == Qt.Key_Escape: self.reject() elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_F: if self.tabWidget.currentWidget().hasFocus(): if not self.searchButton.isChecked(): self.searchButton.setChecked(True) else: self.on_toggled_find(True) else: self.searchButton.setChecked(not self.searchButton.isChecked()) elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_R: if self.tabWidget.currentWidget().hasFocus(): if not self.replaceButton.isChecked(): self.replaceButton.setChecked(True) else: self.on_toggled_replace(True) else: self.replaceButton.setChecked(not self.replaceButton.isChecked()) elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_E: if self.tabWidget.currentWidget().hasFocus(): if not self.graphButton.isChecked(): self.graphButton.setChecked(True) else: self.on_toggled_graph(True) else: self.graphButton.setChecked(not self.graphButton.isChecked()) elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_W: self.on_close_tab(self.tabWidget.currentIndex()) elif event.modifiers() in [Qt.ControlModifier, Qt.AltModifier] and event.key() == Qt.Key_Up: self.on_upperButton_clicked() elif event.modifiers() in [Qt.ControlModifier, Qt.AltModifier] and event.key() == Qt.Key_Down: self.on_downButton_clicked() else: event.accept() QMainWindow.keyPressEvent(self, event) def _translate(self, text): if hasattr(QApplication, "UnicodeUTF8"): return QApplication.translate("Editor", text, None, QApplication.UnicodeUTF8) else: return QApplication.translate("Editor", text, None) def readSettings(self): if nm.settings().store_geometry: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup("editor") maximized = settings.value("maximized", 'false') == 'true' if maximized: self.showMaximized() else: self.resize(settings.value("size", QSize(800, 640))) self.move(settings.value("pos", QPoint(0, 0))) try: self.restoreState(settings.value("window_state")) except Exception: import traceback print(traceback.format_exc()) settings.endGroup() def storeSetting(self): if nm.settings().store_geometry: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup("editor") settings.setValue("size", self.size()) settings.setValue("pos", self.pos()) settings.setValue("maximized", self.isMaximized()) settings.setValue("window_state", self.saveState()) settings.endGroup() def on_load_request(self, filename, search_text='', insert_index=-1, goto_line=-1, only_launch=False, count_results=0): ''' Loads a file in a new tab or focus the tab, if the file is already open. :param str filename: the path to file :param str search_text: if not empty, searches in new document for first occurrence of the given text ''' if not filename: return self.tabWidget.setUpdatesEnabled(False) try: if filename not in self.files: tab_name = self.__getTabName(filename) editor = TextEdit(filename, parent=self) linenumber_editor = LineNumberWidget(editor) tab_index = 0 if insert_index > -1: tab_index = self.tabWidget.insertTab(insert_index, linenumber_editor, tab_name) else: tab_index = self.tabWidget.addTab(linenumber_editor, tab_name) self.files.append(filename) editor.setCurrentPath(os.path.basename(filename)) editor.load_request_signal.connect(self.on_load_request) editor.document().modificationChanged.connect(self.on_editor_modificationChanged) editor.cursorPositionChanged.connect(self.on_editor_positionChanged) editor.setFocus(Qt.OtherFocusReason) # editor.textChanged.connect(self.on_text_changed) editor.undoAvailable.connect(self.on_text_changed) self.tabWidget.setCurrentIndex(tab_index) # self.find_dialog.set_search_path(filename) else: for i in range(self.tabWidget.count()): if self.tabWidget.widget(i).filename == filename: self.tabWidget.setCurrentIndex(i) break self.tabWidget.setUpdatesEnabled(True) if search_text: if only_launch: self.find_dialog.found_files_list.clear() try: self._search_thread.stop() self._search_thread = None except Exception: pass # TODO: put all text of all tabs into path_text rospy.logdebug("serach for '%s'" % search_text) self._search_node_count = 0 self._search_thread = TextSearchThread(search_text, filename, recursive=True, only_launch=only_launch, count_results=count_results) self._search_thread.search_result_signal.connect(self.on_search_result_on_open) self._search_thread.warning_signal.connect(self.on_search_result_warning) self._last_search_request = (filename, search_text, insert_index, goto_line, only_launch) if not self.graph_view.is_loading(): self.on_graph_info("search thread: start search for '%s'" % self._search_thread._search_text) self._search_thread.start() if goto_line != -1: self._goto(goto_line, True) self.upperButton.setEnabled(self.tabWidget.count() > 1) except Exception as err: self.tabWidget.setUpdatesEnabled(True) import traceback msg = "Error while open %s: %s" % (filename, traceback.format_exc()) rospy.logwarn(msg) MessageBox.critical(self, "Error", utf8(err), msg) if self.tabWidget.count() == 0: self.close() def on_graph_load_file(self, path, insert_after=True): insert_index = self.tabWidget.currentIndex() + 1 if not insert_after and insert_index > 1: insert_index = self.tabWidget.currentIndex() self.on_load_request(path, insert_index=insert_index) def on_graph_goto(self, path, linenr): if path == self.tabWidget.currentWidget().filename: if linenr != -1: self._goto(linenr, True) def on_graph_finished(self): self.on_graph_info("build tree: finished", False) if self.graph_view.has_warnings: self.graphButton.setIcon(self._info_icon) else: self.graphButton.setIcon(self._empty_icon) if self._search_thread: try: self._search_thread.find_args_not_set = True self._search_thread.start() self.on_graph_info("search thread: start search for '%s'" % self._search_thread._search_text) except Exception: pass def on_graph_info(self, msg, warning=False): text_color = "#000000" if warning: self._log_warning_count += 1 if self._log_warning_count == 1: self.show_log_button.setIcon(self._error_icon) text_color = "#FE9A2E" text = ('<pre style="padding:10px;"><dt><font color="%s">' '%s</font></dt></pre>' % (text_color, msg)) self.log_browser.append(text) def on_text_changed(self, value=""): if self.tabWidget.currentWidget().hasFocus(): self.find_dialog.file_changed(self.tabWidget.currentWidget().filename) self._last_search_request = None def on_tab_changed(self, index): if index > -1: self.graph_view.set_file(self.tabWidget.widget(index).filename, self.tabWidget.widget(0).filename) self._last_search_request = None def on_close_tab(self, tab_index): ''' Signal handling to close single tabs. :param int tab_index: tab index to close ''' try: doremove = True w = self.tabWidget.widget(tab_index) if w.document().isModified(): name = self.__getTabName(w.filename) result = MessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", name])) if result == MessageBox.Yes: self.tabWidget.currentWidget().save() elif result == MessageBox.No: pass elif rospy.is_shutdown(): doremove = False if doremove: # remove the indexed files if w.filename in self.files: self.files.remove(w.filename) # close tab self.tabWidget.removeTab(tab_index) # close editor, if no tabs are open if not self.tabWidget.count(): self.close() self._last_search_request = None except Exception: import traceback rospy.logwarn("Error while close tab %s: %s", str(tab_index), traceback.format_exc(1)) self.upperButton.setEnabled(self.tabWidget.count() > 1) def reject(self): if self.find_dialog.isVisible(): self.searchButton.setChecked(not self.searchButton.isChecked()) else: self.close() def on_changed_file(self, grpc_path, mtime): if grpc_path in self.files: for i in range(self.tabWidget.count()): if self.tabWidget.widget(i).filename == grpc_path: self.tabWidget.widget(i).file_changed(mtime) break if self._last_search_request is not None: self.on_load_request(*self._last_search_request) def closeEvent(self, event): ''' Test the open files for changes and save this if needed. ''' changed = [] # get the names of all changed files for i in range(self.tabWidget.count()): w = self.tabWidget.widget(i) if w.document().isModified(): changed.append(self.__getTabName(w.filename)) if changed: # ask the user for save changes if self.isHidden(): buttons = MessageBox.Yes | MessageBox.No else: buttons = MessageBox.Yes | MessageBox.No | MessageBox.Cancel result = MessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", '\n'.join(changed)]), buttons=buttons) if result == MessageBox.Yes: for i in range(self.tabWidget.count()): w = self.tabWidget.widget(i).save() self.graph_view.clear_cache() event.accept() elif result == MessageBox.No: event.accept() elif rospy.is_shutdown(): event.ignore() else: event.accept() if event.isAccepted(): self.storeSetting() nm.nmd().file.changed_file.disconnect(self.on_changed_file) nm.nmd().file.packages_available.connect(self._on_new_packages) self.finished_signal.emit(self.init_filenames) def on_editor_modificationChanged(self, value=None): ''' If the content was changed, a '*' will be shown in the tab name. ''' tab_name = self.__getTabName(self.tabWidget.currentWidget().filename) if (self.tabWidget.currentWidget().document().isModified()): tab_name = '*%s' % tab_name self.tabWidget.setTabText(self.tabWidget.currentIndex(), tab_name) def on_editor_positionChanged(self): ''' Shows the number of the line and column in a label. ''' cursor = self.tabWidget.currentWidget().textCursor() self.pos_label.setText(':%s:%s #%s' % (cursor.blockNumber() + 1, cursor.columnNumber(), cursor.position())) def __getTabName(self, lfile): base = os.path.basename(lfile).replace('.launch', '') (package, _) = package_name(os.path.dirname(lfile)) return '%s [%s]' % (base, package) def _on_new_packages(self, url): try: if nmdurl.nmduri_from_path(url) == nmdurl.nmduri_from_path(self.tabWidget.currentWidget().filename): rospy.logdebug("packages updated, rebuild graph") if self.graph_view.has_none_packages: self.graph_view.clear_cache() except Exception: import traceback print(traceback.format_exc()) ############################################################################## # HANDLER for buttons ############################################################################## def on_clear_log_button_clicked(self): self._log_warning_count = 0 self.show_log_button.setIcon(self._empty_icon) self.log_browser.clear() self.log_dock.setVisible(False) self.show_log_button.setChecked(False) self.tabWidget.currentWidget().setFocus() def on_upperButton_clicked(self): ''' Opens the file which include the current open file ''' if self.tabWidget.currentIndex() != 0: self.graph_view.find_parent_file() def on_downButton_clicked(self): ''' Select editor right from current. ''' if self.tabWidget.currentIndex() < self.tabWidget.count(): self.tabWidget.setCurrentIndex(self.tabWidget.currentIndex() + 1) def on_saveButton_clicked(self): ''' Saves the current document. This method is called if the C{save button} was clicked. ''' saved, errors, msg = self.tabWidget.currentWidget().save() if errors: if msg: rospy.logwarn(msg) MessageBox.critical(self, "Error", "Error while save file: %s" % os.path.basename(self.tabWidget.currentWidget().filename), detailed_text=msg) self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._error_icon) self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), msg) self.on_graph_info("saved failed %s: %s" % (self.tabWidget.currentWidget().filename, msg), True) elif saved: self.on_graph_info("saved %s" % self.tabWidget.currentWidget().filename) self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._empty_icon) self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), '') self.graph_view.clear_cache() def on_shortcut_find(self): pass def on_toggled_log(self, value): ''' Shows the log bar ''' if value: self.log_dock.setVisible(True) else: self.log_dock.setVisible(False) self.tabWidget.currentWidget().setFocus() def on_toggled_graph(self, value): ''' Shows the search frame ''' if value: self.graph_view.enable() else: # self.replaceButton.setChecked(False) self.graph_view.setVisible(False) self.tabWidget.currentWidget().setFocus() def on_toggled_find(self, value, only_results=False): ''' Shows the search frame ''' if value: self.find_dialog.enable() self.find_dialog.find_frame.setVisible(not only_results) self.find_dialog.recursive_search_box.setVisible(not only_results) if not only_results: # clear results if not search text exists and we show not only search results if not self.find_dialog.search_field.text(): self.find_dialog.found_files_list.clear() else: self.replaceButton.setChecked(False) self.find_dialog.setVisible(False) self.tabWidget.currentWidget().setFocus() def on_toggled_replace(self, value): ''' Shows the replace lineedit in the search frame ''' if value: self.searchButton.setChecked(True) self.find_dialog.set_replace_visible(value) def on_shortcut_goto(self): ''' Opens a C{goto} dialog. ''' value = 1 ok = False try: value, ok = QInputDialog.getInt(self, "Goto", self.tr("Line number:"), QLineEdit.Normal, minValue=1, step=1) except Exception: value, ok = QInputDialog.getInt(self, "Goto", self.tr("Line number:"), QLineEdit.Normal, min=1, step=1) if ok: self._goto(value) self.tabWidget.currentWidget().setFocus(Qt.ActiveWindowFocusReason) def _goto(self, linenr, select_line=True): if linenr > self.tabWidget.currentWidget().document().blockCount(): linenr = self.tabWidget.currentWidget().document().blockCount() curpos = self.tabWidget.currentWidget().textCursor().blockNumber() + 1 while curpos != linenr: mov = QTextCursor.NextBlock if curpos < linenr else QTextCursor.PreviousBlock self.tabWidget.currentWidget().moveCursor(mov) curpos = self.tabWidget.currentWidget().textCursor().blockNumber() + 1 self.tabWidget.currentWidget().moveCursor(QTextCursor.EndOfBlock) self.tabWidget.currentWidget().moveCursor(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) ############################################################################## # SLOTS for search dialog ############################################################################## def on_search_result(self, search_text, found, path, startpos, endpos, linenr=-1, line_text=''): ''' A slot to handle a found text. It goes to the position in the text and select the searched text. On new file it will be open. :param search_text: the searched text :type search_text: str :param found: the text was found or not :type found: bool :param path: the path of the file the text was found :type path: str :param startpos: the position in the text :type startpos: int :param endpos: the end position in the text :type endpos: int ''' if found: if self.tabWidget.currentWidget().filename != path: focus_widget = QApplication.focusWidget() self.on_load_request(path) focus_widget.setFocus() self.tabWidget.currentWidget().select(startpos, endpos, False) def on_search_result_on_open(self, search_text, found, path, startpos, endpos, linenr, line_text): ''' Like on_search_result, but skips the text in comments. ''' if found: self._search_node_count += 1 if self._search_node_count > 1: self.on_toggled_find(True, only_results=True) self.find_dialog.current_search_text = search_text self.find_dialog.on_search_result(search_text, found, path, startpos, endpos, linenr, line_text) self.on_graph_info("search thread: found %s in '%s:%d'" % (search_text, path, linenr)) if self.tabWidget.currentWidget().filename != path: focus_widget = QApplication.focusWidget() self.on_load_request(path) if focus_widget is not None: focus_widget.setFocus() self.tabWidget.currentWidget().select(startpos, endpos, True) # self.on_search_result(search_text, found, path, startpos, endpos, linenr, line_text) def on_search_result_warning(self, msg): self.on_graph_info("search thread: %s" % (msg), True) def on_replace(self, search_text, path, index, replaced_text): ''' A slot to handle a text replacement of the TextSearchFrame. :param search_text: the searched text :type search_text: str :param path: the path of the file the text was found :type path: str :param index: the position in the text :type index: int :param replaced_text: the new text :type replaced_text: str ''' cursor = self.tabWidget.currentWidget().textCursor() if cursor.selectedText() == search_text: cursor.insertText(replaced_text) ############################################################################## # LAUNCH TAG insertion ############################################################################## def _show_custom_parameter_dialog(self): methods = {'nm/associations': self._on_add_cp_associations, 'capability_group': self._on_add_cp_capability_group, 'nm/kill_on_stop': self._on_add_cp_kill_on_stop, 'autostart/delay': self._on_add_cp_as_delay, 'autostart/exclude': self._on_add_cp_as_exclude, 'autostart/required_publisher': self._on_add_cp_as_req_publisher, 'respawn/max': self._on_add_cp_r_max, 'respawn/min_runtime': self._on_add_cp_r_min_runtime, 'respawn/delay': self._on_add_cp_r_delay, } res = SelectDialog.getValue('Insert custom parameter', "Select parameter to insert:", sorted(methods.keys()), exclusive=True, parent=self, select_if_single=False, store_geometry='insert_param') tags2insert = res[0] for tag in tags2insert: methods[tag]() def _create_tag_button(self, parent=None): btn = QPushButton(parent) btn.setObjectName("tagButton") btn.setText(self._translate("Add &tag")) # btn.setShortcut("Ctrl+T") btn.setToolTip('Adds a ROS launch tag to launch file') btn.setMenu(self._create_tag_menu(btn)) btn.setFlat(True) return btn 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 _insert_text(self, text, cursor_pose=None, selection_len=None): if self.tabWidget.currentWidget().isReadOnly(): return cursor = self.tabWidget.currentWidget().textCursor() if not cursor.isNull(): cursor.beginEditBlock() col = cursor.columnNumber() spaces = ''.join([' ' for _ in range(col)]) curr_cursor_pos = cursor.position() cursor.insertText(text.replace('\n', '\n%s' % spaces)) if cursor_pose is not None: cursor.setPosition(curr_cursor_pos + cursor_pose, QTextCursor.MoveAnchor) if selection_len is not None: cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, selection_len) cursor.endEditBlock() self.tabWidget.currentWidget().setTextCursor(cursor) self.tabWidget.currentWidget().setFocus(Qt.OtherFocusReason) def _on_add_group_tag(self): self._insert_text('<group ns="namespace" clear_params="true|false">\n' '</group>', 11, 9) def _get_package_dialog(self): muri = masteruri_from_ros() if self.init_filenames: muri = nmdurl.masteruri(self.init_filenames[0]) return PackageDialog(muri) def _on_add_node_tag(self): dia = self._get_package_dialog() if dia.exec_(): self._insert_text('<node name="%s" pkg="%s" type="%s">\n' '</node>' % (dia.binary, dia.package, dia.binary)) def _on_add_node_tag_all(self): dia = self._get_package_dialog() if dia.exec_(): self._insert_text('<node name="%s" pkg="%s" type="%s"\n' ' args="arg1" machine="machine_name"\n' ' respawn="true" required="true"\n' ' ns="foo" clear_params="true|false"\n' ' output="log|screen" cwd="ROS_HOME|node"\n' ' launch-prefix="prefix arguments">\n' '</node>' % (dia.binary, dia.package, dia.binary)) def _on_add_include_tag_all(self): self._insert_text('<include file="$(find pkg-name)/path/filename.xml"\n' ' ns="foo" clear_params="true|false">\n' '</include>', 22, 27) def _on_add_remap_tag(self): self._insert_text('<remap from="original" to="new"/>', 13, 8) def _on_add_env_tag(self): self._insert_text('<env name="variable" value="value"/>', 11, 8) def _on_add_param_clipboard_tag(self): lines = QApplication.clipboard().mimeData().text().splitlines() name = "" if len(lines) == 1: name = lines[0] self._insert_text('<param name="%s" value="value" />' % name, 22 + len(name), 5) def _on_add_param_tag(self): self._insert_text('<param name="name" value="value" />', 13, 4) def _on_add_param_tag_all(self): self._insert_text('<param name="name" value="value"\n' ' type="str|int|double|bool"\n' ' textfile="$(find pkg-name)/path/file.txt"\n' ' binfile="$(find pkg-name)/path/file"\n' ' command="$(find pkg-name)/exe \'$(find pkg-name)/arg.txt\'">\n' '</param>', 13, 4) def _on_add_rosparam_tag_all(self): self._insert_text('<rosparam param="name"\n' ' file="$(find pkg-name)/path/foo.yaml"\n' ' command="load|dump|delete"\n' ' ns="namespace"\n' ' subst_value="true|false">\n' '</rosparam>', 17, 4) def _on_add_arg_tag_default(self): self._insert_text('<arg name="foo" default="1" />', 11, 3) def _on_add_arg_tag_value(self): self._insert_text('<arg name="foo" value="bar" />', 11, 3) def _on_add_test_tag(self): dia = self._get_package_dialog() if dia.exec_(): self._insert_text('<test name="%s" pkg="%s" type="%s" test-name="test_%s">\n' '</test>' % (dia.binary, dia.package, dia.binary, dia.binary)) def _on_add_test_tag_all(self): dia = self._get_package_dialog() if dia.exec_(): self._insert_text('<test name="%s" pkg="%s" type="%s" test-name="test_%s">\n' ' args="arg1" time-limit="60.0"\n' ' ns="foo" clear_params="true|false"\n' ' cwd="ROS_HOME|node" retry="0"\n' ' launch-prefix="prefix arguments">\n' '</test>' % (dia.binary, dia.package, dia.binary, dia.binary)) def _on_add_cp_capability_group(self): self._insert_text('<param name="capability_group" value="demo" />', 38, 4) def _on_add_cp_kill_on_stop(self): self._insert_text('<param name="nm/kill_on_stop" value="100" hint="[ms]" />', 34, 3) def _on_add_cp_associations(self): self._insert_text('<param name="nm/associations" value="node1,node2" hint="list of nodes" />', 34, 11) def _on_add_cp_as_delay(self): self._insert_text('<param name="autostart/delay" value="1" hint="[seconds]" />', 37, 1) def _on_add_cp_as_exclude(self): self._insert_text('<param name="autostart/exclude" value="True" />', 39, 4) def _on_add_cp_as_req_publisher(self): self._insert_text('<param name="autostart/required/publisher" value="topic" />', 50, 5) def _on_add_cp_r_max(self): self._insert_text('<param name="respawn/max" value="10" />', 33, 2) def _on_add_cp_r_min_runtime(self): self._insert_text('<param name="respawn/min_runtime" value="10" hint="[seconds]" />', 41, 2) def _on_add_cp_r_delay(self): self._insert_text('<param name="respawn/delay" value="5" hint="[seconds]" />', 31, 2)
def closeEvent(self, event): # close tabs on hide self.tab_widget.clear() self.closed_signal.emit(self) QDockWidget.closeEvent(self, event)
def keyPressEvent(self, event): ''' Defines some of shortcuts for navigation/management in launch list view or topics view. ''' key_mod = QApplication.keyboardModifiers() if not self.ui_file_view.state() == QAbstractItemView.EditingState: # remove history file from list by pressing DEL if event == QKeySequence.Delete or (event.key() == Qt.Key_Delete and key_mod & Qt.ShiftModifier): selected = self._pathItemsFromIndexes( self.ui_file_view.selectionModel().selectedIndexes(), False) for item in selected: if item in nm.settings().launch_history: nm.settings().launch_history_remove(item.path) self.launchlist_model.reload_current_path() elif not self.launchlist_model.is_in_root: if key_mod & Qt.ShiftModifier: rem_uri, rem_path = nmdurl.split(item.path) host = rem_uri.split(':') result = MessageBox.question( self, "Delete Question", "Delete %s\n@ %s" % (rem_path, host[0]), buttons=MessageBox.No | MessageBox.Yes) if result == MessageBox.Yes: try: nm.nmd().file.delete(item.path) self.launchlist_model.reload_current_path( clear_cache=True) except Exception as e: rospy.logwarn("Error while delete %s: %s" % (item.path, utf8(e))) MessageBox.warning( self, "Delete error", 'Error while delete:\n%s' % item.name, "%s" % utf8(e)) else: MessageBox.information( self, "Delete Info", "Use Shift+Del to delete files or directories", buttons=MessageBox.Ok) elif not key_mod and event.key( ) == Qt.Key_F4 and self.ui_button_edit.isEnabled(): # open selected launch file in xml editor by F4 self.on_edit_xml_clicked() elif event == QKeySequence.Find: # set focus to filter box for packages self.ui_search_line.setFocus(Qt.ActiveWindowFocusReason) elif event == QKeySequence.Paste: # paste files from clipboard self.launchlist_model.paste_from_clipboard() elif event == QKeySequence.Copy: # copy the selected items as file paths into clipboard selected = self.ui_file_view.selectionModel().selectedIndexes() indexes = [] for s in selected: indexes.append(self.launchlist_proxy_model.mapToSource(s)) self.launchlist_model.copy_to_clipboard(indexes) if self.ui_search_line.hasFocus() and event.key() == Qt.Key_Escape: # cancel package filtering on pressing ESC self.launchlist_model.reload_current_path() self.ui_search_line.setText('') self.ui_file_view.setFocus(Qt.ActiveWindowFocusReason) QDockWidget.keyReleaseEvent(self, event)
def _update_title_label(self): if self.title_edit.text(): self.title_label.setText(self.title_edit.text()) self._dock_widget.setWindowTitle(self.title_edit.text()) if __name__ == '__main__': import sys from python_qt_binding.QtGui import QApplication from .dockable_main_window import DockableMainWindow app = QApplication(sys.argv) win = DockableMainWindow() dock1 = QDockWidget('dockwidget1', win) win.addDockWidget(Qt.LeftDockWidgetArea, dock1) title_bar = DockWidgetTitleBar(dock1) dock1.setTitleBarWidget(title_bar) dock2 = QDockWidget('dockwidget2') win.addDockWidget(Qt.RightDockWidgetArea, dock2) title_bar = DockWidgetTitleBar(dock2) dock2.setTitleBarWidget(title_bar) win.resize(640, 480) win.show() app.exec_()
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.hostLabel.setVisible(False) 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 self._first_path = self.launchlist_model.current_path