Coverage for /Users/Newville/Codes/xraylarch/larch/qtlib/view.py: 0%

175 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-09 10:08 -0600

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3"""This module provides base classes to implement models for 

4spectroscopy data. 

5""" 

6from __future__ import absolute_import, division 

7import os 

8 

9from silx.gui import qt 

10import silx.io 

11 

12from .items import (ExperimentItem, 

13 GroupItem, 

14 FileItem, 

15 ScanItem) 

16 

17from larch.utils import get_cwd 

18from larch.utils.logging import getLogger 

19_logger = getLogger('larch.qtlib.view') 

20 

21 

22class HorizontalHeaderView(qt.QHeaderView): 

23 

24 def __init__(self, parent=None): 

25 super(HorizontalHeaderView, self).__init__(qt.Qt.Horizontal, parent) 

26 

27 # Some properties 

28 self.setStretchLastSection(True) 

29 # self.setSectionsMovable(True) 

30 

31 # Context menu 

32 self.setContextMenuPolicy(qt.Qt.CustomContextMenu) 

33 self.customContextMenuRequested.connect(self.showContextMenu) 

34 

35 def showContextMenu(self, position): 

36 menu = qt.QMenu('Horizontal Header View Menu', self) 

37 

38 section = self.logicalIndexAt(position) 

39 

40 action = qt.QAction('Add', self, triggered=self.append) 

41 menu.addAction(action) 

42 

43 action = qt.QAction('Remove', self, 

44 triggered=lambda: self.remove(section)) 

45 menu.addAction(action) 

46 

47 menu.exec_(self.mapToGlobal(position)) 

48 

49 def append(self): 

50 pass 

51 

52 def remove(self, section): 

53 model = self.model() 

54 if not model.header[section].removable: 

55 _logger.info('The selected column cannot be removed') 

56 return 

57 model.setHeaderData(section, orientation=qt.Qt.Horizontal, value=None) 

58 view = self.parent() 

59 view.setItemsDelegates() 

60 

61 

62class TreeView(qt.QTreeView): 

63 

64 def __init__(self, parent=None): 

65 super(TreeView, self).__init__(parent) 

66 

67 # Header 

68 headerView = HorizontalHeaderView() 

69 self.setHeader(headerView) 

70 

71 # Context menu 

72 self.setContextMenuPolicy(qt.Qt.CustomContextMenu) 

73 self.customContextMenuRequested.connect(self.showContextMenu) 

74 

75 # Selection mode 

76 self.setSelectionMode(qt.QAbstractItemView.ExtendedSelection) 

77 

78 def setModel(self, model): 

79 super(TreeView, self).setModel(model) 

80 self.setItemsDelegates() 

81 

82 def setItemsDelegates(self): 

83 if self.model() is None: 

84 return 

85 

86 header = self.model().header 

87 for i, _ in enumerate(header): 

88 delegate = header.delegate(i) 

89 if delegate is not None: 

90 self.setItemDelegateForColumn(i, delegate(parent=self)) 

91 

92 def showContextMenu(self, position): 

93 menu = qt.QMenu('Tree View Menu', self) 

94 

95 action = qt.QAction( 

96 'Add Experiment', self, triggered=self.addExperiment) 

97 menu.addAction(action) 

98 

99 # Get the index under the cursor. 

100 index = self.indexAt(position) 

101 item = self.model().itemFromIndex(index) 

102 

103 if isinstance(item, ExperimentItem) or isinstance(item, GroupItem): 

104 action = qt.QAction('Add Group', self, triggered=self.addGroup) 

105 menu.addAction(action) 

106 

107 action = qt.QAction('Load Files', self, triggered=self.loadFiles) 

108 menu.addAction(action) 

109 

110 # If there are selected indexes, they can be removed or checked. 

111 if self.selectedIndexes(): 

112 menu.addSeparator() 

113 action = qt.QAction( 

114 'Toggle Selected', self, triggered=self.toggleSelected) 

115 menu.addAction(action) 

116 action = qt.QAction( 

117 'Remove Selected', self, triggered=self.removeSelected) 

118 menu.addAction(action) 

119 

120 if isinstance(item, ScanItem) and index.column() > 0: 

121 menu.addSeparator() 

122 action = qt.QAction( 

123 'Copy Value to Selected', self, 

124 triggered=lambda: self.copyValueToSelected(index)) 

125 menu.addAction(action) 

126 

127 action = qt.QAction( 

128 'Copy Value to Toggled', self, 

129 triggered=lambda: self.copyValueToToggled(index)) 

130 menu.addAction(action) 

131 

132 menu.exec_(self.mapToGlobal(position)) 

133 

134 def loadFiles(self): 

135 paths, _ = qt.QFileDialog.getOpenFileNames( 

136 self, 'Select Files to Load', get_cwd(), 

137 'Data Files (*.spec *.hdf5);; All Files (*)') 

138 

139 if not paths: 

140 return 

141 

142 parent = self.selectionModel().selectedRows().pop() 

143 parentItem = self.model().itemFromIndex(parent) 

144 for path in paths: 

145 self.addFile(path, parentItem) 

146 

147 def addExperiment(self, name=None): 

148 rootItem = self.model().rootItem 

149 row = rootItem.childCount() 

150 if name is None or not name: 

151 name = 'Experiment{}'.format(row) 

152 item = ExperimentItem(name=name, parentItem=rootItem) 

153 self.model().appendRow(item) 

154 return item 

155 

156 def addGroup(self, name=None, parentItem=None): 

157 """Add a generic GroupItem at a given parentItem""" 

158 # Add the file to the last added item. 

159 if parentItem is None: 

160 parent = self.selectionModel().selectedRows().pop() 

161 parentItem = self.model().itemFromIndex(parent) 

162 row = parentItem.childCount() 

163 

164 if name is None or not name: 

165 name = 'Group{}'.format(row) 

166 

167 item = GroupItem(name, parentItem) 

168 self.model().appendRow(item) 

169 

170 def addFile(self, path=None, parentItem=None): 

171 if path is None: 

172 return 

173 

174 # Add the file to the last added experiment item. 

175 if parentItem is None: 

176 parentItem = self.model().rootItem.lastChild() 

177 

178 try: 

179 data = silx.io.open(path) 

180 except OSError as e: 

181 _logger.warning(e) 

182 return 

183 

184 # Create a tree item for the file and add it to the experiment item. 

185 name, _ = os.path.splitext(os.path.basename(path)) 

186 item = FileItem(name, parentItem) 

187 self.model().appendRow(item) 

188 

189 # Create a tree item for each scan. The parent item is now the 

190 # previous file item. 

191 # TODO: Make this more "intelligent" by using the command to 

192 # set better defaults for x, signal, etc. 

193 parentItem = item 

194 for scan in data: 

195 item = ScanItem(name=scan, parentItem=parentItem, data=data[scan]) 

196 self.model().appendRow(item) 

197 

198 def selectedItems(self): 

199 indexes = self.selectionModel().selectedRows() 

200 items = [self.model().itemFromIndex(index) for index in indexes] 

201 return items 

202 

203 def scanItems(self): 

204 for index in self.model().visitModel(): 

205 item = self.model().itemFromIndex(index) 

206 if isinstance(item, ScanItem): 

207 yield item 

208 

209 def toggleSelected(self): 

210 for item in self.selectedItems(): 

211 index = self.model().indexFromItem(item) 

212 try: 

213 if item.isChecked: 

214 self.model().setData( 

215 index, qt.Qt.Unchecked, qt.Qt.CheckStateRole) 

216 else: 

217 self.model().setData( 

218 index, qt.Qt.Checked, qt.Qt.CheckStateRole) 

219 except AttributeError: 

220 pass 

221 

222 def removeSelected(self): 

223 items = self.selectedItems() 

224 parentItems = dict() 

225 for item in items: 

226 parentItem = item.parentItem 

227 remove = True 

228 while parentItem is not self.model().rootItem: 

229 if parentItem in items: 

230 remove = False 

231 break 

232 parentItem = parentItem.parentItem 

233 # If an ancestors is selected for removal, pass the item. 

234 if not remove: 

235 continue 

236 

237 # Get the parent item for the current item. 

238 parentItem = item.parentItem 

239 if parentItem not in parentItems: 

240 parentItems[parentItem] = list() 

241 # Create a list with the positions of the children that are 

242 # going to be removed. 

243 parentItems[parentItem].append(item.childPosition()) 

244 

245 # Remove the rows from the parent. 

246 for parentItem in parentItems: 

247 rows = parentItems[parentItem] 

248 parent = self.model().indexFromItem(parentItem) 

249 for row in reversed(sorted(rows)): 

250 self.model().removeRow(row, parent) 

251 

252 def copyValueToToggled(self, indexAt): 

253 """Copy the value under the cursor to the toggled items.""" 

254 indexes = self.model().visitModel(columns=True) 

255 for index in indexes: 

256 item = self.model().itemFromIndex(index) 

257 if not item.isChecked or index == indexAt: 

258 continue 

259 elif index.column() == indexAt.column(): 

260 value = self.model().data(indexAt) 

261 self.model().setData(index, value) 

262 

263 def copyValueToSelected(self, indexAt): 

264 """Copy the value under the cursor to the selected indexes.""" 

265 indexes = self.selectionModel().selectedIndexes() 

266 for index in indexes: 

267 if index == indexAt: 

268 continue 

269 elif index.column() == indexAt.column(): 

270 value = self.model().data(indexAt) 

271 self.model().setData(index, value)