fetch: add support for the traditional FETCH_HEAD behavior
[git-cola.git] / cola / models / selection.py
blob6207fe893684808c647d7690e7ad24ad70b562da
1 """Provides a selection model to handle selection."""
2 import collections
4 from qtpy import QtCore
5 from qtpy.QtCore import Signal
7 State = collections.namedtuple('State', 'staged unmerged modified untracked')
10 def create():
11 """Create a SelectionModel"""
12 return SelectionModel()
15 def pick(selection):
16 """Choose the first list from stage, unmerged, modified, untracked"""
17 if selection.staged:
18 files = selection.staged
19 elif selection.unmerged:
20 files = selection.unmerged
21 elif selection.modified:
22 files = selection.modified
23 elif selection.untracked:
24 files = selection.untracked
25 else:
26 files = []
27 return files
30 def union(selection):
31 """Return the union of all selected items in a sorted list"""
32 values = set(
33 selection.staged + selection.unmerged + selection.modified + selection.untracked
35 return list(sorted(values))
38 def _filter(values, remove):
39 """Filter a list in-place by removing items"""
40 remove_set = set(remove)
41 values_copy = list(values)
42 last = len(values_copy) - 1
43 for idx, value in enumerate(reversed(values)):
44 if value not in remove_set:
45 values.pop(last - idx)
48 class SelectionModel(QtCore.QObject):
49 """Provides information about selected file paths."""
51 selection_changed = Signal()
53 # These properties wrap the individual selection items
54 # to provide higher-level pseudo-selections.
55 unstaged = property(lambda self: self.unmerged + self.modified + self.untracked)
57 def __init__(self):
58 super().__init__()
59 self.staged = []
60 self.unmerged = []
61 self.modified = []
62 self.untracked = []
63 self.line_number = None
65 def reset(self, emit=False):
66 self.staged = []
67 self.unmerged = []
68 self.modified = []
69 self.untracked = []
70 self.line_number = None
71 if emit:
72 self.selection_changed.emit()
74 def is_empty(self):
75 return not (
76 bool(self.staged or self.unmerged or self.modified or self.untracked)
79 def set_selection(self, s):
80 """Set the new selection."""
81 self.staged = s.staged
82 self.unmerged = s.unmerged
83 self.modified = s.modified
84 self.untracked = s.untracked
85 self.selection_changed.emit()
87 def update(self, other):
88 _filter(self.staged, other.staged)
89 _filter(self.unmerged, other.unmerged)
90 _filter(self.modified, other.modified)
91 _filter(self.untracked, other.untracked)
92 self.selection_changed.emit()
94 def selection(self):
95 return State(self.staged, self.unmerged, self.modified, self.untracked)
97 def single_selection(self):
98 """Scan across staged, modified, etc. and return a single item."""
99 staged = None
100 modified = None
101 unmerged = None
102 untracked = None
103 if self.staged:
104 staged = self.staged[0]
105 elif self.modified:
106 modified = self.modified[0]
107 elif self.unmerged:
108 unmerged = self.unmerged[0]
109 elif self.untracked:
110 untracked = self.untracked[0]
111 return State(staged, unmerged, modified, untracked)
113 def filename(self):
114 paths = [path for path in self.single_selection() if path is not None]
115 if paths:
116 filename = paths[0]
117 else:
118 filename = None
119 return filename
121 def group(self):
122 """A list of selected files in various states of being"""
123 return pick(self.selection())
125 def union(self):
126 """Return the union of all selected items in a sorted list"""
127 return union(self)