1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import os
34 import sys
35 import shutil
36
37 from python_qt_binding import QtCore
38 from python_qt_binding import QtGui
39
40 import node_manager_fkie as nm
41 from common import is_package, package_name
42 from packages_thread import PackagesThread
43 from .detailed_msg_box import WarningMessageBox
46 '''
47 The launch item stored in the launch model.
48 '''
49
50 ITEM_TYPE = QtGui.QStandardItem.UserType + 40
51
52 NOT_FOUND = -1
53 NOTHING = 0
54 RECENT_FILE = 1
55 LAUNCH_FILE = 2
56 CFG_FILE = 3
57 FOLDER = 10
58 PACKAGE = 11
59 STACK = 12
60
61 - def __init__(self, name, path, id, parent=None):
62 '''
63 Initialize the topic item.
64 @param name: the topic name
65 @type name: C{str}
66 '''
67 QtGui.QStandardItem.__init__(self, name)
68 self.parent_item = parent
69 self.name = name
70 self.path = path
71 self.package_name = package_name(os.path.dirname(self.path))[0]
72 self.id = id
73 if self.id == LaunchItem.FOLDER:
74 self.setIcon(QtGui.QIcon(":/icons/crystal_clear_folder.png"))
75 elif self.id == LaunchItem.PACKAGE:
76 self.setIcon(QtGui.QIcon(":/icons/crystal_clear_package.png"))
77 elif self.id == LaunchItem.LAUNCH_FILE:
78 self.setIcon(QtGui.QIcon(":/icons/crystal_clear_launch_file.png"))
79 elif self.id == LaunchItem.RECENT_FILE:
80 self.setIcon(QtGui.QIcon(":/icons/crystal_clear_launch_file_recent.png"))
81 elif self.id == LaunchItem.STACK:
82 self.setIcon(QtGui.QIcon(":/icons/crystal_clear_stack.png"))
83
84
85
86
89
90 - def data(self, role):
91 '''
92 The view asks us for all sorts of information about our data...
93 @param index: parent of the list
94 @type index: L{QtCore.QModelIndex}
95 @param role: the art of the data
96 @type role: L{QtCore.Qt.DisplayRole}
97 @see: U{http://www.pyside.org/docs/pyside-1.0.1/PySide/QtCore/Qt.html}
98 '''
99 if role == QtCore.Qt.DisplayRole:
100
101 if self.id == LaunchItem.RECENT_FILE:
102 return "%s [%s]"%(self.name, self.package_name)
103 else:
104 return "%s"%self.name
105 elif role == QtCore.Qt.ToolTipRole:
106
107 result = "%s"%self.path
108 if self.id == LaunchItem.RECENT_FILE:
109 result = "%s\nPress 'Delete' to remove the entry from the history list"%self.path
110 return result
111
112
113
114
115
116
117 elif role == QtCore.Qt.EditRole:
118 return "%s"%self.name
119 else:
120
121 return QtGui.QStandardItem.data(self, role)
122
123 - def setData(self, value, role=QtCore.Qt.EditRole):
124 if role == QtCore.Qt.EditRole:
125
126 if self.name != value and self.id in [self.RECENT_FILE, self.LAUNCH_FILE, self.CFG_FILE, self.FOLDER]:
127 new_path = os.path.join(os.path.dirname(self.path), value)
128 if not os.path.exists(new_path):
129 os.rename(self.path, new_path)
130 self.name = value
131 self.path = new_path
132 else:
133 WarningMessageBox(QtGui.QMessageBox.Warning, "Path already exists",
134 "`%s` already exists!"%value, "Complete path: %s"%new_path).exec_()
135 return QtGui.QStandardItem.setData(self, value, role)
136
137 @classmethod
139 '''
140 Creates the list of the items . This list is used for the
141 visualization of topic data as a table row.
142 @param name: the topic name
143 @type name: C{str}
144 @param root: The parent QStandardItem
145 @type root: L{PySide.QtGui.QStandardItem}
146 @return: the list for the representation as a row
147 @rtype: C{[L{LaunchItem} or L{PySide.QtGui.QStandardItem}, ...]}
148 '''
149 items = []
150 item = LaunchItem(name, path, id, parent=root)
151 items.append(item)
152 return items
153
155 '''
156 @return: C{True} if it is a launch file
157 @rtype: C{boolean}
158 '''
159 return not self.path is None and os.path.isfile(self.path) and self.path.endswith('.launch')
160
162 '''
163 @return: C{True} if it is a config file
164 @rtype: C{boolean}
165 '''
166 return self.id == self.CFG_FILE
167
171 '''
172 The model to manage the list with launch files.
173 '''
174 header = [('Name', -1)]
175 '''@ivar: the list with columns C{[(name, width), ...]}'''
176
178 '''
179 Creates a new list model.
180 '''
181 QtGui.QStandardItemModel.__init__(self)
182 self.setColumnCount(len(LaunchListModel.header))
183 self.setHorizontalHeaderLabels([label for label, width in LaunchListModel.header])
184 self.pyqt_workaround = dict()
185 self.items = []
186 self.DIR_CACHE = {}
187 self.currentPath = None
188 self.load_history = self._getLoadHistory()
189 self.root_paths = [os.path.normpath(p) for p in os.getenv("ROS_PACKAGE_PATH").split(':')]
190 self._setNewList(self._moveUp(None))
191 self.__packages = {}
192 self._fill_packages_thread = PackagesThread()
193 self._fill_packages_thread.packages.connect(self._fill_packages)
194 self._fill_packages_thread.start()
195
197 result = list(self.load_history)
198 result.extend(self.root_paths)
199 return result
200
203
204
205
206
207
209 '''
210 @param index: parent of the list
211 @type index: L{PySide.QtCore.QModelIndex}
212 @return: Flag or the requested item
213 @rtype: L{PySide.QtCore.Qt.ItemFlag}
214 @see: U{http://www.pyside.org/docs/pyside-1.0.1/PySide/QtCore/Qt.html}
215 '''
216 if not index.isValid():
217 return QtCore.Qt.NoItemFlags
218 try:
219 item = self.itemFromIndex(index)
220 result = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsDragEnabled
221 if item.id in [LaunchItem.RECENT_FILE, LaunchItem.LAUNCH_FILE, LaunchItem.CFG_FILE, LaunchItem.FOLDER]:
222 result = result | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled
223 return result
224 except:
225 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
226
227
228
229
230
232 return ['text/plain']
233
235 mimeData = QtCore.QMimeData()
236 text = ''
237 for index in indexes:
238 if index.isValid():
239 item = self.itemFromIndex(index)
240 prev = '%s\n'%text if text else ''
241 text = '%sfile://%s'%(prev, item.path)
242 mimeData.setData('text/plain', text)
243 return mimeData
244
245
246
247
248
250 '''
251 Reloads the cached packag list.
252 '''
253 if not self._fill_packages_thread.isAlive():
254 self._fill_packages_thread = PackagesThread()
255 self._fill_packages_thread.packages.connect(self._fill_packages)
256 self._fill_packages_thread.start()
257
259 '''
260 Reloads the current path.
261 '''
262
263 try:
264 from common import PACKAGE_CACHE
265 PACKAGE_CACHE.clear()
266 self.DIR_CACHE = {}
267 except:
268 import traceback
269 print traceback.format_exc(2)
270 try:
271 if self.currentPath is None:
272 self._setNewList(self._moveUp(self.currentPath))
273 else:
274 self._setNewList(self._moveDown(self.currentPath))
275 except:
276 self._setNewList(self._moveUp(None))
277
279 '''
280 Returns for the given item and path the file path if this is a file. Otherwise the
281 folder will be expanded and None will be returned.
282 @param path_item: the list item
283 @type path_item: C{str}
284 @param path: the real path of the item
285 @type path: C{str}
286 @return: path of the launch file or None
287 @rtype: C{str
288 @raise Exception if no path to given item was found
289 '''
290 if path_item == '..':
291 root_path, items = self._moveUp(os.path.dirname(path))
292 elif os.path.isfile(path):
293 return path
294 elif id == LaunchItem.RECENT_FILE or id == LaunchItem.LAUNCH_FILE:
295 raise Exception("Invalid file path: %s", path)
296 else:
297 root_path, items = self._moveDown(path)
298 self._setNewList((root_path, items))
299 return None
300
301
303 '''
304 Shows the new path in the launch configuration view. Only if the new path
305 is in ros package paths
306 @param path: new path
307 @type path: C{str}
308 '''
309
310 self._setNewList(self._moveDown(path))
311
312 - def add2LoadHistory(self, file):
313 try:
314 self.load_history.remove(file)
315 except:
316 pass
317 self.load_history.append(file)
318 try:
319 while len(self.load_history) > nm.settings().launch_history_length:
320 self.load_history.pop(0)
321 except:
322 pass
323 self._storeLoadHistory(self.load_history)
324
325
326 - def removeFromLoadHistory(self, file):
327 try:
328 self.load_history.remove(file)
329 except:
330 pass
331 self._storeLoadHistory(self.load_history)
332
333
335 if show:
336
337 try:
338 items = []
339 for package, path in self.__packages.items():
340 items.append((package, path, LaunchItem.PACKAGE))
341 self._setNewList((self.currentPath if self.currentPath else '', items))
342 except:
343 import traceback
344 print traceback.format_exc(2)
345 else:
346 self._setNewList(self._moveUp(self.currentPath))
347
349 '''
350 Copy the file or folder to new position...
351 '''
352 if QtGui.QApplication.clipboard().mimeData().hasText() and self.currentPath:
353 text = QtGui.QApplication.clipboard().mimeData().text()
354 if text.startswith('file://'):
355 path = text.replace('file://', '')
356 basename = os.path.basename(text)
357 ok = True
358 if os.path.exists(os.path.join(self.currentPath, basename)):
359 basename, ok = QtGui.QInputDialog.getText(None, 'File exists', 'New name (or override):', QtGui.QLineEdit.Normal, basename)
360 if ok and basename:
361 if os.path.isdir(path):
362 shutil.copytree(path, os.path.join(self.currentPath, basename))
363 elif os.path.isfile(path):
364 shutil.copy2(path, os.path.join(self.currentPath, basename))
365 self.reloadCurrentPath()
366
368 '''
369 Copy the selected path to the clipboard
370 '''
371 mimeData = QtCore.QMimeData()
372 text = ''
373 for index in indexes:
374 if index.isValid():
375 item = self.itemFromIndex(index)
376 prev = '%s\n'%text if text else ''
377 text = '%sfile://%s'%(prev, item.path)
378 mimeData.setData('text/plain', text)
379 QtGui.QApplication.clipboard().setMimeData(mimeData)
380
381
382
383
384
386 '''
387 Sets the list to the given path and insert the items. If the root path is not
388 None the additional item '..' to go up will be inserted. The items parameter
389 is a tupel with three values (the displayed name, the path of the item, the id
390 of the item).
391 @see: L{LaunchListModel._addPathToList()}
392 @param root_path: the root directory
393 @type root_path: C{str}
394 @param items: the list with characterized items
395 @type items: C{[(item, path, id)]}
396 '''
397 root = self.invisibleRootItem()
398 while root.rowCount():
399 root.removeRow(0)
400 self.pyqt_workaround.clear()
401
402 if not root_path is None:
403 self._addPathToList('..', root_path, LaunchItem.NOTHING)
404 for item_name, item_path, item_id in items:
405 self._addPathToList(item_name, item_path, item_id)
406 self.currentPath = root_path
407
409 '''
410 Test whether the given path is in ROS_PACKAGE_PATH.
411 @return: C{True}, if the path is in the ROS_PACKAGE_PATH
412 @rtype: C{boolean}
413 '''
414
415 for p in self.root_paths:
416 if path.startswith(p):
417 return True
418 return False
419
421 '''
422 Inserts the given item in the list model.
423 @param item: the displayed name
424 @type item: C{str}
425 @param path: the path of the item
426 @type path: C{str}
427 @param path_id: the id of the item, which represents whether it is a file, package or stack.
428 @type path_id: C{constants of LaunchListModel}
429 '''
430 root = self.invisibleRootItem()
431 if item is None or path is None or path_id == LaunchItem.NOT_FOUND:
432 return False
433 if (path_id != LaunchItem.NOT_FOUND):
434
435 try:
436 for i in range(root.rowCount()):
437 launchItem = root.child(i)
438 launch_file_cmp = (path_id in [LaunchItem.RECENT_FILE, LaunchItem.LAUNCH_FILE] and item < launchItem.name)
439 launch_id_cmp = (launchItem.id > path_id and launchItem.id > LaunchItem.LAUNCH_FILE)
440 launch_name_cmp = (launchItem.id == path_id and item < launchItem.name)
441 if launch_file_cmp or launch_id_cmp or launch_name_cmp:
442 new_item_row = LaunchItem.getItemList(item, path, path_id, root)
443 root.insertRow(i, new_item_row)
444 self.pyqt_workaround[item] = new_item_row[0]
445 return True
446 new_item_row = LaunchItem.getItemList(item, path, path_id, root)
447 root.appendRow(new_item_row)
448 self.pyqt_workaround[item] = new_item_row[0]
449 return True
450 except:
451 import traceback
452 print traceback.format_exc(2)
453 return False
454
492
494 '''
495 Moves recursively down in the path tree and searches for a launch file. If
496 one is found True will be returned.
497 @return: C{True} if the path contains a launch file.
498 @rtype: C{boolean}
499 '''
500 fileList = os.listdir(path)
501 for file in fileList:
502 file_name, file_extension = os.path.splitext(file)
503 if os.path.isfile(os.path.join(path, file)) and (file.endswith('.launch')) or (file_extension in nm.settings().launch_view_file_ext):
504 return True
505 for file in fileList:
506 if os.path.isdir(os.path.join(path, file)):
507 if self._containsLaunches(os.path.join(path, file)):
508 return True
509 return False
510
512 '''
513 Moves recursively down in the path tree until the current path contains a
514 launch file.
515 @return: tupel of (root_path, items)
516 @rtype: C{tupel of (root_path, items)}
517 @see: L{LaunchListModel._setNewList()}
518 '''
519 result_list = []
520 dirlist = os.listdir(path)
521 for file in dirlist:
522 item = os.path.normpath(''.join([path, '/', file]))
523 pathItem = os.path.basename(item)
524 if pathItem == 'src':
525 pathItem = '%s (src)'%os.path.basename(os.path.dirname(item))
526 pathId = self._identifyPath(item)
527 if (pathId != LaunchItem.NOT_FOUND):
528 result_list.append((pathItem, item, pathId))
529 if len(result_list) == 1 and not os.path.isfile(result_list[0][1]):
530 return self._moveDown(result_list[0][1])
531 return path, result_list
532
534 '''
535 Moves recursively up in the path tree until the current path contains a
536 launch file or the root path defined by ROS_PACKAGES_PATH is reached.
537 @return: tupel of (root_path, items)
538 @rtype: C{tupel of (root_path, items)}
539 @see: L{LaunchListModel._setNewList()}
540 '''
541 result_list = []
542 if path is None or not self._is_in_ros_packages(path):
543 dirlist = self._getRootItems()
544 path = None
545 else:
546 dirlist = os.listdir(path)
547 for file in dirlist:
548 item = os.path.normpath(os.path.join(path, file)) if not path is None else file
549 pathItem = os.path.basename(item)
550 if pathItem == 'src':
551 pathItem = '%s (src)'%os.path.basename(os.path.dirname(item))
552 pathId = self._identifyPath(item)
553 if (pathId != LaunchItem.NOT_FOUND):
554 result_list.append((pathItem, item, pathId))
555 if not path is None and len(result_list) == 1 and not os.path.isfile(result_list[0][1]):
556 return self._moveUp(os.path.dirname(path))
557 else:
558 self.currentPath = None
559 return path, result_list
560
561 - def _getLoadHistory(self):
562 '''
563 Read the history of the recently loaded files from the file stored in ROS_HOME path.
564 @return: the list with file names
565 @rtype: C{[str]}
566 '''
567 result = list()
568 historyFile = nm.settings().qsettings(nm.settings().LAUNCH_HISTORY_FILE)
569 size = historyFile.beginReadArray("launch_history")
570 for i in range(size):
571 historyFile.setArrayIndex(i)
572 if i >= nm.settings().launch_history_length:
573 break
574 file = historyFile.value("file")
575 if os.path.isfile(file):
576 result.append(file)
577 historyFile.endArray()
578 return result
579
580 - def _storeLoadHistory(self, files):
581 '''
582 Saves the list of recently loaded files to history. The existing history will be replaced!
583 @param files: the list with filenames
584 @type files: C{[str]}
585 '''
586 historyFile = nm.settings().qsettings(nm.settings().LAUNCH_HISTORY_FILE)
587 historyFile.beginWriteArray("launch_history")
588 for i, file in enumerate(files):
589 historyFile.setArrayIndex(i)
590 historyFile.setValue("file", file)
591 historyFile.endArray()
592