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