Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: 

 

# Copyright 2017-2018 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net> 

# 

# This file is part of qutebrowser. 

# 

# qutebrowser is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# qutebrowser is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. 

 

"""A model that proxies access to one or more completion categories.""" 

 

from PyQt5.QtCore import Qt, QModelIndex, QAbstractItemModel 

 

from qutebrowser.utils import log, qtutils 

from qutebrowser.commands import cmdexc 

 

 

class CompletionModel(QAbstractItemModel): 

 

"""A model that proxies access to one or more completion categories. 

 

Top level indices represent categories. 

Child indices represent rows of those tables. 

 

Attributes: 

column_widths: The width percentages of the columns used in the 

completion view. 

_categories: The sub-categories. 

""" 

 

def __init__(self, *, column_widths=(30, 70, 0), parent=None): 

super().__init__(parent) 

self.column_widths = column_widths 

self._categories = [] 

 

def _cat_from_idx(self, index): 

"""Return the category pointed to by the given index. 

 

Args: 

idx: A QModelIndex 

Returns: 

A category if the index points at one, else None 

""" 

# items hold an index to the parent category in their internalPointer 

# categories have an empty internalPointer 

if index.isValid() and not index.internalPointer(): 

return self._categories[index.row()] 

return None 

 

def add_category(self, cat): 

"""Add a completion category to the model.""" 

self._categories.append(cat) 

 

def data(self, index, role=Qt.DisplayRole): 

"""Return the item data for index. 

 

Override QAbstractItemModel::data. 

 

Args: 

index: The QModelIndex to get item flags for. 

 

Return: The item data, or None on an invalid index. 

""" 

if role != Qt.DisplayRole: 

return None 

cat = self._cat_from_idx(index) 

if cat: 

# category header 

if index.column() == 0: 

return self._categories[index.row()].name 

return None 

# item 

cat = self._cat_from_idx(index.parent()) 

if not cat: 

return None 

idx = cat.index(index.row(), index.column()) 

return cat.data(idx) 

 

def flags(self, index): 

"""Return the item flags for index. 

 

Override QAbstractItemModel::flags. 

 

Return: The item flags, or Qt.NoItemFlags on error. 

""" 

if not index.isValid(): 

return Qt.NoItemFlags 

if index.parent().isValid(): 

# item 

return (Qt.ItemIsEnabled | Qt.ItemIsSelectable | 

Qt.ItemNeverHasChildren) 

else: 

# category 

return Qt.NoItemFlags 

 

def index(self, row, col, parent=QModelIndex()): 

"""Get an index into the model. 

 

Override QAbstractItemModel::index. 

 

Return: A QModelIndex. 

""" 

if (row < 0 or row >= self.rowCount(parent) or 

col < 0 or col >= self.columnCount(parent)): 

return QModelIndex() 

if parent.isValid(): 

117 ↛ 118line 117 didn't jump to line 118, because the condition on line 117 was never true if parent.column() != 0: 

return QModelIndex() 

# store a pointer to the parent category in internalPointer 

return self.createIndex(row, col, self._categories[parent.row()]) 

return self.createIndex(row, col, None) 

 

def parent(self, index): 

"""Get an index to the parent of the given index. 

 

Override QAbstractItemModel::parent. 

 

Args: 

index: The QModelIndex to get the parent index for. 

""" 

parent_cat = index.internalPointer() 

if not parent_cat: 

# categories have no parent 

return QModelIndex() 

row = self._categories.index(parent_cat) 

return self.createIndex(row, 0, None) 

 

def rowCount(self, parent=QModelIndex()): 

"""Override QAbstractItemModel::rowCount.""" 

if not parent.isValid(): 

# top-level 

return len(self._categories) 

cat = self._cat_from_idx(parent) 

if not cat or parent.column() != 0: 

# item or nonzero category column (only first col has children) 

return 0 

else: 

# category 

return cat.rowCount() 

 

def columnCount(self, parent=QModelIndex()): 

"""Override QAbstractItemModel::columnCount.""" 

# pylint: disable=unused-argument 

return 3 

 

def canFetchMore(self, parent): 

"""Override to forward the call to the categories.""" 

cat = self._cat_from_idx(parent) 

if cat: 

return cat.canFetchMore(QModelIndex()) 

return False 

 

def fetchMore(self, parent): 

"""Override to forward the call to the categories.""" 

cat = self._cat_from_idx(parent) 

if cat: 

cat.fetchMore(QModelIndex()) 

 

def count(self): 

"""Return the count of non-category items.""" 

return sum(t.rowCount() for t in self._categories) 

 

def set_pattern(self, pattern): 

"""Set the filter pattern for all categories. 

 

Args: 

pattern: The filter pattern to set. 

""" 

log.completion.debug("Setting completion pattern '{}'".format(pattern)) 

# WORKAROUND: 

# layoutChanged is broken in PyQt 5.7.1, so we must use metaObject 

# https://www.riverbankcomputing.com/pipermail/pyqt/2017-January/038483.html 

self.metaObject().invokeMethod(self, "layoutAboutToBeChanged") 

for cat in self._categories: 

cat.set_pattern(pattern) 

self.metaObject().invokeMethod(self, "layoutChanged") 

 

def first_item(self): 

"""Return the index of the first child (non-category) in the model.""" 

for row, cat in enumerate(self._categories): 

if cat.rowCount() > 0: 

parent = self.index(row, 0) 

index = self.index(0, 0, parent) 

qtutils.ensure_valid(index) 

return index 

return QModelIndex() 

 

def last_item(self): 

"""Return the index of the last child (non-category) in the model.""" 

for row, cat in reversed(list(enumerate(self._categories))): 

childcount = cat.rowCount() 

if childcount > 0: 

parent = self.index(row, 0) 

index = self.index(childcount - 1, 0, parent) 

qtutils.ensure_valid(index) 

return index 

return QModelIndex() 

 

def columns_to_filter(self, index): 

"""Return the column indices the filter pattern applies to. 

 

Args: 

index: index of the item to check. 

 

Return: A list of integers. 

""" 

cat = self._cat_from_idx(index.parent()) 

return cat.columns_to_filter if cat else [] 

 

def delete_cur_item(self, index): 

"""Delete the row at the given index.""" 

qtutils.ensure_valid(index) 

parent = index.parent() 

cat = self._cat_from_idx(parent) 

assert cat, "CompletionView sent invalid index for deletion" 

if not cat.delete_func: 

raise cmdexc.CommandError("Cannot delete this item.") 

 

data = [cat.data(cat.index(index.row(), i)) 

for i in range(cat.columnCount())] 

cat.delete_func(data) 

 

self.beginRemoveRows(parent, index.row(), index.row()) 

cat.removeRow(index.row(), QModelIndex()) 

self.endRemoveRows()