Package node_manager_fkie :: Module launch_files_widget
[frames] | no frames]

Source Code for Module node_manager_fkie.launch_files_widget

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Fraunhofer nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32  from python_qt_binding import loadUi 
 33  from python_qt_binding.QtCore import QRegExp, Qt, Signal 
 34  try: 
 35      from python_qt_binding.QtGui import QSortFilterProxyModel 
 36  except: 
 37      from python_qt_binding.QtCore import QSortFilterProxyModel 
 38  try: 
 39      from python_qt_binding.QtGui import QAbstractItemView, QAction, QApplication, QDockWidget, QFileDialog, QLineEdit, QMenu, QMessageBox 
 40  except: 
 41      from python_qt_binding.QtWidgets import QAbstractItemView, QAction, QApplication, QDockWidget, QFileDialog, QLineEdit, QMenu, QMessageBox 
 42  from python_qt_binding.QtGui import QKeySequence 
 43  import os 
 44   
 45  import rospy 
 46   
 47  from .common import package_name  # , masteruri_from_ros 
 48  from .detailed_msg_box import WarningMessageBox 
 49  from .launch_list_model import LaunchListModel, LaunchItem 
 50  from .parameter_dialog import ParameterDialog 
 51  from .progress_queue import ProgressQueue  # , ProgressThread 
 52   
 53   
54 -class LaunchFilesWidget(QDockWidget):
55 ''' 56 Launch file browser. 57 ''' 58 59 load_signal = Signal(str) 60 ''' load the launch file ''' 61 load_as_default_signal = Signal(str, str) 62 ''' load the launch file as default (path, host) ''' 63 edit_signal = Signal(list) 64 ''' list of paths to open in an editor ''' 65 transfer_signal = Signal(list) 66 ''' list of paths selected for transfer ''' 67
68 - def __init__(self, parent=None):
69 ''' 70 Creates the window, connects the signals and init the class. 71 ''' 72 QDockWidget.__init__(self, parent) 73 # initialize parameter 74 self.__current_path = os.path.expanduser('~') 75 # load the UI file 76 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'LaunchFilesDockWidget.ui') 77 loadUi(ui_file, self) 78 # initialize the view for the launch files 79 self.launchlist_model = LaunchListModel() 80 self.launchlist_proxyModel = QSortFilterProxyModel(self) 81 self.launchlist_proxyModel.setSourceModel(self.launchlist_model) 82 self.xmlFileView.setModel(self.launchlist_proxyModel) 83 self.xmlFileView.setAlternatingRowColors(True) 84 self.xmlFileView.activated.connect(self.on_launch_selection_activated) 85 self.xmlFileView.setDragDropMode(QAbstractItemView.DragOnly) 86 self.xmlFileView.setDragEnabled(True) 87 sm = self.xmlFileView.selectionModel() 88 sm.selectionChanged.connect(self.on_xmlFileView_selection_changed) 89 # self.searchPackageLine.setVisible(False) 90 self.searchPackageLine.textChanged.connect(self.set_package_filter) 91 self.searchPackageLine.focusInEvent = self._searchline_focusInEvent 92 # connect to the button signals 93 self.refreshXmlButton.clicked.connect(self.on_refresh_xml_clicked) 94 self.editXmlButton.clicked.connect(self.on_edit_xml_clicked) 95 self.newXmlButton.clicked.connect(self.on_new_xml_clicked) 96 self.openXmlButton.clicked.connect(self.on_open_xml_clicked) 97 self.transferButton.clicked.connect(self.on_transfer_file_clicked) 98 self.loadXmlButton.clicked.connect(self.on_load_xml_clicked) 99 self.loadXmlAsDefaultButton.clicked.connect(self.on_load_as_default) 100 # creates a default config menu 101 start_menu = QMenu(self) 102 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) 103 start_menu.addAction(self.loadDeafaultAtHostAct) 104 self.loadXmlAsDefaultButton.setMenu(start_menu) 105 # initialize the progress queue 106 self.progress_queue = ProgressQueue(self.progressFrame_cfg, self.progressBar_cfg, self.progressCancelButton_cfg)
107
108 - def stop(self):
109 ''' 110 Cancel the executing queued actions. This method must be 111 called at the exit! 112 ''' 113 self.progress_queue.stop()
114
115 - def on_launch_selection_activated(self, activated):
116 ''' 117 Tries to load the launch file, if one was activated. 118 ''' 119 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 120 for item in selected: 121 try: 122 lfile = self.launchlist_model.expandItem(item.name, item.path, item.id) 123 self.searchPackageLine.setText('') 124 if lfile is not None: 125 if item.isLaunchFile(): 126 self.launchlist_model.add2LoadHistory(item.path) 127 key_mod = QApplication.keyboardModifiers() 128 if key_mod & Qt.ShiftModifier: 129 self.load_as_default_signal.emit(item.path, None) 130 elif key_mod & Qt.ControlModifier: 131 self.launchlist_model.setPath(os.path.dirname(item.path)) 132 else: 133 self.load_signal.emit(item.path) 134 elif item.isConfigFile(): 135 self.edit_signal.emit([lfile]) 136 except Exception as e: 137 rospy.logwarn("Error while load launch file %s: %s" % (item, e)) 138 WarningMessageBox(QMessageBox.Warning, "Load error", 139 'Error while load launch file:\n%s' % item.name, 140 "%s" % e).exec_()
141 # self.launchlist_model.reloadCurrentPath() 142
143 - def on_xmlFileView_selection_changed(self, selected, deselected):
144 ''' 145 On selection of a launch file, the buttons are enabled otherwise disabled. 146 ''' 147 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 148 for item in selected: 149 islaunch = item.isLaunchFile() 150 isconfig = item.isConfigFile() 151 self.editXmlButton.setEnabled(islaunch or isconfig) 152 self.loadXmlButton.setEnabled(islaunch) 153 self.transferButton.setEnabled(islaunch or isconfig) 154 self.loadXmlAsDefaultButton.setEnabled(islaunch)
155
156 - def set_package_filter(self, text):
157 self.launchlist_proxyModel.setFilterRegExp(QRegExp(text, 158 Qt.CaseInsensitive, 159 QRegExp.Wildcard))
160
161 - def on_refresh_xml_clicked(self):
162 ''' 163 Reload the current path. 164 ''' 165 self.launchlist_model.reloadCurrentPath() 166 self.launchlist_model.reloadPackages() 167 self.editXmlButton.setEnabled(False) 168 self.loadXmlButton.setEnabled(False) 169 self.transferButton.setEnabled(False) 170 self.loadXmlAsDefaultButton.setEnabled(False)
171
172 - def on_edit_xml_clicked(self):
173 ''' 174 Opens an XML editor to edit the launch file. 175 ''' 176 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 177 for item in selected: 178 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 179 if path is not None: 180 self.edit_signal.emit([path])
181
182 - def on_new_xml_clicked(self):
183 ''' 184 Creates a new launch file. 185 ''' 186 # get new file from open dialog, use last path if one exists 187 open_path = self.__current_path 188 if self.launchlist_model.currentPath is not None: 189 open_path = self.launchlist_model.currentPath 190 (fileName, _) = QFileDialog.getSaveFileName(self, 191 "New launch file", 192 open_path, 193 "Config files (*.launch *.yaml);;All files (*)") 194 if fileName: 195 try: 196 (pkg, _) = package_name(os.path.dirname(fileName)) # _:=pkg_path 197 if pkg is None: 198 WarningMessageBox(QMessageBox.Warning, "New File Error", 199 'The new file is not in a ROS package').exec_() 200 return 201 with open(fileName, 'w+') as f: 202 f.write("<launch>\n" 203 " <arg name=\"robot_ns\" default=\"my_robot\"/>\n" 204 " <group ns=\"$(arg robot_ns)\">\n" 205 " <param name=\"tf_prefix\" value=\"$(arg robot_ns)\"/>\n" 206 "\n" 207 " <node pkg=\"my_pkg\" type=\"my_node\" name=\"my_name\" >\n" 208 " <param name=\"capability_group\" value=\"MY_GROUP\"/>\n" 209 " </node>\n" 210 " </group>\n" 211 "</launch>\n" 212 ) 213 self.__current_path = os.path.dirname(fileName) 214 self.launchlist_model.setPath(self.__current_path) 215 self.edit_signal.emit([fileName]) 216 except EnvironmentError as e: 217 WarningMessageBox(QMessageBox.Warning, "New File Error", 218 'Error while create a new file', 219 '%s' % e).exec_()
220
221 - def on_open_xml_clicked(self):
222 (fileName, _) = QFileDialog.getOpenFileName(self, 223 "Load launch file", 224 self.__current_path, 225 "Config files (*.launch);;All files (*)") 226 if fileName: 227 self.__current_path = os.path.dirname(fileName) 228 self.launchlist_model.add2LoadHistory(fileName) 229 self.load_signal.emit(fileName)
230
231 - def on_transfer_file_clicked(self):
232 ''' 233 Emit the signal to copy the selected file to a remote host. 234 ''' 235 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 236 paths = list() 237 for item in selected: 238 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 239 if path is not None: 240 paths.append(path) 241 if paths: 242 self.transfer_signal.emit(paths)
243
244 - def on_load_xml_clicked(self):
245 ''' 246 Tries to load the selected launch file. The button is only enabled and this 247 method is called, if the button was enabled by on_launch_selection_clicked() 248 ''' 249 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 250 for item in selected: 251 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 252 if path is not None: 253 self.launchlist_model.add2LoadHistory(path) 254 self.load_signal.emit(path)
255
257 ''' 258 Tries to load the selected launch file as default configuration. The button 259 is only enabled and this method is called, if the button was enabled by 260 on_launch_selection_clicked() 261 ''' 262 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 263 for item in selected: 264 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 265 if path is not None: 266 params = {'Host': ('string', 'localhost')} 267 dia = ParameterDialog(params) 268 dia.setFilterVisible(False) 269 dia.setWindowTitle('Start node on...') 270 dia.resize(350, 120) 271 dia.setFocusField('Host') 272 if dia.exec_(): 273 try: 274 params = dia.getKeywords() 275 host = params['Host'] 276 rospy.loginfo("LOAD the launch file on host %s as default: %s" % (host, path)) 277 self.launchlist_model.add2LoadHistory(path) 278 self.load_as_default_signal.emit(path, host) 279 except Exception, e: 280 WarningMessageBox(QMessageBox.Warning, "Load default config error", 281 'Error while parse parameter', 282 '%s' % e).exec_()
283
284 - def on_load_as_default(self):
285 ''' 286 Tries to load the selected launch file as default configuration. The button 287 is only enabled and this method is called, if the button was enabled by 288 on_launch_selection_clicked() 289 ''' 290 key_mod = QApplication.keyboardModifiers() 291 if (key_mod & Qt.ShiftModifier): 292 self.loadXmlAsDefaultButton.showMenu() 293 else: 294 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 295 for item in selected: 296 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 297 if path is not None: 298 rospy.loginfo("LOAD the launch file as default: %s", path) 299 self.launchlist_model.add2LoadHistory(path) 300 self.load_as_default_signal.emit(path, None)
301
302 - def _launchItemsFromIndexes(self, indexes, recursive=True):
303 result = [] 304 for index in indexes: 305 if index.column() == 0: 306 model_index = self.launchlist_proxyModel.mapToSource(index) 307 item = self.launchlist_model.itemFromIndex(model_index) 308 if item is not None and isinstance(item, LaunchItem): 309 result.append(item) 310 return result
311
312 - def keyReleaseEvent(self, event):
313 ''' 314 Defines some of shortcuts for navigation/management in launch 315 list view or topics view. 316 ''' 317 key_mod = QApplication.keyboardModifiers() 318 if not self.xmlFileView.state() == QAbstractItemView.EditingState: 319 # remove history file from list by pressing DEL 320 if event == QKeySequence.Delete: 321 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 322 for item in selected: 323 if item.path in self.launchlist_model.load_history: 324 self.launchlist_model.removeFromLoadHistory(item.path) 325 self.launchlist_model.reloadCurrentPath() 326 elif not key_mod and event.key() == Qt.Key_F4 and self.editXmlButton.isEnabled(): 327 # open selected launch file in xml editor by F4 328 self.on_edit_xml_clicked() 329 elif event == QKeySequence.Find: 330 # set focus to filter box for packages 331 self.searchPackageLine.setFocus(Qt.ActiveWindowFocusReason) 332 elif event == QKeySequence.Paste: 333 # paste files from clipboard 334 self.launchlist_model.paste_from_clipboard() 335 elif event == QKeySequence.Copy: 336 # copy the selected items as file paths into clipboard 337 selected = self.xmlFileView.selectionModel().selectedIndexes() 338 indexes = [] 339 for s in selected: 340 indexes.append(self.launchlist_proxyModel.mapToSource(s)) 341 self.launchlist_model.copy_to_clipboard(indexes) 342 if self.searchPackageLine.hasFocus() and event.key() == Qt.Key_Escape: 343 # cancel package filtering on pressing ESC 344 self.launchlist_model.show_packages(False) 345 self.searchPackageLine.setText('') 346 self.xmlFileView.setFocus(Qt.ActiveWindowFocusReason) 347 QDockWidget.keyReleaseEvent(self, event)
348
349 - def _searchline_focusInEvent(self, event):
350 self.launchlist_model.show_packages(True) 351 QLineEdit.focusInEvent(self.searchPackageLine, event)
352