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 
 40  except: 
 41      from python_qt_binding.QtWidgets import QAbstractItemView, QAction, QApplication, QDockWidget, QFileDialog, QLineEdit, QMenu 
 42  from python_qt_binding.QtGui import QKeySequence 
 43  import os 
 44   
 45  import rospy 
 46   
 47  import node_manager_fkie as nm 
 48  from .common import package_name, utf8  # , masteruri_from_ros 
 49  from .detailed_msg_box import MessageBox 
 50  from .launch_list_model import LaunchListModel, LaunchItem 
 51  from .parameter_dialog import ParameterDialog 
 52  from .progress_queue import ProgressQueue  # , ProgressThread 
 53   
 54   
55 -class LaunchFilesWidget(QDockWidget):
56 ''' 57 Launch file browser. 58 ''' 59 60 load_signal = Signal(str, list, str) 61 ''' load the launch file with given arguments (launchfile, argv, masteruri)''' 62 load_profile_signal = Signal(str) 63 ''' load the profile file ''' 64 load_as_default_signal = Signal(str, str) 65 ''' load the launch file as default (path, host) ''' 66 edit_signal = Signal(list) 67 ''' list of paths to open in an editor ''' 68 transfer_signal = Signal(list) 69 ''' list of paths selected for transfer ''' 70
71 - def __init__(self, parent=None):
72 ''' 73 Creates the window, connects the signals and init the class. 74 ''' 75 QDockWidget.__init__(self, parent) 76 # initialize parameter 77 self.__current_path = os.path.expanduser('~') 78 # load the UI file 79 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'LaunchFilesDockWidget.ui') 80 loadUi(ui_file, self) 81 # initialize the view for the launch files 82 self.launchlist_model = LaunchListModel() 83 self.launchlist_proxyModel = QSortFilterProxyModel(self) 84 self.launchlist_proxyModel.setSourceModel(self.launchlist_model) 85 self.xmlFileView.setModel(self.launchlist_proxyModel) 86 self.xmlFileView.setAlternatingRowColors(True) 87 self.xmlFileView.activated.connect(self.on_launch_selection_activated) 88 self.xmlFileView.setDragDropMode(QAbstractItemView.DragOnly) 89 self.xmlFileView.setDragEnabled(True) 90 sm = self.xmlFileView.selectionModel() 91 sm.selectionChanged.connect(self.on_xmlFileView_selection_changed) 92 # self.searchPackageLine.setVisible(False) 93 self.searchPackageLine.textChanged.connect(self.set_package_filter) 94 self.searchPackageLine.focusInEvent = self._searchline_focusInEvent 95 # connect to the button signals 96 self.refreshXmlButton.clicked.connect(self.on_refresh_xml_clicked) 97 self.editXmlButton.clicked.connect(self.on_edit_xml_clicked) 98 self.newXmlButton.clicked.connect(self.on_new_xml_clicked) 99 self.openXmlButton.clicked.connect(self.on_open_xml_clicked) 100 self.transferButton.clicked.connect(self.on_transfer_file_clicked) 101 self.loadXmlButton.clicked.connect(self.on_load_xml_clicked) 102 self.loadXmlAsDefaultButton.clicked.connect(self.on_load_as_default) 103 # creates a default config menu 104 start_menu = QMenu(self) 105 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) 106 start_menu.addAction(self.loadDeafaultAtHostAct) 107 self.loadXmlAsDefaultButton.setMenu(start_menu) 108 # initialize the progress queue 109 self.progress_queue = ProgressQueue(self.progressFrame_cfg, self.progressBar_cfg, self.progressCancelButton_cfg, 'Launch File')
110
111 - def stop(self):
112 ''' 113 Cancel the executing queued actions. This method must be 114 called at the exit! 115 ''' 116 self.progress_queue.stop()
117
118 - def on_launch_selection_activated(self, activated):
119 ''' 120 Tries to load the launch file, if one was activated. 121 ''' 122 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 123 for item in selected: 124 try: 125 lfile = self.launchlist_model.expandItem(item.name, item.path, item.id) 126 self.searchPackageLine.setText('') 127 if lfile is not None: 128 if item.isLaunchFile(): 129 nm.settings().launch_history_add(item.path) 130 key_mod = QApplication.keyboardModifiers() 131 if key_mod & Qt.ShiftModifier: 132 self.load_as_default_signal.emit(item.path, None) 133 elif key_mod & Qt.ControlModifier: 134 self.launchlist_model.setPath(os.path.dirname(item.path)) 135 else: 136 self.load_signal.emit(item.path, [], None) 137 elif item.isProfileFile(): 138 nm.settings().launch_history_add(item.path) 139 key_mod = QApplication.keyboardModifiers() 140 if key_mod & Qt.ControlModifier: 141 self.launchlist_model.setPath(os.path.dirname(item.path)) 142 else: 143 self.load_profile_signal.emit(item.path) 144 elif item.isConfigFile(): 145 self.edit_signal.emit([lfile]) 146 except Exception as e: 147 rospy.logwarn("Error while load launch file %s: %s" % (item, utf8(e))) 148 MessageBox.warning(self, "Load error", 149 'Error while load launch file:\n%s' % item.name, 150 "%s" % utf8(e))
151 # self.launchlist_model.reloadCurrentPath() 152
153 - def load_file(self, path, argv=[], masteruri=None):
154 ''' 155 Tries to load the launch file, if one was activated. 156 ''' 157 if path is not None: 158 if os.path.isfile(path): 159 if path.endswith('.launch'): 160 self.load_signal.emit(path, argv, masteruri) 161 elif path.endswith('.nmprofile'): 162 self.load_profile_signal.emit(path)
163
164 - def on_xmlFileView_selection_changed(self, selected, deselected):
165 ''' 166 On selection of a launch file, the buttons are enabled otherwise disabled. 167 ''' 168 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 169 for item in selected: 170 islaunch = item.isLaunchFile() 171 isconfig = item.isConfigFile() 172 isprofile = item.isProfileFile() 173 self.editXmlButton.setEnabled(islaunch or isconfig or isprofile) 174 self.loadXmlButton.setEnabled(islaunch or isprofile) 175 self.transferButton.setEnabled(islaunch or isconfig) 176 self.loadXmlAsDefaultButton.setEnabled(islaunch)
177
178 - def set_package_filter(self, text):
179 self.launchlist_proxyModel.setFilterRegExp(QRegExp(text, 180 Qt.CaseInsensitive, 181 QRegExp.Wildcard))
182
183 - def on_refresh_xml_clicked(self):
184 ''' 185 Reload the current path. 186 ''' 187 self.launchlist_model.reloadCurrentPath() 188 self.launchlist_model.reloadPackages() 189 self.editXmlButton.setEnabled(False) 190 self.loadXmlButton.setEnabled(False) 191 self.transferButton.setEnabled(False) 192 self.loadXmlAsDefaultButton.setEnabled(False) 193 try: 194 from roslaunch import substitution_args 195 import rospkg 196 substitution_args._rospack = rospkg.RosPack() 197 except Exception as err: 198 rospy.logwarn("Cannot reset package cache: %s" % utf8(err))
199
200 - def on_edit_xml_clicked(self):
201 ''' 202 Opens an XML editor to edit the launch file. 203 ''' 204 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 205 for item in selected: 206 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 207 if path is not None: 208 self.edit_signal.emit([path])
209
210 - def on_new_xml_clicked(self):
211 ''' 212 Creates a new launch file. 213 ''' 214 # get new file from open dialog, use last path if one exists 215 open_path = self.__current_path 216 if self.launchlist_model.currentPath is not None: 217 open_path = self.launchlist_model.currentPath 218 (fileName, _) = QFileDialog.getSaveFileName(self, 219 "New launch file", 220 open_path, 221 "Config files (*.launch *.yaml);;All files (*)") 222 if fileName: 223 try: 224 (pkg, _) = package_name(os.path.dirname(fileName)) # _:=pkg_path 225 if pkg is None: 226 MessageBox.warning(self, "New File Error", 'The new file is not in a ROS package') 227 return 228 with open(fileName, 'w+') as f: 229 f.write("<launch>\n" 230 " <arg name=\"robot_ns\" default=\"my_robot\"/>\n" 231 " <group ns=\"$(arg robot_ns)\">\n" 232 " <param name=\"tf_prefix\" value=\"$(arg robot_ns)\"/>\n" 233 "\n" 234 " <node pkg=\"my_pkg\" type=\"my_node\" name=\"my_name\" >\n" 235 " <param name=\"capability_group\" value=\"MY_GROUP\"/>\n" 236 " </node>\n" 237 " </group>\n" 238 "</launch>\n" 239 ) 240 self.__current_path = os.path.dirname(fileName) 241 self.launchlist_model.setPath(self.__current_path) 242 self.edit_signal.emit([fileName]) 243 except EnvironmentError as e: 244 MessageBox.warning(self, "New File Error", 245 'Error while create a new file', 246 '%s' % utf8(e))
247
248 - def on_open_xml_clicked(self):
249 (fileName, _) = QFileDialog.getOpenFileName(self, 250 "Load launch file", 251 self.__current_path, 252 "Config files (*.launch);;All files (*)") 253 if fileName: 254 self.__current_path = os.path.dirname(fileName) 255 nm.settings().launch_history_add(fileName) 256 self.load_signal.emit(fileName, [], None)
257
258 - def on_transfer_file_clicked(self):
259 ''' 260 Emit the signal to copy the selected file to a remote host. 261 ''' 262 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 263 paths = list() 264 for item in selected: 265 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 266 if path is not None: 267 paths.append(path) 268 if paths: 269 self.transfer_signal.emit(paths)
270
271 - def on_load_xml_clicked(self):
272 ''' 273 Tries to load the selected launch file. The button is only enabled and this 274 method is called, if the button was enabled by on_launch_selection_clicked() 275 ''' 276 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 277 for item in selected: 278 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 279 if path is not None: 280 nm.settings().launch_history_add(item.path) 281 self.load_signal.emit(path, [], None)
282
284 ''' 285 Tries to load the selected launch file as default configuration. The button 286 is only enabled and this method is called, if the button was enabled by 287 on_launch_selection_clicked() 288 ''' 289 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 290 for item in selected: 291 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 292 if path is not None: 293 params = {'Host': ('string', 'localhost')} 294 dia = ParameterDialog(params) 295 dia.setFilterVisible(False) 296 dia.setWindowTitle('Start node on...') 297 dia.resize(350, 120) 298 dia.setFocusField('Host') 299 if dia.exec_(): 300 try: 301 params = dia.getKeywords() 302 host = params['Host'] 303 rospy.loginfo("LOAD the launch file on host %s as default: %s" % (host, path)) 304 nm.settings().launch_history_add(path) 305 self.load_as_default_signal.emit(path, host) 306 except Exception, e: 307 MessageBox.warning(self, "Load default config error", 308 'Error while parse parameter', 309 '%s' % utf8(e))
310
311 - def on_load_as_default(self):
312 ''' 313 Tries to load the selected launch file as default configuration. The button 314 is only enabled and this method is called, if the button was enabled by 315 on_launch_selection_clicked() 316 ''' 317 key_mod = QApplication.keyboardModifiers() 318 if (key_mod & Qt.ShiftModifier): 319 self.loadXmlAsDefaultButton.showMenu() 320 else: 321 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 322 for item in selected: 323 path = self.launchlist_model.expandItem(item.name, item.path, item.id) 324 if path is not None: 325 rospy.loginfo("LOAD the launch file as default: %s", path) 326 nm.settings().launch_history_add(path) 327 self.load_as_default_signal.emit(path, None)
328
329 - def _launchItemsFromIndexes(self, indexes, recursive=True):
330 result = [] 331 for index in indexes: 332 if index.column() == 0: 333 model_index = self.launchlist_proxyModel.mapToSource(index) 334 item = self.launchlist_model.itemFromIndex(model_index) 335 if item is not None and isinstance(item, LaunchItem): 336 result.append(item) 337 return result
338
339 - def keyPressEvent(self, event):
340 ''' 341 Defines some of shortcuts for navigation/management in launch 342 list view or topics view. 343 ''' 344 key_mod = QApplication.keyboardModifiers() 345 if not self.xmlFileView.state() == QAbstractItemView.EditingState: 346 # remove history file from list by pressing DEL 347 if event == QKeySequence.Delete: 348 selected = self._launchItemsFromIndexes(self.xmlFileView.selectionModel().selectedIndexes(), False) 349 for item in selected: 350 nm.settings().launch_history_remove(item.path) 351 self.launchlist_model.reloadCurrentPath() 352 elif not key_mod and event.key() == Qt.Key_F4 and self.editXmlButton.isEnabled(): 353 # open selected launch file in xml editor by F4 354 self.on_edit_xml_clicked() 355 elif event == QKeySequence.Find: 356 # set focus to filter box for packages 357 self.searchPackageLine.setFocus(Qt.ActiveWindowFocusReason) 358 elif event == QKeySequence.Paste: 359 # paste files from clipboard 360 self.launchlist_model.paste_from_clipboard() 361 elif event == QKeySequence.Copy: 362 # copy the selected items as file paths into clipboard 363 selected = self.xmlFileView.selectionModel().selectedIndexes() 364 indexes = [] 365 for s in selected: 366 indexes.append(self.launchlist_proxyModel.mapToSource(s)) 367 self.launchlist_model.copy_to_clipboard(indexes) 368 if self.searchPackageLine.hasFocus() and event.key() == Qt.Key_Escape: 369 # cancel package filtering on pressing ESC 370 self.launchlist_model.show_packages(False) 371 self.searchPackageLine.setText('') 372 self.xmlFileView.setFocus(Qt.ActiveWindowFocusReason) 373 QDockWidget.keyReleaseEvent(self, event)
374
375 - def _searchline_focusInEvent(self, event):
376 self.launchlist_model.show_packages(True) 377 QLineEdit.focusInEvent(self.searchPackageLine, event)
378