Coverage for /usr/local/lib/python3.13/dist-packages/pyrocko/gui/sparrow/common.py: 90%
264 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-12-04 10:41 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-12-04 10:41 +0000
1# https://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6import copy
8import math
9import numpy as num
10from scipy.interpolate import interp1d
12from pyrocko import plot
13from pyrocko.plot import automap
14from pyrocko.geometry import d2r
15from pyrocko.gui.qt_compat import qg, qw, qc
16from pyrocko.gui.util import tmin_effective, tmax_effective, get_app, \
17 errorize, de_errorize, time_or_none_to_str, time_to_lineedit, \
18 lineedit_to_time # noqa
21g_viewer = None
24def get_viewer():
25 return g_viewer
28def set_viewer(viewer):
29 global g_viewer
30 if viewer is not None and g_viewer:
31 raise Exception('Global viewer object already set.')
33 g_viewer = viewer
36def release_viewer():
37 set_viewer(None)
40def get_err_palette():
41 err_palette = qg.QPalette()
42 err_palette.setColor(qg.QPalette.Text, qg.QColor(255, 200, 200))
43 return err_palette
46def get_palette():
47 return qw.QApplication.palette()
50def strings_to_combobox(list_of_str):
51 cb = qw.QComboBox()
52 for i, s in enumerate(list_of_str):
53 cb.insertItem(i, s)
55 return cb
58def string_choices_to_combobox(cls):
59 return strings_to_combobox(cls.choices)
62def cover_region(lat, lon, delta, step=None, avoid_poles=False):
63 if step is None:
64 step = plot.nice_value(delta / 10.)
66 assert step <= 20.
68 def fl_major(x):
69 return math.floor(x / step) * step
71 def ce_major(x):
72 return math.ceil(x / step) * step
74 if avoid_poles:
75 lat_min_lim = -90. + step
76 lat_max_lim = 90. - step
77 else:
78 lat_min_lim = -90.
79 lat_max_lim = 90.
81 lat_min = max(lat_min_lim, fl_major(lat - delta))
82 lat_max = min(lat_max_lim, ce_major(lat + delta))
84 lon_closed = False
85 if abs(lat)+delta < 89.:
86 factor = 1.0 / math.cos((abs(lat)+delta) * d2r)
87 lon_min = fl_major(lon - delta * factor)
88 lon_max = ce_major(lon + delta * factor)
89 if lon_max >= lon_min + 360. - step*1e-5:
90 lon_min, lon_max = -180., 180. - step
91 lon_closed = True
92 else:
93 lon_min, lon_max = -180., 180. - step
94 lon_closed = True
96 return lat_min, lat_max, lon_min, lon_max, lon_closed
99qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \
100 qw.QFileDialog.DontUseSheet
103def _paint_cpt_rect(painter, cpt, rect):
104 rect.adjust(+5, +2, -5, -2)
106 rect_cpt = copy.deepcopy(rect)
107 rect_cpt.setWidth(int(rect.width() * 0.9) - 2)
109 rect_c_nan = copy.deepcopy(rect)
110 rect_c_nan.setLeft(rect.left() + rect_cpt.width() + 4)
111 rect_c_nan.setWidth(int(rect.width() * 0.1) - 2)
113 levels = num.zeros(len(cpt.levels) * 2 + 4)
114 colors = num.ones((levels.shape[0], 4)) * 255
116 for il, level in enumerate(cpt.levels):
117 levels[il*2+2] = level.vmin + (
118 level.vmax - level.vmin) / rect_cpt.width() # ow interp errors
119 levels[il*2+3] = level.vmax
121 colors[il*2+2, :3] = level.color_min
122 colors[il*2+3, :3] = level.color_max
124 level_range = levels[-3] - levels[2]
125 levels[0], levels[1] = levels[2] - level_range * 0.05, levels[2]
126 levels[-2], levels[-1] = levels[-3], levels[-3] + level_range * 0.05
128 if cpt.color_below:
129 colors[:2, :3] = cpt.color_below
130 else:
131 colors[:2] = (0, 0, 0, 0)
133 if cpt.color_above:
134 colors[-2:, :3] = cpt.color_above
135 else:
136 colors[-2:] = (0, 0, 0, 0)
138 levels_interp = num.linspace(levels[0], levels[-1], rect_cpt.width())
139 interpolator = interp1d(levels, colors.T)
141 colors_interp = interpolator(
142 levels_interp).T.astype(num.uint8).tobytes()
144 colors_interp = num.tile(
145 colors_interp, rect_cpt.height())
147 img = qg.QImage(
148 colors_interp, rect_cpt.width(), rect_cpt.height(),
149 qg.QImage.Format_RGBA8888)
151 painter.drawImage(rect_cpt, img)
153 c = cpt.color_nan
154 qcolor_nan = qg.QColor(*map(int, c) if c is not None else (0, 0, 0))
155 qcolor_nan.setAlpha(255 if c is not None else 0)
157 painter.fillRect(rect_c_nan, qcolor_nan)
160class CPTStyleDelegate(qw.QItemDelegate):
162 def __init__(self, parent=None):
163 qw.QItemDelegate.__init__(self, parent)
165 def paint(self, painter, option, index):
166 data = index.model().data(index, qc.Qt.UserRole)
168 if isinstance(data, automap.CPT):
169 painter.save()
170 rect = option.rect
171 _paint_cpt_rect(painter, data, rect)
172 painter.restore()
174 else:
175 qw.QItemDelegate.paint(self, painter, option, index)
178class CPTComboBox(qw.QComboBox):
179 def __init__(self):
180 super().__init__()
182 self.setItemDelegate(CPTStyleDelegate(parent=self))
183 self.setInsertPolicy(qw.QComboBox.InsertAtBottom)
185 def paintEvent(self, e):
186 data = self.itemData(self.currentIndex(), qc.Qt.UserRole)
188 if isinstance(data, automap.CPT):
189 spainter = qw.QStylePainter(self)
190 spainter.setPen(self.palette().color(qg.QPalette.Text))
192 opt = qw.QStyleOptionComboBox()
193 self.initStyleOption(opt)
194 spainter.drawComplexControl(qw.QStyle.CC_ComboBox, opt)
196 painter = qg.QPainter(self)
197 painter.save()
199 rect = spainter.style().subElementRect(
200 qw.QStyle.SE_ComboBoxFocusRect, opt, self)
202 _paint_cpt_rect(painter, data, rect)
204 painter.restore()
206 else:
207 qw.QComboBox.paintEvent(self, e)
210class MyDockWidgetTitleBarButton(qw.QPushButton):
212 def __init__(self, *args, **kwargs):
213 qw.QPushButton.__init__(self, *args, **kwargs)
214 self.setFlat(True)
215 self.setSizePolicy(
216 qw.QSizePolicy.Fixed, qw.QSizePolicy.Fixed)
218 def sizeHint(self):
219 s = qw.QPushButton.sizeHint(self)
220 return qc.QSize(s.height(), s.height())
223class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton):
225 toggled = qc.pyqtSignal(bool)
227 def __init__(self, text_checked, text_unchecked, *args, **kwargs):
228 MyDockWidgetTitleBarButton.__init__(
229 self, text_checked, *args, **kwargs)
231 self._checked = True
232 self._text_checked = text_checked
233 self._text_unchecked = text_unchecked
234 self.update_text()
235 self.clicked.connect(self.toggle)
237 def set_checked(self, checked):
238 self._checked = checked
239 self.update_text()
241 def toggle(self):
242 self._checked = not self._checked
243 self.update_text()
244 self.toggled.emit(self._checked)
246 def update_text(self):
247 if self._checked:
248 self.setText(self._text_checked)
249 else:
250 self.setText(self._text_unchecked)
253class MyDockWidgetTitleBarLabel(qw.QLabel):
255 title_changed = qc.pyqtSignal()
256 representation_changed = qc.pyqtSignal()
258 def __init__(self, title, *args, **kwargs):
259 qw.QLabel.__init__(self, '', *args, **kwargs)
260 self._slug = ''
261 self._slug_abbreviated_length = 0
262 self._title = ''
263 self.set_title(title)
265 def set_title(self, title):
266 self._title = title
267 self.title_changed.emit()
268 self.update_text()
270 def get_title(self):
271 return self._title
273 def get_abbreviated_slug(self):
274 slug = self._slug[:self._slug_abbreviated_length]
275 if len(slug) != 0 and slug != self._slug:
276 return slug + '...'
277 else:
278 return slug
280 def update_text(self):
281 slug = self.get_abbreviated_slug()
282 self.setText('<strong>%s</strong>%s' % (
283 self._title,
284 ' <small>%s</small>' % slug if slug else ''))
285 self.representation_changed.emit()
287 def event(self, ev):
288 ev.ignore()
289 return qw.QLabel.event(self, ev)
291 def set_slug(self, slug):
292 self._slug = slug
293 self.setToolTip(slug)
294 self.update_text()
295 self.title_changed.emit()
297 def set_slug_abbreviated_length(self, n):
298 self._slug_abbreviated_length = n
299 self.update_text()
301 def get_slug(self):
302 return self._slug
304 def get_full_title(self):
305 slug = self._slug
306 return '%s%s' % (
307 self._title, ' [%s]' % slug if slug else '')
309 def get_abbreviated_title(self):
310 slug = self.get_abbreviated_slug()
311 return '%s%s' % (self._title, ' [%s]' % slug if slug else '')
314class MyDockWidgetTitleBar(qw.QFrame):
316 def __init__(self, title_label, title_controls=[]):
317 qw.QFrame.__init__(self)
319 if isinstance(title_label, str):
320 title_label = MyDockWidgetTitleBarLabel(title_label)
322 title_label.setSizePolicy(
323 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)
325 self._title_label = title_label
327 button_hide = MyDockWidgetTitleBarButton('\u2501')
328 button_hide.setStatusTip('Hide Panel')
330 layout = qw.QGridLayout()
331 layout.setSpacing(0)
332 layout.addWidget(title_label, 0, 0)
333 layout.addWidget(button_hide, 0, 1)
334 for i, button in enumerate(title_controls):
335 layout.addWidget(button, 0, 2 + i)
337 self.setLayout(layout)
338 self.setBackgroundRole(qg.QPalette.Mid)
339 self.setAutoFillBackground(True)
340 self.button_hide = button_hide
342 def event(self, ev):
343 ev.ignore()
344 return qw.QFrame.event(self, ev)
347class MyDockWidget(qw.QDockWidget):
349 def __init__(self, parent, title_label, title_controls=[], **kwargs):
351 qw.QDockWidget.__init__(self, parent, **kwargs)
353 self.setFeatures(
354 qw.QDockWidget.DockWidgetClosable
355 | qw.QDockWidget.DockWidgetMovable
356 | qw.QDockWidget.DockWidgetFloatable
357 | qw.QDockWidget.DockWidgetClosable)
359 self._visible = False
360 self._blocked = False
362 tb = MyDockWidgetTitleBar(title_label, title_controls)
363 tb.button_hide.clicked.connect(self.hide)
364 self.setTitleBarWidget(tb)
365 self.titlebar = tb
367 def update_title():
368 lab = self.titlebar._title_label.get_abbreviated_title()
369 self.setWindowTitle(lab)
371 self.titlebar._title_label.representation_changed.connect(update_title)
372 update_title()
374 def setVisible(self, visible):
375 self._visible = visible
376 if not self._blocked:
377 qw.QDockWidget.setVisible(self, self._visible)
379 def show(self):
380 self.setVisible(True)
382 def hide(self):
383 self.setVisible(False)
385 def setBlocked(self, blocked):
386 self._blocked = blocked
387 if blocked:
388 qw.QDockWidget.setVisible(self, False)
389 else:
390 qw.QDockWidget.setVisible(self, self._visible)
392 def block(self):
393 self.setBlocked(True)
395 def unblock(self):
396 self.setBlocked(False)
399class MyScrollArea(qw.QScrollArea):
401 def sizeHint(self):
402 s = qc.QSize()
403 s.setWidth(self.widget().sizeHint().width())
404 s.setHeight(self.widget().sizeHint().height())
405 return s
407 def minimumSizeHint(self):
408 s = qc.QSize()
409 s.setWidth(self.widget().minimumSizeHint().width())
410 s.setHeight(0)
411 return s
413 def resizeEvent(self, event):
414 s = event.size()
415 s.setHeight(self.widget().height())
416 self.widget().resize(s)
419class MyDoubleSpinBox(qw.QDoubleSpinBox):
420 def __init__(self, *args, **kwargs):
421 qw.QDoubleSpinBox.__init__(self, *args, **kwargs)
423 self.setLocale(qc.QLocale(' English'))