1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5from __future__ import absolute_import
7import sys
9from .qt_compat import qc, qg, qw, QSortFilterProxyModel, \
10 QItemSelectionModel, QItemSelection, QPixmapCache, use_pyqt5
12from .util import EventMarker, PhaseMarker, make_QPolygonF
13from pyrocko.plot.beachball import mt2beachball, BeachballError
14from pyrocko.moment_tensor import kagan_angle
15from pyrocko.plot import tango_colors
16from pyrocko import orthodrome
17from pyrocko.util import time_to_str
19import logging
21from .marker import g_color_b
24def faint(c):
25 return tuple(255 - (255 - x) // 5 for x in c)
28g_color_b_faint = [faint(c) for c in g_color_b]
31def noop(x=None):
32 return x
35if sys.version_info[0] >= 3 or use_pyqt5:
36 qc.QString = str
37 qc.QVariant = noop
39 def toFloat(val):
40 try:
41 return float(val), True
42 except (ValueError, TypeError):
43 return 9e99, False
45else:
46 def toFloat(val):
47 return val.toFloat()
50logger = logging.getLogger('pyrocko.gui.marker_editor')
52_header_data = [
53 'T', 'Time', 'M', 'Label', 'Depth [km]', 'Lat', 'Lon', 'Kind', 'Dist [km]',
54 'NSLCs', 'Polarity', 'Kagan Angle [deg]', 'Event Hash', 'MT']
56_column_mapping = dict(zip(_header_data, range(len(_header_data))))
58_string_header = (_column_mapping['Time'], _column_mapping['Label'])
60_header_sizes = [70] * len(_header_data)
61_header_sizes[0] = 40
62_header_sizes[1] = 190
63_header_sizes[-1] = 20
66class BeachballWidget(qw.QWidget):
68 def __init__(self, moment_tensor, color, *args, **kwargs):
69 qw.QWidget.__init__(self, *args, **kwargs)
70 self.color = color
71 self.moment_tensor = moment_tensor
72 self.setGeometry(0, 0, 100, 100)
73 self.setAttribute(qc.Qt.WA_TranslucentBackground)
75 self.flipy = qg.QTransform()
76 self.flipy.translate(0, self.height())
77 self.flipy.scale(1, -1)
79 def paintEvent(self, e):
80 center = e.rect().center()
81 painter = qg.QPainter(self)
82 painter.save()
83 painter.setWorldTransform(self.flipy)
84 try:
85 data = mt2beachball(
86 self.moment_tensor, size=self.height()/2.2,
87 position=(center.x(), center.y()),
88 color_t=self.color, color_p=qc.Qt.white, edgecolor=qc.Qt.black)
89 for pdata in data:
90 paths, fill, edges, thickness = pdata
92 pen = qg.QPen(edges)
93 pen.setWidthF(3)
94 if fill != 'none':
95 brush = qg.QBrush(fill)
96 painter.setBrush(brush)
98 polygon = qg.QPolygonF()
99 polygon = make_QPolygonF(*paths.T)
100 painter.setRenderHint(qg.QPainter.Antialiasing)
101 painter.setPen(pen)
102 painter.drawPolygon(polygon)
103 except BeachballError as e:
104 logger.exception(e)
105 finally:
106 painter.restore()
108 def to_qpixmap(self):
109 try:
110 return self.grab(self.rect())
111 except AttributeError:
112 return qg.QPixmap().grabWidget(self, self.rect())
115class MarkerItemDelegate(qw.QStyledItemDelegate):
117 # Takes care of how table entries are displayed
119 def __init__(self, *args, **kwargs):
120 qw.QStyledItemDelegate.__init__(self, *args, **kwargs)
121 self.c_alignment = qc.Qt.AlignHCenter
122 self.bbcache = QPixmapCache()
124 def paint(self, painter, option, index):
125 mcolor = self.color_from_index(index)
127 if index.column() == _column_mapping['MT']:
128 mt = self.bb_data_from_index(index)
129 if mt:
130 key = ''.join([str(round(x, 1)) for x in mt.m6()])
131 pixmap = self.bbcache.cached(key)
132 if pixmap:
133 pixmap = pixmap.scaledToHeight(option.rect.height())
134 else:
135 pixmap = BeachballWidget(
136 moment_tensor=mt,
137 color=qg.QColor(*tango_colors['scarletred3'])
138 ).to_qpixmap()
139 self.bbcache.insert(key, pixmap)
140 a, b, c, d = option.rect.getRect()
141 painter.save()
142 painter.setRenderHint(qg.QPainter.Antialiasing)
143 painter.drawPixmap(a+d/2., b, d, d, pixmap)
144 painter.restore()
146 else:
147 if index.column() == 0:
148 option.state = option.state & ~qw.QStyle.State_Selected
150 qw.QStyledItemDelegate.paint(self, painter, option, index)
152 marker = self.parent().model().get_marker(index)
154 if marker.active:
156 painter.save()
158 rect = option.rect
159 x1, y1, x2, y2 = rect.getCoords()
160 y1 += 1
161 pen = painter.pen()
162 pen.setWidth(2)
163 pen.setColor(mcolor)
164 painter.setPen(pen)
165 painter.drawLine(qc.QLineF(x1, y1, x2, y1))
166 painter.drawLine(qc.QLineF(x1, y2, x2, y2))
167 painter.restore()
169 def marker_from_index(self, index):
170 tv = self.parent()
171 pv = tv.pile_viewer
172 tvm = tv.model()
173 if isinstance(tvm, QSortFilterProxyModel):
174 return pv.markers[tvm.mapToSource(index).row()]
175 else:
176 return pv.markers[index.row()]
178 def bb_data_from_index(self, index):
179 marker = self.marker_from_index(index)
180 if isinstance(marker, EventMarker):
181 return marker.get_event().moment_tensor
182 else:
183 return None
185 def color_from_index(self, index):
186 marker = self.marker_from_index(index)
187 return qg.QColor(*marker.select_color(g_color_b))
190class MarkerSortFilterProxyModel(QSortFilterProxyModel):
192 # Proxy object between view and model to handle sorting
194 def __init__(self, *args, **kwargs):
195 QSortFilterProxyModel.__init__(self, *args, **kwargs)
196 self.setSortRole(qc.Qt.UserRole)
197 self.sort(1, qc.Qt.AscendingOrder)
199 def get_marker(self, index):
200 return self.sourceModel().get_marker(self.mapToSource(index))
203class MarkerTableView(qw.QTableView):
204 def __init__(self, *args, **kwargs):
205 sortable = kwargs.pop('sortable', True)
206 qw.QTableView.__init__(self, *args, **kwargs)
207 self.setSelectionBehavior(qw.QAbstractItemView.SelectRows)
208 self.setHorizontalScrollMode(qw.QAbstractItemView.ScrollPerPixel)
209 self.setEditTriggers(qw.QAbstractItemView.DoubleClicked)
210 self.setSortingEnabled(sortable)
211 self.setStyleSheet(
212 'QTableView{selection-background-color: \
213 rgba(130, 130, 130, 100% );}')
215 self.sortByColumn(1, qc.Qt.AscendingOrder)
216 self.setAlternatingRowColors(True)
218 self.setShowGrid(False)
219 self.verticalHeader().hide()
220 self.pile_viewer = None
222 self.clicked.connect(self.table_clicked)
223 self.doubleClicked.connect(self.table_double_clicked)
225 self.header_menu = qw.QMenu(self)
227 show_initially = ['Type', 'Time', 'Magnitude']
228 self.menu_labels = ['Type', 'Time', 'Magnitude', 'Label', 'Depth [km]',
229 'Latitude/Longitude', 'Kind', 'Distance [km]',
230 'NSLCs', 'Polarity', 'Kagan Angle [deg]',
231 'Event Hash', 'MT']
233 self.menu_items = dict(zip(
234 self.menu_labels, [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13]))
236 self.editable_columns = [2, 3, 4, 5, 6, 7]
238 self.column_actions = {}
239 for hd in self.menu_labels:
240 a = qw.QAction(hd, self.header_menu)
241 a.triggered.connect(
242 self.toggle_columns)
243 a.setCheckable(True)
244 a.setChecked(hd in show_initially)
245 self.header_menu.addAction(a)
246 self.column_actions[hd] = a
248 a = qw.QAction('Numbering', self.header_menu)
249 a.setCheckable(True)
250 a.setChecked(False)
251 a.triggered.connect(
252 self.toggle_numbering)
253 self.header_menu.addAction(a)
255 header = self.horizontalHeader()
256 header.setContextMenuPolicy(qc.Qt.CustomContextMenu)
257 header.customContextMenuRequested.connect(
258 self.show_context_menu)
260 self.right_click_menu = qw.QMenu(self)
261 print_action = qw.QAction('Print Table', self.right_click_menu)
262 print_action.triggered.connect(self.print_menu)
263 self.right_click_menu.addAction(print_action)
265 def wheelEvent(self, wheel_event):
266 if wheel_event.modifiers() & qc.Qt.ControlModifier:
267 height = self.rowAt(self.height())
268 ci = self.indexAt(
269 qc.QPoint(self.viewport().rect().x(), height))
270 v = self.verticalHeader()
272 if use_pyqt5:
273 wheel_delta = wheel_event.angleDelta().y()
274 else:
275 wheel_delta = wheel_event.delta()
277 v.setDefaultSectionSize(
278 max(12, v.defaultSectionSize()+wheel_delta//60))
279 self.scrollTo(ci)
280 if v.isVisible():
281 self.toggle_numbering(False)
282 self.toggle_numbering(True)
284 else:
285 super(MarkerTableView, self).wheelEvent(wheel_event)
287 def set_viewer(self, viewer):
288 '''
289 Set connected pile viewer and hook up signals.
290 '''
292 self.pile_viewer = viewer
294 def keyPressEvent(self, key_event):
295 # Propagate key_event to pile_viewer, unless up/down pressed
297 if key_event.key() in [qc.Qt.Key_Up, qc.Qt.Key_Down]:
298 qw.QTableView.keyPressEvent(self, key_event)
299 self.pile_viewer.go_to_selection()
300 else:
301 self.pile_viewer.keyPressEvent(key_event)
303 def table_clicked(self, model_index):
304 # Ignore mouse clicks
305 pass
307 def contextMenuEvent(self, event):
308 self.right_click_menu.popup(qg.QCursor.pos())
310 def toggle_numbering(self, want):
311 if want:
312 self.verticalHeader().show()
313 else:
314 self.verticalHeader().hide()
316 def print_menu(self):
317 from .qt_compat import qprint
318 printer = qprint.QPrinter(qprint.QPrinter.ScreenResolution)
319 printer.setOutputFormat(qprint.QPrinter.NativeFormat)
320 printer_dialog = qprint.QPrintDialog(printer, self)
321 if printer_dialog.exec_() == qw.QDialog.Accepted:
323 scrollbarpolicy = self.verticalScrollBarPolicy()
324 self.setVerticalScrollBarPolicy(qc.Qt.ScrollBarAlwaysOff)
325 rect = printer.pageRect()
326 painter = qg.QPainter()
327 painter.begin(printer)
328 xscale = rect.width() / (self.width()*1.1)
329 yscale = rect.height() / (self.height() * 1.1)
330 scale = min(xscale, yscale)
331 painter.translate(rect.x() + rect.width()/2.,
332 rect.y() + rect.height()/2.)
333 painter.scale(scale, scale)
334 painter.translate(-self.width()/2., -self.height()/2.)
335 painter.setRenderHints(qg.QPainter.HighQualityAntialiasing |
336 qg.QPainter.TextAntialiasing)
337 self.render(painter)
338 painter.end()
339 self.setVerticalScrollBarPolicy(scrollbarpolicy)
341 def table_double_clicked(self, model_index):
342 if model_index.column() in self.editable_columns:
343 return
344 else:
345 self.pile_viewer.go_to_selection()
347 def show_context_menu(self, point):
348 '''
349 Pop-up menu to toggle column visibility.
350 '''
352 self.header_menu.popup(self.mapToGlobal(point))
354 def toggle_columns(self):
355 '''
356 Toggle columns depending in checked state.
357 '''
359 width = 0
360 for header, ca in self.column_actions.items():
361 hide = not ca.isChecked()
362 self.setColumnHidden(self.menu_items[header], hide)
363 if header == 'Latitude/Longitude':
364 self.setColumnHidden(self.menu_items[header]+1, hide)
365 if not hide:
366 width += _header_sizes[self.menu_labels.index(header)]
368 self.parent().setMinimumWidth(width)
370 def update_viewport(self):
371 self.viewport().update()
374class MarkerTableModel(qc.QAbstractTableModel):
376 def __init__(self, *args, **kwargs):
377 qc.QAbstractTableModel.__init__(self, *args, **kwargs)
378 self.pile_viewer = None
379 self.distances = {}
380 self.kagan_angles = {}
381 self.row_count = 0
382 self.proxy_filter = None
384 def sourceModel(self):
385 return self
387 def set_viewer(self, viewer):
388 '''
389 Set connected pile viewer and hook up signals.
390 '''
392 self.pile_viewer = viewer
393 self.pile_viewer.begin_markers_add.connect(
394 self.begin_markers_add)
395 self.pile_viewer.end_markers_add.connect(
396 self.end_markers_add)
397 self.pile_viewer.begin_markers_remove.connect(
398 self.begin_markers_remove)
399 self.pile_viewer.end_markers_remove.connect(
400 self.end_markers_remove)
402 def rowCount(self, parent=None):
403 if not self.pile_viewer:
404 return 0
405 return len(self.pile_viewer.get_markers())
407 def columnCount(self, parent=None):
408 return len(_column_mapping)
410 def begin_markers_add(self, istart, istop):
411 self.beginInsertRows(qc.QModelIndex(), istart, istop)
413 def end_markers_add(self):
414 self.endInsertRows()
416 def begin_markers_remove(self, istart, istop):
417 self.beginRemoveRows(qc.QModelIndex(), istart, istop)
419 def end_markers_remove(self):
420 self.endRemoveRows()
421 self.marker_table_view.updateGeometries()
423 def headerData(self, col, orientation, role):
424 '''
425 Get header data entry.
426 '''
428 if orientation == qc.Qt.Horizontal:
429 if role == qc.Qt.DisplayRole:
430 return qc.QVariant(_header_data[col])
431 elif role == qc.Qt.SizeHintRole:
432 return qc.QSize(10, 20)
434 elif orientation == qc.Qt.Vertical:
435 if role == qc.Qt.DisplayRole:
436 return qc.QVariant(str(col))
438 else:
439 return qc.QVariant()
441 def get_marker(self, index):
442 return self.pile_viewer.markers[index.row()]
444 def data(self, index, role):
445 '''
446 Get model data entry.
447 '''
449 if not self.pile_viewer:
450 return qc.QVariant()
452 marker = self.pile_viewer.markers[index.row()]
453 column = index.column()
455 if role == qc.Qt.BackgroundRole:
456 if marker.active or column == _column_mapping['T']:
457 return qg.QBrush(
458 qg.QColor(*marker.select_color(g_color_b_faint)))
460 if role == qc.Qt.ForegroundRole:
461 if marker.active or column == _column_mapping['T']:
462 return qg.QBrush(
463 qg.QColor(*marker.select_color(g_color_b)))
465 elif role in (qc.Qt.DisplayRole, qc.Qt.UserRole):
467 v = None
468 if column == _column_mapping['Time']:
469 if role == qc.Qt.UserRole:
470 v = marker.tmin
471 else:
472 v = time_to_str(marker.tmin)
474 elif column == _column_mapping['T']:
475 if isinstance(marker, EventMarker):
476 v = u'\u25ce'
477 elif isinstance(marker, PhaseMarker):
478 v = marker.get_label()
480 elif column == _column_mapping['M']:
481 if isinstance(marker, EventMarker):
482 e = marker.get_event()
483 if e.moment_tensor is not None:
484 v = round(e.moment_tensor.magnitude, 1)
485 elif e.magnitude is not None:
486 v = round(e.magnitude, 1)
488 elif column == _column_mapping['Label']:
489 if isinstance(marker, EventMarker):
490 v = marker.label()
491 elif isinstance(marker, PhaseMarker):
492 v = marker.get_label()
494 elif column == _column_mapping['Depth [km]']:
495 if isinstance(marker, EventMarker):
496 d = marker.get_event().depth
497 if d is not None:
498 v = round(marker.get_event().depth/1000., 1)
500 elif column == _column_mapping['Lat']:
501 if isinstance(marker, EventMarker):
502 v = round(marker.get_event().effective_lat, 2)
504 elif column == _column_mapping['Lon']:
505 if isinstance(marker, EventMarker):
506 v = round(marker.get_event().effective_lon, 2)
508 elif column == _column_mapping['Kind']:
509 v = marker.kind
511 elif column == _column_mapping['Dist [km]']:
512 active_event = self.pile_viewer.get_active_event()
513 if isinstance(marker, EventMarker) \
514 and active_event is not None:
516 dist = orthodrome.distance_accurate50m(
517 marker.get_event(),
518 active_event)
520 v = dist
521 if role == qc.Qt.DisplayRole:
522 v = '%.5g' % (v/1000.)
524 elif column == _column_mapping['NSLCs']:
525 strs = []
526 for nslc_id in marker.get_nslc_ids():
527 strs.append('.'.join(nslc_id))
528 v = '|'.join(strs)
530 elif column == _column_mapping['Kagan Angle [deg]']:
531 active_event = self.pile_viewer.get_active_event()
532 if isinstance(marker, EventMarker) \
533 and active_event is not None \
534 and active_event.moment_tensor is not None \
535 and marker.get_event().moment_tensor is not None:
537 v = kagan_angle(
538 active_event.moment_tensor,
539 marker.get_event().moment_tensor)
541 if role == qc.Qt.DisplayRole:
542 v = '%.1f' % v
544 elif column == _column_mapping['MT']:
545 return qc.QVariant()
547 elif column == _column_mapping['Event Hash']:
548 if isinstance(marker, (EventMarker, PhaseMarker)):
549 v = marker.get_event_hash()
550 else:
551 return qc.QVariant()
553 elif column == _column_mapping['Polarity']:
554 if isinstance(marker, (PhaseMarker)):
555 v = marker.get_polarity_symbol()
556 else:
557 return qc.QVariant()
559 return qc.QVariant(v)
561 return qc.QVariant()
563 def handle_active_event_changed(self):
564 nmarkers = self.rowCount()
565 istart = self.index(0, _column_mapping['Dist [km]'])
566 istop = self.index(nmarkers-1, _column_mapping['Dist [km]'])
567 self.dataChanged.emit(istart, istop)
569 istart = self.index(0, _column_mapping['Kagan Angle [deg]'])
570 istop = self.index(nmarkers-1, _column_mapping['Kagan Angle [deg]'])
571 self.dataChanged.emit(istart, istop)
573 def done(self, index):
574 self.dataChanged.emit(index, index)
575 return True
577 def setData(self, index, value, role):
578 '''
579 Set model data entry.
580 '''
582 if role == qc.Qt.EditRole:
583 imarker = index.row()
584 marker = self.pile_viewer.markers[imarker]
585 if index.column() in [_column_mapping[c] for c in [
586 'M', 'Lat', 'Lon', 'Depth [km]']]:
588 if not isinstance(marker, EventMarker):
589 return False
590 else:
591 if index.column() == _column_mapping['M']:
592 valuef, valid = toFloat(value)
593 if valid:
594 e = marker.get_event()
595 if e.moment_tensor is None:
596 e.magnitude = valuef
597 else:
598 e.moment_tensor.magnitude = valuef
599 return self.done(index)
601 if index.column() in [_column_mapping['Lon'],
602 _column_mapping['Lat'],
603 _column_mapping['Depth [km]']]:
604 if isinstance(marker, EventMarker):
605 valuef, valid = toFloat(value)
606 if valid:
607 if index.column() == _column_mapping['Lat']:
608 marker.get_event().lat = valuef
609 elif index.column() == _column_mapping['Lon']:
610 marker.get_event().lon = valuef
611 elif index.column() == _column_mapping[
612 'Depth [km]']:
613 marker.get_event().depth = valuef*1000.
614 return self.done(index)
616 if index.column() == _column_mapping['Label']:
617 values = str(value)
618 if values != '':
619 if isinstance(marker, EventMarker):
620 marker.get_event().set_name(values)
621 return self.done(index)
623 if isinstance(marker, PhaseMarker):
624 marker.set_phasename(values)
625 return self.done(index)
627 return False
629 def flags(self, index):
630 '''
631 Set flags for cells which the user can edit.
632 '''
634 if index.column() not in self.marker_table_view.editable_columns:
635 return qc.Qt.ItemFlags(33)
636 else:
637 if isinstance(self.pile_viewer.markers[index.row()], EventMarker):
638 if index.column() in self.marker_table_view.editable_columns:
639 return qc.Qt.ItemFlags(35)
640 if index.column() == _column_mapping['Label']:
641 return qc.Qt.ItemFlags(35)
642 return qc.Qt.ItemFlags(33)
645class MarkerEditor(qw.QFrame):
647 def __init__(self, *args, **kwargs):
648 sortable = kwargs.pop('sortable', True)
649 qw.QFrame.__init__(self, *args, **kwargs)
650 layout = qw.QGridLayout()
651 layout.setContentsMargins(0, 0, 0, 0)
652 self.setLayout(layout)
653 self.marker_table_view = MarkerTableView(self, sortable=sortable)
655 self.delegate = MarkerItemDelegate(self.marker_table_view)
656 self.marker_table_view.setItemDelegate(self.delegate)
658 self.marker_model = MarkerTableModel()
659 self.marker_model.marker_table_view = self.marker_table_view
661 if sortable:
662 self.proxy_filter = MarkerSortFilterProxyModel()
663 self.proxy_filter.setDynamicSortFilter(True)
664 self.proxy_filter.setSourceModel(self.marker_model)
665 self.marker_model.proxy_filter = self.proxy_filter
667 self.marker_table_view.setModel(self.proxy_filter)
668 else:
669 self.proxy_filter = None
670 self.marker_table_view.setModel(self.marker_model)
672 header = self.marker_table_view.horizontalHeader()
673 for i_s, s in enumerate(_header_sizes):
674 if use_pyqt5:
675 header.setSectionResizeMode(i_s, qw.QHeaderView.Interactive)
676 else:
677 header.setResizeMode(i_s, qw.QHeaderView.Interactive)
679 header.resizeSection(i_s, s)
681 header.setStretchLastSection(True)
683 if self.proxy_filter:
684 self.selection_model = QItemSelectionModel(self.proxy_filter)
685 else:
686 self.selection_model = QItemSelectionModel(self.marker_model)
688 self.marker_table_view.setSelectionModel(self.selection_model)
689 self.selection_model.selectionChanged.connect(
690 self.set_selected_markers)
692 layout.addWidget(self.marker_table_view, 0, 0)
694 self.pile_viewer = None
695 self._size_hint = qc.QSize(1, 1)
697 def set_viewer(self, viewer):
698 '''
699 Set the pile viewer and connect signals.
700 '''
702 self.pile_viewer = viewer
703 self.marker_model.set_viewer(viewer)
704 self.marker_table_view.set_viewer(viewer)
705 self.pile_viewer.marker_selection_changed.connect(
706 self.update_selection_model)
708 self.pile_viewer.active_event_marker_changed.connect(
709 self.marker_model.handle_active_event_changed)
711 # self.pile_viewer.active_event_marker_changed.connect(
712 # self.marker_table_view.update_viewport)
714 self.marker_table_view.toggle_columns()
716 def set_selected_markers(self, selected, deselected):
717 '''
718 Update selection in viewer to reflect changes in table data.
719 '''
721 if self.proxy_filter:
722 def to_source(x):
723 ind = self.proxy_filter.index(x, 0)
724 return self.proxy_filter.mapToSource(ind).row()
725 else:
726 def to_source(x):
727 return x
729 markers = self.pile_viewer.markers
731 for rsel in selected:
732 for i in range(rsel.top(), rsel.bottom()+1):
733 marker = markers[to_source(i)]
734 if not marker.selected:
735 marker.selected = True
736 self.pile_viewer.n_selected_markers += 1
738 for rsel in deselected:
739 for i in range(rsel.top(), rsel.bottom()+1):
740 marker = markers[to_source(i)]
741 if marker.selected:
742 marker.selected = False
743 self.pile_viewer.n_selected_markers -= 1
745 self.pile_viewer.update()
747 def get_marker_model(self):
748 '''
749 Get the attached Qt table data model.
751 :returns: :py:class:`MarkerTableModel` object
752 '''
754 return self.marker_model
756 def update_selection_model(self, indices):
757 '''
758 Set currently selected table rows.
760 :param indices: begin and end+1 indices of contiguous selection chunks
761 :type indices: list of tuples
762 '''
763 self.selection_model.clearSelection()
764 selections = QItemSelection()
765 selection_flags = QItemSelectionModel.SelectionFlags(
766 (QItemSelectionModel.Select |
767 QItemSelectionModel.Rows |
768 QItemSelectionModel.Current))
770 for chunk in indices:
771 mi_start = self.marker_model.index(chunk[0], 0)
772 mi_stop = self.marker_model.index(chunk[1]-1, 0)
773 if self.proxy_filter:
774 row_selection = self.proxy_filter.mapSelectionFromSource(
775 QItemSelection(mi_start, mi_stop))
776 else:
777 row_selection = QItemSelection(mi_start, mi_stop)
778 selections.merge(row_selection, selection_flags)
780 if len(indices) != 0:
781 if self.proxy_filter:
782 self.marker_table_view.scrollTo(
783 self.proxy_filter.mapFromSource(mi_start))
784 else:
785 self.marker_table_view.scrollTo(mi_start)
787 self.marker_table_view.setCurrentIndex(mi_start)
788 self.selection_model.setCurrentIndex(
789 mi_start, selection_flags)
791 self.selection_model.select(selections, selection_flags)