1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6from .qt_compat import qc, qg, qw, QPixmapCache 

7 

8from .util import EventMarker, PhaseMarker, make_QPolygonF 

9from pyrocko.plot.beachball import mt2beachball, BeachballError 

10from pyrocko.moment_tensor import kagan_angle 

11from pyrocko.plot import tango_colors 

12from pyrocko import orthodrome 

13from pyrocko.util import time_to_str 

14 

15import logging 

16 

17from .marker import g_color_b 

18 

19 

20def faint(c): 

21 return tuple(255 - (255 - x) // 5 for x in c) 

22 

23 

24g_color_b_faint = [faint(c) for c in g_color_b] 

25 

26 

27def noop(x=None): 

28 return x 

29 

30 

31qc.QString = str 

32qc.QVariant = noop 

33 

34 

35def toFloat(val): 

36 try: 

37 return float(val), True 

38 except (ValueError, TypeError): 

39 return 9e99, False 

40 

41 

42logger = logging.getLogger('pyrocko.gui.marker_editor') 

43 

44_header_data = [ 

45 'T', 'Time', 'M', 'Label', 'Depth [km]', 'Lat', 'Lon', 'Kind', 'Dist [km]', 

46 'NSLCs', 'Polarity', 'Kagan Angle [deg]', 'Event Hash', 'MT'] 

47 

48_column_mapping = dict(zip(_header_data, range(len(_header_data)))) 

49 

50_string_header = (_column_mapping['Time'], _column_mapping['Label']) 

51 

52_header_sizes = [70] * len(_header_data) 

53_header_sizes[0] = 40 

54_header_sizes[1] = 190 

55_header_sizes[-1] = 20 

56 

57 

58class BeachballWidget(qw.QWidget): 

59 

60 def __init__(self, moment_tensor, color, *args, **kwargs): 

61 qw.QWidget.__init__(self, *args, **kwargs) 

62 self.color = color 

63 self.moment_tensor = moment_tensor 

64 self.setGeometry(0, 0, 100, 100) 

65 self.setAttribute(qc.Qt.WA_TranslucentBackground) 

66 

67 self.flipy = qg.QTransform() 

68 self.flipy.translate(0, self.height()) 

69 self.flipy.scale(1, -1) 

70 

71 def paintEvent(self, e): 

72 center = e.rect().center() 

73 painter = qg.QPainter(self) 

74 painter.save() 

75 painter.setWorldTransform(self.flipy) 

76 try: 

77 data = mt2beachball( 

78 self.moment_tensor, size=self.height()/2.2, 

79 position=(center.x(), center.y()), 

80 color_t=self.color, color_p=qc.Qt.white, edgecolor=qc.Qt.black) 

81 for pdata in data: 

82 paths, fill, edges, thickness = pdata 

83 

84 pen = qg.QPen(edges) 

85 pen.setWidthF(3) 

86 if fill != 'none': 

87 brush = qg.QBrush(fill) 

88 painter.setBrush(brush) 

89 

90 polygon = qg.QPolygonF() 

91 polygon = make_QPolygonF(*paths.T) 

92 painter.setRenderHint(qg.QPainter.Antialiasing) 

93 painter.setPen(pen) 

94 painter.drawPolygon(polygon) 

95 except BeachballError as e: 

96 logger.exception(e) 

97 finally: 

98 painter.restore() 

99 

100 def to_qpixmap(self): 

101 try: 

102 return self.grab(self.rect()) 

103 except AttributeError: 

104 return qg.QPixmap().grabWidget(self, self.rect()) 

105 

106 

107class MarkerItemDelegate(qw.QStyledItemDelegate): 

108 

109 # Takes care of how table entries are displayed 

110 

111 def __init__(self, *args, **kwargs): 

112 qw.QStyledItemDelegate.__init__(self, *args, **kwargs) 

113 self.c_alignment = qc.Qt.AlignHCenter 

114 self.bbcache = QPixmapCache() 

115 

116 def paint(self, painter, option, index): 

117 mcolor = self.color_from_index(index) 

118 

119 if index.column() == _column_mapping['MT']: 

120 mt = self.bb_data_from_index(index) 

121 if mt: 

122 key = ''.join([str(round(x, 1)) for x in mt.m6()]) 

123 pixmap = self.bbcache.cached(key) 

124 if pixmap: 

125 pixmap = pixmap.scaledToHeight(option.rect.height()) 

126 else: 

127 pixmap = BeachballWidget( 

128 moment_tensor=mt, 

129 color=qg.QColor(*tango_colors['scarletred3']) 

130 ).to_qpixmap() 

131 self.bbcache.insert(key, pixmap) 

132 a, b, c, d = option.rect.getRect() 

133 painter.save() 

134 painter.setRenderHint(qg.QPainter.Antialiasing) 

135 painter.drawPixmap(a+d/2., b, d, d, pixmap) 

136 painter.restore() 

137 

138 else: 

139 if index.column() == 0: 

140 option.state = option.state & ~qw.QStyle.State_Selected 

141 

142 qw.QStyledItemDelegate.paint(self, painter, option, index) 

143 

144 marker = self.parent().model().get_marker(index) 

145 

146 if marker.active: 

147 

148 painter.save() 

149 

150 rect = option.rect 

151 x1, y1, x2, y2 = rect.getCoords() 

152 y1 += 1 

153 pen = painter.pen() 

154 pen.setWidth(2) 

155 pen.setColor(mcolor) 

156 painter.setPen(pen) 

157 painter.drawLine(qc.QLineF(x1, y1, x2, y1)) 

158 painter.drawLine(qc.QLineF(x1, y2, x2, y2)) 

159 painter.restore() 

160 

161 def marker_from_index(self, index): 

162 tv = self.parent() 

163 pv = tv.pile_viewer 

164 tvm = tv.model() 

165 if isinstance(tvm, qc.QSortFilterProxyModel): 

166 return pv.markers[tvm.mapToSource(index).row()] 

167 else: 

168 return pv.markers[index.row()] 

169 

170 def bb_data_from_index(self, index): 

171 marker = self.marker_from_index(index) 

172 if isinstance(marker, EventMarker): 

173 return marker.get_event().moment_tensor 

174 else: 

175 return None 

176 

177 def color_from_index(self, index): 

178 marker = self.marker_from_index(index) 

179 return qg.QColor(*marker.select_color(g_color_b)) 

180 

181 

182class MarkerSortFilterProxyModel(qc.QSortFilterProxyModel): 

183 

184 # Proxy object between view and model to handle sorting 

185 

186 def __init__(self, *args, **kwargs): 

187 qc.QSortFilterProxyModel.__init__(self, *args, **kwargs) 

188 self.setSortRole(qc.Qt.UserRole) 

189 self.sort(1, qc.Qt.AscendingOrder) 

190 

191 def get_marker(self, index): 

192 return self.sourceModel().get_marker(self.mapToSource(index)) 

193 

194 

195class MarkerTableView(qw.QTableView): 

196 def __init__(self, *args, **kwargs): 

197 sortable = kwargs.pop('sortable', True) 

198 qw.QTableView.__init__(self, *args, **kwargs) 

199 self.setSelectionBehavior(qw.QAbstractItemView.SelectRows) 

200 self.setHorizontalScrollMode(qw.QAbstractItemView.ScrollPerPixel) 

201 self.setEditTriggers(qw.QAbstractItemView.DoubleClicked) 

202 self.setSortingEnabled(sortable) 

203 self.setStyleSheet( 

204 'QTableView{selection-background-color: \ 

205 rgba(130, 130, 130, 100% );}') 

206 

207 self.sortByColumn(1, qc.Qt.AscendingOrder) 

208 self.setAlternatingRowColors(True) 

209 

210 self.setShowGrid(False) 

211 self.verticalHeader().hide() 

212 self.pile_viewer = None 

213 

214 self.clicked.connect(self.table_clicked) 

215 self.doubleClicked.connect(self.table_double_clicked) 

216 

217 self.header_menu = qw.QMenu(self) 

218 

219 show_initially = ['Type', 'Time', 'Magnitude'] 

220 self.menu_labels = ['Type', 'Time', 'Magnitude', 'Label', 'Depth [km]', 

221 'Latitude/Longitude', 'Kind', 'Distance [km]', 

222 'NSLCs', 'Polarity', 'Kagan Angle [deg]', 

223 'Event Hash', 'MT'] 

224 

225 self.menu_items = dict(zip( 

226 self.menu_labels, [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13])) 

227 

228 self.editable_columns = [2, 3, 4, 5, 6, 7] 

229 

230 self.column_actions = {} 

231 for hd in self.menu_labels: 

232 a = qw.QAction(hd, self.header_menu) 

233 a.triggered.connect( 

234 self.toggle_columns) 

235 a.setCheckable(True) 

236 a.setChecked(hd in show_initially) 

237 self.header_menu.addAction(a) 

238 self.column_actions[hd] = a 

239 

240 a = qw.QAction('Numbering', self.header_menu) 

241 a.setCheckable(True) 

242 a.setChecked(False) 

243 a.triggered.connect( 

244 self.toggle_numbering) 

245 self.header_menu.addAction(a) 

246 

247 header = self.horizontalHeader() 

248 header.setContextMenuPolicy(qc.Qt.CustomContextMenu) 

249 header.customContextMenuRequested.connect( 

250 self.show_context_menu) 

251 

252 self.right_click_menu = qw.QMenu(self) 

253 print_action = qw.QAction('Print Table', self.right_click_menu) 

254 print_action.triggered.connect(self.print_menu) 

255 self.right_click_menu.addAction(print_action) 

256 

257 def wheelEvent(self, wheel_event): 

258 if wheel_event.modifiers() & qc.Qt.ControlModifier: 

259 height = self.rowAt(self.height()) 

260 ci = self.indexAt( 

261 qc.QPoint(self.viewport().rect().x(), height)) 

262 v = self.verticalHeader() 

263 

264 wheel_delta = wheel_event.angleDelta().y() 

265 

266 v.setDefaultSectionSize( 

267 max(12, v.defaultSectionSize()+wheel_delta//60)) 

268 self.scrollTo(ci) 

269 if v.isVisible(): 

270 self.toggle_numbering(False) 

271 self.toggle_numbering(True) 

272 

273 else: 

274 super(MarkerTableView, self).wheelEvent(wheel_event) 

275 

276 def set_viewer(self, viewer): 

277 ''' 

278 Set connected pile viewer and hook up signals. 

279 ''' 

280 

281 self.pile_viewer = viewer 

282 

283 def keyPressEvent(self, key_event): 

284 # Propagate key_event to pile_viewer, unless up/down pressed 

285 

286 if key_event.key() in [qc.Qt.Key_Up, qc.Qt.Key_Down]: 

287 qw.QTableView.keyPressEvent(self, key_event) 

288 self.pile_viewer.go_to_selection() 

289 else: 

290 self.pile_viewer.keyPressEvent(key_event) 

291 

292 def table_clicked(self, model_index): 

293 # Ignore mouse clicks 

294 pass 

295 

296 def contextMenuEvent(self, event): 

297 self.right_click_menu.popup(qg.QCursor.pos()) 

298 

299 def toggle_numbering(self, want): 

300 if want: 

301 self.verticalHeader().show() 

302 else: 

303 self.verticalHeader().hide() 

304 

305 def print_menu(self): 

306 from .qt_compat import qprint 

307 printer = qprint.QPrinter(qprint.QPrinter.ScreenResolution) 

308 printer.setOutputFormat(qprint.QPrinter.NativeFormat) 

309 printer_dialog = qprint.QPrintDialog(printer, self) 

310 if printer_dialog.exec_() == qw.QDialog.Accepted: 

311 

312 scrollbarpolicy = self.verticalScrollBarPolicy() 

313 self.setVerticalScrollBarPolicy(qc.Qt.ScrollBarAlwaysOff) 

314 rect = printer.pageRect() 

315 painter = qg.QPainter() 

316 painter.begin(printer) 

317 xscale = rect.width() / (self.width()*1.1) 

318 yscale = rect.height() / (self.height() * 1.1) 

319 scale = min(xscale, yscale) 

320 painter.translate(rect.x() + rect.width()/2., 

321 rect.y() + rect.height()/2.) 

322 painter.scale(scale, scale) 

323 painter.translate(-self.width()/2., -self.height()/2.) 

324 painter.setRenderHints(qg.QPainter.HighQualityAntialiasing | 

325 qg.QPainter.TextAntialiasing) 

326 self.render(painter) 

327 painter.end() 

328 self.setVerticalScrollBarPolicy(scrollbarpolicy) 

329 

330 def table_double_clicked(self, model_index): 

331 if model_index.column() in self.editable_columns: 

332 return 

333 else: 

334 self.pile_viewer.go_to_selection() 

335 

336 def show_context_menu(self, point): 

337 ''' 

338 Pop-up menu to toggle column visibility. 

339 ''' 

340 

341 self.header_menu.popup(self.mapToGlobal(point)) 

342 

343 def toggle_columns(self): 

344 ''' 

345 Toggle columns depending in checked state. 

346 ''' 

347 

348 width = 0 

349 for header, ca in self.column_actions.items(): 

350 hide = not ca.isChecked() 

351 self.setColumnHidden(self.menu_items[header], hide) 

352 if header == 'Latitude/Longitude': 

353 self.setColumnHidden(self.menu_items[header]+1, hide) 

354 if not hide: 

355 width += _header_sizes[self.menu_labels.index(header)] 

356 

357 self.parent().setMinimumWidth(width) 

358 

359 def update_viewport(self): 

360 self.viewport().update() 

361 

362 

363class MarkerTableModel(qc.QAbstractTableModel): 

364 

365 def __init__(self, *args, **kwargs): 

366 qc.QAbstractTableModel.__init__(self, *args, **kwargs) 

367 self.pile_viewer = None 

368 self.distances = {} 

369 self.kagan_angles = {} 

370 self.row_count = 0 

371 self.proxy_filter = None 

372 

373 def sourceModel(self): 

374 return self 

375 

376 def set_viewer(self, viewer): 

377 ''' 

378 Set connected pile viewer and hook up signals. 

379 ''' 

380 

381 self.pile_viewer = viewer 

382 self.pile_viewer.begin_markers_add.connect( 

383 self.begin_markers_add) 

384 self.pile_viewer.end_markers_add.connect( 

385 self.end_markers_add) 

386 self.pile_viewer.begin_markers_remove.connect( 

387 self.begin_markers_remove) 

388 self.pile_viewer.end_markers_remove.connect( 

389 self.end_markers_remove) 

390 

391 def rowCount(self, parent=None): 

392 if not self.pile_viewer: 

393 return 0 

394 return len(self.pile_viewer.get_markers()) 

395 

396 def columnCount(self, parent=None): 

397 return len(_column_mapping) 

398 

399 def begin_markers_add(self, istart, istop): 

400 self.beginInsertRows(qc.QModelIndex(), istart, istop) 

401 

402 def end_markers_add(self): 

403 self.endInsertRows() 

404 

405 def begin_markers_remove(self, istart, istop): 

406 self.beginRemoveRows(qc.QModelIndex(), istart, istop) 

407 

408 def end_markers_remove(self): 

409 self.endRemoveRows() 

410 self.marker_table_view.updateGeometries() 

411 

412 def headerData(self, col, orientation, role): 

413 ''' 

414 Get header data entry. 

415 ''' 

416 

417 if orientation == qc.Qt.Horizontal: 

418 if role == qc.Qt.DisplayRole: 

419 return qc.QVariant(_header_data[col]) 

420 elif role == qc.Qt.SizeHintRole: 

421 return qc.QSize(10, 20) 

422 

423 elif orientation == qc.Qt.Vertical: 

424 if role == qc.Qt.DisplayRole: 

425 return qc.QVariant(str(col)) 

426 

427 else: 

428 return qc.QVariant() 

429 

430 def get_marker(self, index): 

431 return self.pile_viewer.markers[index.row()] 

432 

433 def data(self, index, role): 

434 ''' 

435 Get model data entry. 

436 ''' 

437 

438 if not self.pile_viewer: 

439 return qc.QVariant() 

440 

441 marker = self.pile_viewer.markers[index.row()] 

442 column = index.column() 

443 

444 if role == qc.Qt.BackgroundRole: 

445 if marker.active or column == _column_mapping['T']: 

446 return qg.QBrush( 

447 qg.QColor(*marker.select_color(g_color_b_faint))) 

448 

449 if role == qc.Qt.ForegroundRole: 

450 if marker.active or column == _column_mapping['T']: 

451 return qg.QBrush( 

452 qg.QColor(*marker.select_color(g_color_b))) 

453 

454 elif role in (qc.Qt.DisplayRole, qc.Qt.UserRole): 

455 

456 v = None 

457 if column == _column_mapping['Time']: 

458 if role == qc.Qt.UserRole: 

459 v = marker.tmin 

460 else: 

461 v = time_to_str(marker.tmin) 

462 

463 elif column == _column_mapping['T']: 

464 if isinstance(marker, EventMarker): 

465 v = u'\u25ce' 

466 elif isinstance(marker, PhaseMarker): 

467 v = marker.get_label() 

468 

469 elif column == _column_mapping['M']: 

470 if isinstance(marker, EventMarker): 

471 e = marker.get_event() 

472 if e.moment_tensor is not None: 

473 v = round(e.moment_tensor.magnitude, 1) 

474 elif e.magnitude is not None: 

475 v = round(e.magnitude, 1) 

476 

477 elif column == _column_mapping['Label']: 

478 if isinstance(marker, EventMarker): 

479 v = marker.label() 

480 elif isinstance(marker, PhaseMarker): 

481 v = marker.get_label() 

482 

483 elif column == _column_mapping['Depth [km]']: 

484 if isinstance(marker, EventMarker): 

485 d = marker.get_event().depth 

486 if d is not None: 

487 v = round(marker.get_event().depth/1000., 1) 

488 

489 elif column == _column_mapping['Lat']: 

490 if isinstance(marker, EventMarker): 

491 v = round(marker.get_event().effective_lat, 2) 

492 

493 elif column == _column_mapping['Lon']: 

494 if isinstance(marker, EventMarker): 

495 v = round(marker.get_event().effective_lon, 2) 

496 

497 elif column == _column_mapping['Kind']: 

498 v = marker.kind 

499 

500 elif column == _column_mapping['Dist [km]']: 

501 active_event = self.pile_viewer.get_active_event() 

502 if isinstance(marker, EventMarker) \ 

503 and active_event is not None: 

504 

505 dist = orthodrome.distance_accurate50m( 

506 marker.get_event(), 

507 active_event) 

508 

509 v = dist 

510 if role == qc.Qt.DisplayRole: 

511 v = '%.5g' % (v/1000.) 

512 

513 elif column == _column_mapping['NSLCs']: 

514 strs = [] 

515 for nslc_id in marker.get_nslc_ids(): 

516 strs.append('.'.join(nslc_id)) 

517 v = '|'.join(strs) 

518 

519 elif column == _column_mapping['Kagan Angle [deg]']: 

520 active_event = self.pile_viewer.get_active_event() 

521 if isinstance(marker, EventMarker) \ 

522 and active_event is not None \ 

523 and active_event.moment_tensor is not None \ 

524 and marker.get_event().moment_tensor is not None: 

525 

526 v = kagan_angle( 

527 active_event.moment_tensor, 

528 marker.get_event().moment_tensor) 

529 

530 if role == qc.Qt.DisplayRole: 

531 v = '%.1f' % v 

532 

533 elif column == _column_mapping['MT']: 

534 return qc.QVariant() 

535 

536 elif column == _column_mapping['Event Hash']: 

537 if isinstance(marker, (EventMarker, PhaseMarker)): 

538 v = marker.get_event_hash() 

539 else: 

540 return qc.QVariant() 

541 

542 elif column == _column_mapping['Polarity']: 

543 if isinstance(marker, (PhaseMarker)): 

544 v = marker.get_polarity_symbol() 

545 else: 

546 return qc.QVariant() 

547 

548 return qc.QVariant(v) 

549 

550 return qc.QVariant() 

551 

552 def handle_active_event_changed(self): 

553 nmarkers = self.rowCount() 

554 istart = self.index(0, _column_mapping['Dist [km]']) 

555 istop = self.index(nmarkers-1, _column_mapping['Dist [km]']) 

556 self.dataChanged.emit(istart, istop) 

557 

558 istart = self.index(0, _column_mapping['Kagan Angle [deg]']) 

559 istop = self.index(nmarkers-1, _column_mapping['Kagan Angle [deg]']) 

560 self.dataChanged.emit(istart, istop) 

561 

562 def done(self, index): 

563 self.dataChanged.emit(index, index) 

564 return True 

565 

566 def setData(self, index, value, role): 

567 ''' 

568 Set model data entry. 

569 ''' 

570 

571 if role == qc.Qt.EditRole: 

572 imarker = index.row() 

573 marker = self.pile_viewer.markers[imarker] 

574 if index.column() in [_column_mapping[c] for c in [ 

575 'M', 'Lat', 'Lon', 'Depth [km]']]: 

576 

577 if not isinstance(marker, EventMarker): 

578 return False 

579 else: 

580 if index.column() == _column_mapping['M']: 

581 valuef, valid = toFloat(value) 

582 if valid: 

583 e = marker.get_event() 

584 if e.moment_tensor is None: 

585 e.magnitude = valuef 

586 else: 

587 e.moment_tensor.magnitude = valuef 

588 return self.done(index) 

589 

590 if index.column() in [_column_mapping['Lon'], 

591 _column_mapping['Lat'], 

592 _column_mapping['Depth [km]']]: 

593 if isinstance(marker, EventMarker): 

594 valuef, valid = toFloat(value) 

595 if valid: 

596 if index.column() == _column_mapping['Lat']: 

597 marker.get_event().lat = valuef 

598 elif index.column() == _column_mapping['Lon']: 

599 marker.get_event().lon = valuef 

600 elif index.column() == _column_mapping[ 

601 'Depth [km]']: 

602 marker.get_event().depth = valuef*1000. 

603 return self.done(index) 

604 

605 if index.column() == _column_mapping['Label']: 

606 values = str(value) 

607 if values != '': 

608 if isinstance(marker, EventMarker): 

609 marker.get_event().set_name(values) 

610 return self.done(index) 

611 

612 if isinstance(marker, PhaseMarker): 

613 marker.set_phasename(values) 

614 return self.done(index) 

615 

616 return False 

617 

618 def flags(self, index): 

619 ''' 

620 Set flags for cells which the user can edit. 

621 ''' 

622 

623 if index.column() not in self.marker_table_view.editable_columns: 

624 return qc.Qt.ItemFlags(33) 

625 else: 

626 if isinstance(self.pile_viewer.markers[index.row()], EventMarker): 

627 if index.column() in self.marker_table_view.editable_columns: 

628 return qc.Qt.ItemFlags(35) 

629 if index.column() == _column_mapping['Label']: 

630 return qc.Qt.ItemFlags(35) 

631 return qc.Qt.ItemFlags(33) 

632 

633 

634class MarkerEditor(qw.QFrame): 

635 

636 def __init__(self, *args, **kwargs): 

637 sortable = kwargs.pop('sortable', True) 

638 qw.QFrame.__init__(self, *args, **kwargs) 

639 layout = qw.QGridLayout() 

640 layout.setContentsMargins(0, 0, 0, 0) 

641 self.setLayout(layout) 

642 self.marker_table_view = MarkerTableView(self, sortable=sortable) 

643 

644 self.delegate = MarkerItemDelegate(self.marker_table_view) 

645 self.marker_table_view.setItemDelegate(self.delegate) 

646 

647 self.marker_model = MarkerTableModel() 

648 self.marker_model.marker_table_view = self.marker_table_view 

649 

650 if sortable: 

651 self.proxy_filter = MarkerSortFilterProxyModel() 

652 self.proxy_filter.setDynamicSortFilter(True) 

653 self.proxy_filter.setSourceModel(self.marker_model) 

654 self.marker_model.proxy_filter = self.proxy_filter 

655 

656 self.marker_table_view.setModel(self.proxy_filter) 

657 else: 

658 self.proxy_filter = None 

659 self.marker_table_view.setModel(self.marker_model) 

660 

661 header = self.marker_table_view.horizontalHeader() 

662 for i_s, s in enumerate(_header_sizes): 

663 header.setSectionResizeMode(i_s, qw.QHeaderView.Interactive) 

664 header.resizeSection(i_s, s) 

665 

666 header.setStretchLastSection(True) 

667 

668 if self.proxy_filter: 

669 self.selection_model = qc.QItemSelectionModel(self.proxy_filter) 

670 else: 

671 self.selection_model = qc.QItemSelectionModel(self.marker_model) 

672 

673 self.marker_table_view.setSelectionModel(self.selection_model) 

674 self.selection_model.selectionChanged.connect( 

675 self.set_selected_markers) 

676 

677 layout.addWidget(self.marker_table_view, 0, 0) 

678 

679 self.pile_viewer = None 

680 self._size_hint = qc.QSize(1, 1) 

681 

682 def set_viewer(self, viewer): 

683 ''' 

684 Set the pile viewer and connect signals. 

685 ''' 

686 

687 self.pile_viewer = viewer 

688 self.marker_model.set_viewer(viewer) 

689 self.marker_table_view.set_viewer(viewer) 

690 self.pile_viewer.marker_selection_changed.connect( 

691 self.update_selection_model) 

692 

693 self.pile_viewer.active_event_marker_changed.connect( 

694 self.marker_model.handle_active_event_changed) 

695 

696 # self.pile_viewer.active_event_marker_changed.connect( 

697 # self.marker_table_view.update_viewport) 

698 

699 self.marker_table_view.toggle_columns() 

700 

701 def set_selected_markers(self, selected, deselected): 

702 ''' 

703 Update selection in viewer to reflect changes in table data. 

704 ''' 

705 

706 if self.proxy_filter: 

707 def to_source(x): 

708 ind = self.proxy_filter.index(x, 0) 

709 return self.proxy_filter.mapToSource(ind).row() 

710 else: 

711 def to_source(x): 

712 return x 

713 

714 markers = self.pile_viewer.markers 

715 

716 for rsel in selected: 

717 for i in range(rsel.top(), rsel.bottom()+1): 

718 marker = markers[to_source(i)] 

719 if not marker.selected: 

720 marker.selected = True 

721 self.pile_viewer.n_selected_markers += 1 

722 

723 for rsel in deselected: 

724 for i in range(rsel.top(), rsel.bottom()+1): 

725 marker = markers[to_source(i)] 

726 if marker.selected: 

727 marker.selected = False 

728 self.pile_viewer.n_selected_markers -= 1 

729 

730 self.pile_viewer.update() 

731 

732 def get_marker_model(self): 

733 ''' 

734 Get the attached Qt table data model. 

735 

736 :returns: :py:class:`MarkerTableModel` object 

737 ''' 

738 

739 return self.marker_model 

740 

741 def update_selection_model(self, indices): 

742 ''' 

743 Set currently selected table rows. 

744 

745 :param indices: begin and end+1 indices of contiguous selection chunks 

746 :type indices: list of tuples 

747 ''' 

748 self.selection_model.clearSelection() 

749 selections = qc.QItemSelection() 

750 selection_flags = qc.QItemSelectionModel.SelectionFlags(int( 

751 qc.QItemSelectionModel.Select | 

752 qc.QItemSelectionModel.Rows | 

753 qc.QItemSelectionModel.Current)) 

754 

755 for chunk in indices: 

756 mi_start = self.marker_model.index(chunk[0], 0) 

757 mi_stop = self.marker_model.index(chunk[1]-1, 0) 

758 if self.proxy_filter: 

759 row_selection = self.proxy_filter.mapSelectionFromSource( 

760 qc.QItemSelection(mi_start, mi_stop)) 

761 else: 

762 row_selection = qc.QItemSelection(mi_start, mi_stop) 

763 selections.merge(row_selection, selection_flags) 

764 

765 if len(indices) != 0: 

766 if self.proxy_filter: 

767 self.marker_table_view.scrollTo( 

768 self.proxy_filter.mapFromSource(mi_start)) 

769 else: 

770 self.marker_table_view.scrollTo(mi_start) 

771 

772 self.marker_table_view.setCurrentIndex(mi_start) 

773 self.selection_model.setCurrentIndex( 

774 mi_start, selection_flags) 

775 

776 self.selection_model.select(selections, selection_flags)