eric6/Project/ProjectBrowserModel.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2006 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the browser model.
8 """
9
10 from __future__ import unicode_literals
11
12 import os
13 import re
14
15 from PyQt5.QtCore import QDir, QModelIndex, pyqtSignal, QFileSystemWatcher, Qt
16 from PyQt5.QtGui import QColor
17
18 from UI.BrowserModel import BrowserModel, BrowserItem, BrowserDirectoryItem, \
19 BrowserFileItem
20
21 import UI.PixmapCache
22 import Preferences
23 import Utilities
24
25 import Utilities.ModuleParser
26
27 ProjectBrowserItemSimpleDirectory = 100
28 ProjectBrowserItemDirectory = 101
29 ProjectBrowserItemFile = 102
30
31 ProjectBrowserNoType = 0
32 ProjectBrowserSourceType = 1
33 ProjectBrowserFormType = 2
34 ProjectBrowserInterfaceType = 3
35 ProjectBrowserTranslationType = 4
36 ProjectBrowserOthersType = 5
37 ProjectBrowserResourceType = 6
38 ProjectBrowserProtocolsType = 7
39
40
41 class ProjectBrowserItemMixin(object):
42 """
43 Class implementing common methods of project browser items.
44
45 It is meant to be used as a mixin class.
46 """
47 def __init__(self, type_, bold=False):
48 """
49 Constructor
50
51 @param type_ type of file/directory in the project
52 @param bold flag indicating a highlighted font
53 """
54 self._projectTypes = [type_]
55 self.bold = bold
56 self.vcsState = " "
57
58 def getTextColor(self):
59 """
60 Public method to get the items text color.
61
62 @return text color (QColor)
63 """
64 if self.bold:
65 return Preferences.getProjectBrowserColour("Highlighted")
66 else:
67 return None
68
69 def setVcsState(self, state):
70 """
71 Public method to set the items VCS state.
72
73 @param state VCS state (one of A, C, M, U or " ") (string)
74 """
75 self.vcsState = state
76
77 def addVcsStatus(self, vcsStatus):
78 """
79 Public method to add the VCS status.
80
81 @param vcsStatus VCS status text (string)
82 """
83 self.itemData.append(vcsStatus)
84
85 def setVcsStatus(self, vcsStatus):
86 """
87 Public method to set the VCS status.
88
89 @param vcsStatus VCS status text (string)
90 """
91 self.itemData[1] = vcsStatus
92
93 def getProjectTypes(self):
94 """
95 Public method to get the project type.
96
97 @return project type
98 """
99 return self._projectTypes[:]
100
101 def addProjectType(self, type_):
102 """
103 Public method to add a type to the list.
104
105 @param type_ type to add to the list
106 """
107 self._projectTypes.append(type_)
108
109
110 class ProjectBrowserSimpleDirectoryItem(BrowserItem, ProjectBrowserItemMixin):
111 """
112 Class implementing the data structure for project browser simple directory
113 items.
114 """
115 def __init__(self, parent, projectType, text, path=""):
116 """
117 Constructor
118
119 @param parent parent item
120 @param projectType type of file/directory in the project
121 @param text text to be displayed (string)
122 @param path path of the directory (string)
123 """
124 BrowserItem.__init__(self, parent, text)
125 ProjectBrowserItemMixin.__init__(self, projectType)
126
127 self._dirName = path
128 if not os.path.isdir(self._dirName):
129 self._dirName = os.path.dirname(self._dirName)
130
131 self.type_ = ProjectBrowserItemSimpleDirectory
132 if os.path.lexists(self._dirName) and os.path.islink(self._dirName):
133 self.symlink = True
134 self.icon = UI.PixmapCache.getSymlinkIcon("dirClosed.png")
135 else:
136 self.icon = UI.PixmapCache.getIcon("dirClosed.png")
137
138 def setName(self, dinfo, full=True):
139 """
140 Public method to set the directory name.
141
142 @param dinfo dinfo is the string for the directory (string)
143 @param full flag indicating full pathname should be displayed (boolean)
144 """
145 self._dirName = os.path.abspath(dinfo)
146 self.itemData[0] = os.path.basename(self._dirName)
147
148 def dirName(self):
149 """
150 Public method returning the directory name.
151
152 @return directory name (string)
153 """
154 return self._dirName
155
156 def name(self):
157 """
158 Public method to return the name of the item.
159
160 @return name of the item (string)
161 """
162 return self._dirName
163
164 def lessThan(self, other, column, order):
165 """
166 Public method to check, if the item is less than the other one.
167
168 @param other reference to item to compare against (BrowserItem)
169 @param column column number to use for the comparison (integer)
170 @param order sort order (Qt.SortOrder) (for special sorting)
171 @return true, if this item is less than other (boolean)
172 """
173 if issubclass(other.__class__, BrowserFileItem):
174 if Preferences.getUI("BrowsersListFoldersFirst"):
175 return order == Qt.AscendingOrder
176
177 return BrowserItem.lessThan(self, other, column, order)
178
179
180 class ProjectBrowserDirectoryItem(BrowserDirectoryItem,
181 ProjectBrowserItemMixin):
182 """
183 Class implementing the data structure for project browser directory items.
184 """
185 def __init__(self, parent, dinfo, projectType, full=True, bold=False):
186 """
187 Constructor
188
189 @param parent parent item
190 @param dinfo dinfo is the string for the directory (string)
191 @param projectType type of file/directory in the project
192 @param full flag indicating full pathname should be displayed (boolean)
193 @param bold flag indicating a highlighted font (boolean)
194 """
195 BrowserDirectoryItem.__init__(self, parent, dinfo, full)
196 ProjectBrowserItemMixin.__init__(self, projectType, bold)
197
198 self.type_ = ProjectBrowserItemDirectory
199
200
201 class ProjectBrowserFileItem(BrowserFileItem, ProjectBrowserItemMixin):
202 """
203 Class implementing the data structure for project browser file items.
204 """
205 def __init__(self, parent, finfo, projectType, full=True, bold=False,
206 sourceLanguage=""):
207 """
208 Constructor
209
210 @param parent parent item
211 @param finfo the string for the file (string)
212 @param projectType type of file/directory in the project
213 @param full flag indicating full pathname should be displayed (boolean)
214 @param bold flag indicating a highlighted font (boolean)
215 @param sourceLanguage source code language of the project (string)
216 """
217 BrowserFileItem.__init__(self, parent, finfo, full, sourceLanguage)
218 ProjectBrowserItemMixin.__init__(self, projectType, bold)
219
220 self.type_ = ProjectBrowserItemFile
221
222
223 class ProjectBrowserModel(BrowserModel):
224 """
225 Class implementing the project browser model.
226
227 @signal vcsStateChanged(str) emitted after the VCS state has changed
228 """
229 vcsStateChanged = pyqtSignal(str)
230
231 def __init__(self, parent):
232 """
233 Constructor
234
235 @param parent reference to parent object (Project.Project)
236 """
237 super(ProjectBrowserModel, self).__init__(parent, nopopulate=True)
238
239 rootData = self.tr("Name")
240 self.rootItem = BrowserItem(None, rootData)
241 self.rootItem.itemData.append(self.tr("VCS Status"))
242
243 self.progDir = None
244 self.project = parent
245
246 self.watchedItems = {}
247 self.watcher = QFileSystemWatcher(self)
248 self.watcher.directoryChanged.connect(self.directoryChanged)
249
250 self.inRefresh = False
251
252 self.projectBrowserTypes = {
253 "SOURCES": ProjectBrowserSourceType,
254 "FORMS": ProjectBrowserFormType,
255 "RESOURCES": ProjectBrowserResourceType,
256 "INTERFACES": ProjectBrowserInterfaceType,
257 "PROTOCOLS": ProjectBrowserProtocolsType,
258 "TRANSLATIONS": ProjectBrowserTranslationType,
259 "OTHERS": ProjectBrowserOthersType,
260 }
261
262 self.colorNames = {
263 "A": "VcsAdded",
264 "M": "VcsModified",
265 "O": "VcsRemoved",
266 "R": "VcsReplaced",
267 "U": "VcsUpdate",
268 "Z": "VcsConflict",
269 }
270 self.itemBackgroundColors = {
271 " ": QColor(),
272 "A": Preferences.getProjectBrowserColour(self.colorNames["A"]),
273 "M": Preferences.getProjectBrowserColour(self.colorNames["M"]),
274 "O": Preferences.getProjectBrowserColour(self.colorNames["O"]),
275 "R": Preferences.getProjectBrowserColour(self.colorNames["R"]),
276 "U": Preferences.getProjectBrowserColour(self.colorNames["U"]),
277 "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]),
278 }
279
280 self.highLightColor = \
281 Preferences.getProjectBrowserColour("Highlighted")
282 # needed by preferencesChanged()
283
284 self.vcsStatusReport = {}
285
286 def data(self, index, role):
287 """
288 Public method to get data of an item.
289
290 @param index index of the data to retrieve (QModelIndex)
291 @param role role of data (Qt.ItemDataRole)
292 @return requested data
293 """
294 if not index.isValid():
295 return None
296
297 if role == Qt.TextColorRole:
298 if index.column() == 0:
299 try:
300 return index.internalPointer().getTextColor()
301 except AttributeError:
302 return None
303 elif role == Qt.BackgroundColorRole:
304 try:
305 col = self.itemBackgroundColors[
306 index.internalPointer().vcsState]
307 if col.isValid():
308 return col
309 else:
310 return None
311 except AttributeError:
312 return None
313 except KeyError:
314 return None
315
316 return BrowserModel.data(self, index, role)
317
318 def populateItem(self, parentItem, repopulate=False):
319 """
320 Public method to populate an item's subtree.
321
322 @param parentItem reference to the item to be populated
323 @param repopulate flag indicating a repopulation (boolean)
324 """
325 if parentItem.type() == ProjectBrowserItemSimpleDirectory:
326 return # nothing to do
327 elif parentItem.type() == ProjectBrowserItemDirectory:
328 self.populateProjectDirectoryItem(parentItem, repopulate)
329 elif parentItem.type() == ProjectBrowserItemFile:
330 self.populateFileItem(parentItem, repopulate)
331 else:
332 BrowserModel.populateItem(self, parentItem, repopulate)
333
334 def populateProjectDirectoryItem(self, parentItem, repopulate=False):
335 """
336 Public method to populate a directory item's subtree.
337
338 @param parentItem reference to the directory item to be populated
339 @param repopulate flag indicating a repopulation (boolean)
340 """
341 self._addWatchedItem(parentItem)
342
343 qdir = QDir(parentItem.dirName())
344
345 if Preferences.getUI("BrowsersListHiddenFiles"):
346 fileFilter = QDir.Filters(
347 QDir.AllEntries | QDir.Hidden | QDir.NoDotAndDotDot)
348 else:
349 fileFilter = QDir.Filters(
350 QDir.AllEntries | QDir.NoDot | QDir.NoDotDot)
351 entryInfoList = qdir.entryInfoList(fileFilter)
352
353 if len(entryInfoList) > 0:
354 if repopulate:
355 self.beginInsertRows(self.createIndex(
356 parentItem.row(), 0, parentItem),
357 0, len(entryInfoList) - 1)
358 states = {}
359 if self.project.vcs is not None:
360 for f in entryInfoList:
361 fname = f.absoluteFilePath()
362 states[os.path.normcase(fname)] = 0
363 dname = parentItem.dirName()
364 self.project.vcs.clearStatusCache()
365 states = self.project.vcs.vcsAllRegisteredStates(states, dname)
366
367 for f in entryInfoList:
368 if f.isDir():
369 node = ProjectBrowserDirectoryItem(
370 parentItem,
371 Utilities.toNativeSeparators(f.absoluteFilePath()),
372 parentItem.getProjectTypes()[0], False)
373 else:
374 node = ProjectBrowserFileItem(
375 parentItem,
376 Utilities.toNativeSeparators(f.absoluteFilePath()),
377 parentItem.getProjectTypes()[0])
378 if self.project.vcs is not None:
379 fname = f.absoluteFilePath()
380 if states[os.path.normcase(fname)] == \
381 self.project.vcs.canBeCommitted:
382 node.addVcsStatus(self.project.vcs.vcsName())
383 self.project.clearStatusMonitorCachedState(
384 f.absoluteFilePath())
385 else:
386 node.addVcsStatus(self.tr("local"))
387 else:
388 node.addVcsStatus("")
389 self._addItem(node, parentItem)
390 if repopulate:
391 self.endInsertRows()
392
393 def projectClosed(self):
394 """
395 Public method called after a project has been closed.
396 """
397 self.__vcsStatus = {}
398
399 self.watchedItems = {}
400 watchedDirs = self.watcher.directories()
401 if watchedDirs:
402 self.watcher.removePaths(watchedDirs)
403
404 self.rootItem.removeChildren()
405 self.beginResetModel()
406 self.endResetModel()
407
408 # reset the module parser cache
409 Utilities.ModuleParser.resetParsedModules()
410
411 def projectOpened(self):
412 """
413 Public method used to populate the model after a project has been
414 opened.
415 """
416 self.__vcsStatus = {}
417 states = {}
418 keys = list(self.projectBrowserTypes.keys())[:]
419
420 if self.project.vcs is not None:
421 for key in keys:
422 for fn in self.project.pdata[key]:
423 states[os.path.normcase(
424 os.path.join(self.project.ppath, fn))] = 0
425
426 self.project.vcs.clearStatusCache()
427 states = self.project.vcs.vcsAllRegisteredStates(
428 states, self.project.ppath)
429
430 self.inRefresh = True
431 for key in keys:
432 # Show the entry in bold in the others browser to make it more
433 # distinguishable
434 if key == "OTHERS":
435 bold = True
436 else:
437 bold = False
438
439 if key == "SOURCES":
440 sourceLanguage = self.project.getProjectLanguage()
441 else:
442 sourceLanguage = ""
443
444 for fn in self.project.pdata[key]:
445 fname = os.path.join(self.project.ppath, fn)
446 parentItem, dt = self.findParentItemByName(
447 self.projectBrowserTypes[key], fn)
448 if os.path.isdir(fname):
449 itm = ProjectBrowserDirectoryItem(
450 parentItem, fname, self.projectBrowserTypes[key],
451 False, bold)
452 else:
453 itm = ProjectBrowserFileItem(
454 parentItem, fname, self.projectBrowserTypes[key],
455 False, bold, sourceLanguage=sourceLanguage)
456 self._addItem(itm, parentItem)
457 if self.project.vcs is not None:
458 if states[os.path.normcase(fname)] == \
459 self.project.vcs.canBeCommitted:
460 itm.addVcsStatus(self.project.vcs.vcsName())
461 else:
462 itm.addVcsStatus(self.tr("local"))
463 else:
464 itm.addVcsStatus("")
465 self.inRefresh = False
466 self.beginResetModel()
467 self.endResetModel()
468
469 def findParentItemByName(self, type_, name, dontSplit=False):
470 """
471 Public method to find an item given its name.
472
473 <b>Note</b>: This method creates all necessary parent items, if they
474 don't exist.
475
476 @param type_ type of the item
477 @param name name of the item (string)
478 @param dontSplit flag indicating the name should not be split (boolean)
479 @return reference to the item found and the new display name (string)
480 """
481 if dontSplit:
482 pathlist = []
483 pathlist.append(name)
484 pathlist.append("ignore_me")
485 else:
486 pathlist = re.split(r'/|\\', name)
487
488 if len(pathlist) > 1:
489 olditem = self.rootItem
490 path = self.project.ppath
491 for p in pathlist[:-1]:
492 itm = self.findChildItem(p, 0, olditem)
493 path = os.path.join(path, p)
494 if itm is None:
495 itm = ProjectBrowserSimpleDirectoryItem(
496 olditem, type_, p, path)
497 self.__addVCSStatus(itm, path)
498 if self.inRefresh:
499 self._addItem(itm, olditem)
500 else:
501 if olditem == self.rootItem:
502 oldindex = QModelIndex()
503 else:
504 oldindex = self.createIndex(
505 olditem.row(), 0, olditem)
506 self.addItem(itm, oldindex)
507 else:
508 if type_ and type_ not in itm.getProjectTypes():
509 itm.addProjectType(type_)
510 index = self.createIndex(itm.row(), 0, itm)
511 self.dataChanged.emit(index, index)
512 olditem = itm
513 return (itm, pathlist[-1])
514 else:
515 return (self.rootItem, name)
516
517 def findChildItem(self, text, column, parentItem=None):
518 """
519 Public method to find a child item given some text.
520
521 @param text text to search for (string)
522 @param column column to search in (integer)
523 @param parentItem reference to parent item
524 @return reference to the item found
525 """
526 if parentItem is None:
527 parentItem = self.rootItem
528
529 for itm in parentItem.children():
530 if itm.data(column) == text:
531 return itm
532
533 return None
534
535 def addNewItem(self, typeString, name, additionalTypeStrings=None):
536 """
537 Public method to add a new item to the model.
538
539 @param typeString string denoting the type of the new item (string)
540 @param name name of the new item (string)
541 @param additionalTypeStrings names of additional types (list of string)
542 """
543 # Show the entry in bold in the others browser to make it more
544 # distinguishable
545 if typeString == "OTHERS":
546 bold = True
547 else:
548 bold = False
549
550 fname = os.path.join(self.project.ppath, name)
551 parentItem, dt = self.findParentItemByName(
552 self.projectBrowserTypes[typeString], name)
553 if parentItem == self.rootItem:
554 parentIndex = QModelIndex()
555 else:
556 parentIndex = self.createIndex(parentItem.row(), 0, parentItem)
557 if os.path.isdir(fname):
558 itm = ProjectBrowserDirectoryItem(
559 parentItem, fname, self.projectBrowserTypes[typeString],
560 False, bold)
561 else:
562 if typeString == "SOURCES":
563 sourceLanguage = self.project.getProjectLanguage()
564 else:
565 sourceLanguage = ""
566 itm = ProjectBrowserFileItem(
567 parentItem, fname, self.projectBrowserTypes[typeString],
568 False, bold, sourceLanguage=sourceLanguage)
569 self.__addVCSStatus(itm, fname)
570 if additionalTypeStrings:
571 for additionalTypeString in additionalTypeStrings:
572 type_ = self.projectBrowserTypes[additionalTypeString]
573 itm.addProjectType(type_)
574 self.addItem(itm, parentIndex)
575
576 def renameItem(self, name, newFilename):
577 """
578 Public method to rename an item.
579
580 @param name the old display name (string)
581 @param newFilename new filename of the item (string)
582 """
583 itm = self.findItem(name)
584 if itm is None:
585 return
586
587 index = self.createIndex(itm.row(), 0, itm)
588 itm.setName(newFilename)
589 self.dataChanged.emit(index, index)
590 self.repopulateItem(newFilename)
591
592 def findItem(self, name):
593 """
594 Public method to find an item given its name.
595
596 @param name name of the item (string)
597 @return reference to the item found
598 """
599 if QDir.isAbsolutePath(name):
600 name = self.project.getRelativePath(name)
601 pathlist = re.split(r'/|\\', name)
602 if len(pathlist) > 0:
603 olditem = self.rootItem
604 for p in pathlist:
605 itm = self.findChildItem(p, 0, olditem)
606 if itm is None:
607 return None
608 olditem = itm
609 return itm
610 else:
611 return None
612
613 def itemIndexByName(self, name):
614 """
615 Public method to find an item's index given its name.
616
617 @param name name of the item (string)
618 @return index of the item found (QModelIndex)
619 """
620 itm = self.findItem(name)
621 if itm is None:
622 index = QModelIndex()
623 else:
624 index = self.createIndex(itm.row(), 0, itm)
625 return index
626
627 def itemIndexByNameAndLine(self, name, lineno):
628 """
629 Public method to find an item's index given its name.
630
631 @param name name of the item (string)
632 @param lineno one based line number of the item (integer)
633 @return index of the item found (QModelIndex)
634 """
635 index = QModelIndex()
636 itm = self.findItem(name)
637 if itm is not None and \
638 isinstance(itm, ProjectBrowserFileItem):
639 olditem = itm
640 autoPopulate = Preferences.getProject("AutoPopulateItems")
641 while itm is not None:
642 if not itm.isPopulated():
643 if itm.isLazyPopulated() and autoPopulate:
644 self.populateItem(itm)
645 else:
646 break
647 for child in itm.children():
648 try:
649 start, end = child.boundaries()
650 if end == -1:
651 end = 1000000 # assume end of file
652 if start <= lineno <= end:
653 itm = child
654 break
655 except AttributeError:
656 pass
657 else:
658 itm = None
659 if itm:
660 olditem = itm
661 index = self.createIndex(olditem.row(), 0, olditem)
662
663 return index
664
665 def directoryChanged(self, path):
666 """
667 Public slot to handle the directoryChanged signal of the watcher.
668
669 @param path path of the directory (string)
670 """
671 if path not in self.watchedItems:
672 # just ignore the situation we don't have a reference to the item
673 return
674
675 if Preferences.getUI("BrowsersListHiddenFiles"):
676 fileFilter = QDir.Filters(
677 QDir.AllEntries | QDir.Hidden | QDir.NoDotAndDotDot)
678 else:
679 fileFilter = QDir.Filters(
680 QDir.AllEntries | QDir.NoDot | QDir.NoDotDot)
681
682 for itm in self.watchedItems[path]:
683 oldCnt = itm.childCount()
684
685 qdir = QDir(itm.dirName())
686
687 entryInfoList = qdir.entryInfoList(fileFilter)
688
689 # step 1: check for new entries
690 children = itm.children()
691 for f in entryInfoList:
692 fpath = Utilities.toNativeSeparators(f.absoluteFilePath())
693 childFound = False
694 for child in children:
695 if child.name() == fpath:
696 childFound = True
697 children.remove(child)
698 break
699 if childFound:
700 continue
701
702 cnt = itm.childCount()
703 self.beginInsertRows(
704 self.createIndex(itm.row(), 0, itm), cnt, cnt)
705 if f.isDir():
706 node = ProjectBrowserDirectoryItem(
707 itm,
708 Utilities.toNativeSeparators(f.absoluteFilePath()),
709 itm.getProjectTypes()[0],
710 False)
711 else:
712 node = ProjectBrowserFileItem(
713 itm,
714 Utilities.toNativeSeparators(f.absoluteFilePath()),
715 itm.getProjectTypes()[0])
716 self._addItem(node, itm)
717 if self.project.vcs is not None:
718 self.project.vcs.clearStatusCache()
719 state = self.project.vcs.vcsRegisteredState(node.name())
720 if state == self.project.vcs.canBeCommitted:
721 node.addVcsStatus(self.project.vcs.vcsName())
722 else:
723 node.addVcsStatus(self.tr("local"))
724 self.endInsertRows()
725
726 # step 2: check for removed entries
727 if len(entryInfoList) != itm.childCount():
728 for row in range(oldCnt - 1, -1, -1):
729 child = itm.child(row)
730 childname = Utilities.fromNativeSeparators(child.name())
731 entryFound = False
732 for f in entryInfoList:
733 if f.absoluteFilePath() == childname:
734 entryFound = True
735 entryInfoList.remove(f)
736 break
737 if entryFound:
738 continue
739
740 self._removeWatchedItem(child)
741 self.beginRemoveRows(
742 self.createIndex(itm.row(), 0, itm), row, row)
743 itm.removeChild(child)
744 self.endRemoveRows()
745
746 def __addVCSStatus(self, item, name):
747 """
748 Private method used to set the vcs status of a node.
749
750 @param item item to work on
751 @param name filename belonging to this item (string)
752 """
753 if self.project.vcs is not None:
754 state = self.project.vcs.vcsRegisteredState(name)
755 if state == self.project.vcs.canBeCommitted:
756 item.addVcsStatus(self.project.vcs.vcsName())
757 else:
758 item.addVcsStatus(self.tr("local"))
759 else:
760 item.addVcsStatus("")
761
762 def __updateVCSStatus(self, item, name, recursive=True):
763 """
764 Private method used to update the vcs status of a node.
765
766 @param item item to work on
767 @param name filename belonging to this item (string)
768 @keyparam recursive flag indicating a recursive update (boolean)
769 """
770 if self.project.vcs is not None:
771 self.project.vcs.clearStatusCache()
772 state = self.project.vcs.vcsRegisteredState(name)
773 if state == self.project.vcs.canBeCommitted:
774 item.setVcsStatus(self.project.vcs.vcsName())
775 else:
776 item.setVcsStatus(self.tr("local"))
777 if recursive:
778 name = os.path.dirname(name)
779 parentItem = item.parent()
780 if name and parentItem is not self.rootItem:
781 self.__updateVCSStatus(parentItem, name, recursive)
782 else:
783 item.setVcsStatus("")
784
785 index = self.createIndex(item.row(), 0, item)
786 self.dataChanged.emit(index, index)
787
788 def updateVCSStatus(self, name, recursive=True):
789 """
790 Public method used to update the vcs status of a node.
791
792 @param name filename belonging to this item (string)
793 @param recursive flag indicating a recursive update (boolean)
794 """
795 item = self.findItem(name)
796 if item:
797 self.__updateVCSStatus(item, name, recursive)
798
799 def removeItem(self, name):
800 """
801 Public method to remove a named item.
802
803 @param name file or directory name of the item (string).
804 """
805 fname = os.path.basename(name)
806 parentItem = self.findParentItemByName(0, name)[0]
807 if parentItem == self.rootItem:
808 parentIndex = QModelIndex()
809 else:
810 parentIndex = self.createIndex(parentItem.row(), 0, parentItem)
811 childItem = self.findChildItem(fname, 0, parentItem)
812 if childItem is not None:
813 self.beginRemoveRows(parentIndex, childItem.row(), childItem.row())
814 parentItem.removeChild(childItem)
815 self.endRemoveRows()
816
817 def repopulateItem(self, name):
818 """
819 Public method to repopulate an item.
820
821 @param name name of the file relative to the project root (string)
822 """
823 itm = self.findItem(name)
824 if itm is None:
825 return
826
827 if itm.isLazyPopulated():
828 if not itm.isPopulated():
829 # item is not populated yet, nothing to do
830 return
831
832 if itm.childCount():
833 index = self.createIndex(itm.row(), 0, itm)
834 self.beginRemoveRows(index, 0, itm.childCount() - 1)
835 itm.removeChildren()
836 self.endRemoveRows()
837 Utilities.ModuleParser.resetParsedModule(
838 os.path.join(self.project.ppath, name))
839
840 self.populateItem(itm, True)
841
842 def projectPropertiesChanged(self):
843 """
844 Public method to react on a change of the project properties.
845 """
846 # nothing to do for now
847 return
848
849 def changeVCSStates(self, statesList):
850 """
851 Public slot to record the (non normal) VCS states.
852
853 @param statesList list of VCS state entries (list of strings) giving
854 the states in the first column and the path relative to the project
855 directory starting with the third column. The allowed status flags
856 are:
857 <ul>
858 <li>"A" path was added but not yet comitted</li>
859 <li>"M" path has local changes</li>
860 <li>"O" path was removed</li>
861 <li>"R" path was deleted and then re-added</li>
862 <li>"U" path needs an update</li>
863 <li>"Z" path contains a conflict</li>
864 <li>" " path is back at normal</li>
865 </ul>
866 """
867 statesList.sort()
868 lastHead = ""
869 itemCache = {}
870 if len(statesList) == 1 and statesList[0] == '--RESET--':
871 statesList = []
872 for name in list(self.__vcsStatus.keys()):
873 statesList.append(" {0}".format(name))
874
875 for name in statesList:
876 state = name[0]
877 name = name[1:].strip()
878 if state == ' ':
879 if name in self.__vcsStatus:
880 del self.__vcsStatus[name]
881 else:
882 self.__vcsStatus[name] = state
883
884 try:
885 itm = itemCache[name]
886 except KeyError:
887 itm = self.findItem(name)
888 if itm:
889 itemCache[name] = itm
890 if itm:
891 itm.setVcsState(state)
892 itm.setVcsStatus(self.project.vcs.vcsName())
893 index1 = self.createIndex(itm.row(), 0, itm)
894 index2 = self.createIndex(
895 itm.row(), self.rootItem.columnCount(), itm)
896 self.dataChanged.emit(index1, index2)
897
898 head, tail = os.path.split(name)
899 if head != lastHead:
900 if lastHead:
901 self.__changeParentsVCSState(lastHead, itemCache)
902 lastHead = head
903 if lastHead:
904 self.__changeParentsVCSState(lastHead, itemCache)
905 try:
906 globalVcsStatus = sorted(self.__vcsStatus.values())[-1]
907 except IndexError:
908 globalVcsStatus = ' '
909 self.vcsStateChanged.emit(globalVcsStatus)
910
911 def __changeParentsVCSState(self, path, itemCache):
912 """
913 Private method to recursively change the parents VCS state.
914
915 @param path pathname of parent item (string)
916 @param itemCache reference to the item cache used to store
917 references to named items
918 """
919 while path:
920 try:
921 itm = itemCache[path]
922 except KeyError:
923 itm = self.findItem(path)
924 if itm:
925 itemCache[path] = itm
926 if itm:
927 state = " "
928 for id_ in itm.children():
929 if state < id_.vcsState:
930 state = id_.vcsState
931 if state != itm.vcsState:
932 itm.setVcsState(state)
933 index1 = self.createIndex(itm.row(), 0, itm)
934 index2 = self.createIndex(
935 itm.row(), self.rootItem.columnCount(), itm)
936 self.dataChanged.emit(index1, index2)
937 path, tail = os.path.split(path)
938
939 def preferencesChanged(self):
940 """
941 Public method used to handle a change in preferences.
942 """
943 for code in list(self.colorNames.keys()):
944 color = Preferences.getProjectBrowserColour(self.colorNames[code])
945 if color.name() == self.itemBackgroundColors[code].name():
946 continue
947
948 self.itemBackgroundColors[code] = color
949
950 color = Preferences.getProjectBrowserColour("Highlighted")
951 if self.highLightColor.name() != color.name():
952 self.highLightColor = color

eric ide

mercurial