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

Source Code for Module node_manager_fkie.launch_list_model

  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   
 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 
44 45 -class LaunchItem(QtGui.QStandardItem):
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 # def __del__(self): 85 # print "delete LAUNCH", self.name 86
87 - def type(self):
88 return LaunchItem.ITEM_TYPE
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 # return the displayed item name 101 if self.id == LaunchItem.RECENT_FILE: 102 return "%s [%s]"%(self.name, self.package_name)#.decode(sys.getfilesystemencoding()) 103 else: 104 return "%s"%self.name 105 elif role == QtCore.Qt.ToolTipRole: 106 # return the tooltip of the item 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 # elif role == QtCore.Qt.DecorationRole: 112 # # return the showed icon 113 # pathItem, path, pathId = self.items[index.row()] 114 # if self.id > LaunchListModel.NOTHING and self.model().icons.has_key(self.id): 115 # return self.model().icons[self.id] 116 # return None 117 elif role == QtCore.Qt.EditRole: 118 return "%s"%self.name 119 else: 120 # We don't care about anything else, so return default value 121 return QtGui.QStandardItem.data(self, role)
122
123 - def setData(self, value, role=QtCore.Qt.EditRole):
124 if role == QtCore.Qt.EditRole: 125 # rename the file or folder 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
138 - def getItemList(self, name, path, id, root):
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
154 - def isLaunchFile(self):
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
161 - def isConfigFile(self):
162 ''' 163 @return: C{True} if it is a config file 164 @rtype: C{boolean} 165 ''' 166 return self.id == self.CFG_FILE
167
168 169 170 -class LaunchListModel(QtGui.QStandardItemModel):
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
177 - def __init__(self):
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() # workaround for using with PyQt: store the python object to keep the defined attributes in the TopicItem subclass 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
196 - def _getRootItems(self):
197 result = list(self.load_history) 198 result.extend(self.root_paths) 199 return result
200
201 - def _fill_packages(self, packages):
202 self.__packages = packages
203 204 #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 205 #%%%%%%%%%%%%% Overloaded methods %%%%%%%% 206 #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 207
208 - def flags(self, index):
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 #%%%%%%%%%%%%% Drag operation %%%%%%%% 229 #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 230
231 - def mimeTypes(self):
232 return ['text/plain']
233
234 - def mimeData(self, indexes):
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 #%%%%%%%%%%%%% External usage %%%%%%%% 247 #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 248
249 - def reloadPackages(self):
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
258 - def reloadCurrentPath(self):
259 ''' 260 Reloads the current path. 261 ''' 262 # clear the cache for package names 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
278 - def expandItem(self, path_item, path, id):
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
302 - def setPath(self, path):
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 # if self._is_in_ros_packages(path): 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 # self.reloadCurrentPath() # todo: keep the item selected in list view after the reload the path 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 # self.reloadCurrentPath() # todo: keep the item selected in list view after the reload the path 333
334 - def show_packages(self, show):
335 if show: 336 # clear the cache for package names 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
348 - def paste_from_clipboard(self):
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
367 - def copy_to_clipboard(self, indexes):
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 #%%%%%%%%%%%%% Functionality %%%%%%%% 383 #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 384
385 - def _setNewList(self, (root_path, items)):
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 # add new items 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
408 - def _is_in_ros_packages(self, path):
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 #TODO fix for paths with symbolic links 415 for p in self.root_paths: 416 if path.startswith(p): 417 return True 418 return False
419
420 - def _addPathToList(self, item, path, path_id):
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 # add sorted a new entry 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] # workaround for using with PyQt: store the python object to keep the defined attributes in the TopicItem subclass 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
455 - def _identifyPath(self, path):
456 ''' 457 Determines the id of the given path 458 @return: the id represents whether it is a file, package or stack 459 @rtype: C{constants of LaunchItem} 460 ''' 461 if path in self.DIR_CACHE: 462 if path in self.load_history: 463 return LaunchItem.RECENT_FILE 464 return self.DIR_CACHE[path] 465 if os.path.basename(path)[0] != '.': 466 if path in self.load_history: 467 self.DIR_CACHE[path] = LaunchItem.RECENT_FILE 468 return LaunchItem.RECENT_FILE 469 elif os.path.isfile(path): 470 if (path.endswith('.launch')): 471 self.DIR_CACHE[path] = LaunchItem.LAUNCH_FILE 472 return LaunchItem.LAUNCH_FILE 473 else: 474 for e in nm.settings().launch_view_file_ext: 475 if path.endswith(e): 476 self.DIR_CACHE[path] = LaunchItem.CFG_FILE 477 return LaunchItem.CFG_FILE 478 elif os.path.isdir(path): 479 fileList = os.listdir(path) 480 if self._containsLaunches(path): 481 if 'stack.xml' in fileList: 482 self.DIR_CACHE[path] = LaunchItem.STACK 483 return LaunchItem.STACK 484 elif is_package(fileList): 485 self.DIR_CACHE[path] = LaunchItem.PACKAGE 486 return LaunchItem.PACKAGE 487 else: 488 self.DIR_CACHE[path] = LaunchItem.FOLDER 489 return LaunchItem.FOLDER 490 self.DIR_CACHE[path] = LaunchItem.NOT_FOUND 491 return LaunchItem.NOT_FOUND
492
493 - def _containsLaunches(self, path):
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
511 - def _moveDown(self, path):
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
533 - def _moveUp(self, path):
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