Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/gui/sparrow/common.py: 88%
283 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-07 11:54 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-07 11:54 +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, util
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 # noqa
19g_viewer = None
22def get_viewer():
23 return g_viewer
26def set_viewer(viewer):
27 global g_viewer
28 if viewer is not None and g_viewer:
29 raise Exception('Global viewer object already set.')
31 g_viewer = viewer
34def release_viewer():
35 set_viewer(None)
38def get_err_palette():
39 err_palette = qg.QPalette()
40 err_palette.setColor(qg.QPalette.Text, qg.QColor(255, 200, 200))
41 return err_palette
44def get_palette():
45 return qw.QApplication.palette()
48def errorize(widget):
49 widget.setStyleSheet('''
50 QLineEdit {
51 background: rgb(200, 150, 150);
52 }''')
55def de_errorize(widget):
56 if isinstance(widget, qw.QWidget):
57 widget.setStyleSheet('')
60def strings_to_combobox(list_of_str):
61 cb = qw.QComboBox()
62 for i, s in enumerate(list_of_str):
63 cb.insertItem(i, s)
65 return cb
68def string_choices_to_combobox(cls):
69 return strings_to_combobox(cls.choices)
72def time_or_none_to_str(t):
73 if t is None:
74 return ''
75 else:
76 return util.time_to_str(t)
79def time_to_lineedit(state, attribute, widget):
80 widget.setText(time_or_none_to_str(getattr(state, attribute)))
83def lineedit_to_time(widget, state, attribute):
84 from pyrocko.util import str_to_time_fillup
86 s = str(widget.text())
87 if not s.strip():
88 setattr(state, attribute, None)
89 else:
90 try:
91 setattr(state, attribute, str_to_time_fillup(s))
92 except Exception:
93 raise ValueError(
94 'Use time format: YYYY-MM-DD HH:MM:SS.FFF')
97def cover_region(lat, lon, delta, step=None, avoid_poles=False):
98 if step is None:
99 step = plot.nice_value(delta / 10.)
101 assert step <= 20.
103 def fl_major(x):
104 return math.floor(x / step) * step
106 def ce_major(x):
107 return math.ceil(x / step) * step
109 if avoid_poles:
110 lat_min_lim = -90. + step
111 lat_max_lim = 90. - step
112 else:
113 lat_min_lim = -90.
114 lat_max_lim = 90.
116 lat_min = max(lat_min_lim, fl_major(lat - delta))
117 lat_max = min(lat_max_lim, ce_major(lat + delta))
119 lon_closed = False
120 if abs(lat)+delta < 89.:
121 factor = 1.0 / math.cos((abs(lat)+delta) * d2r)
122 lon_min = fl_major(lon - delta * factor)
123 lon_max = ce_major(lon + delta * factor)
124 if lon_max >= lon_min + 360. - step*1e-5:
125 lon_min, lon_max = -180., 180. - step
126 lon_closed = True
127 else:
128 lon_min, lon_max = -180., 180. - step
129 lon_closed = True
131 return lat_min, lat_max, lon_min, lon_max, lon_closed
134qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \
135 qw.QFileDialog.DontUseSheet
138def _paint_cpt_rect(painter, cpt, rect):
139 rect.adjust(+5, +2, -5, -2)
141 rect_cpt = copy.deepcopy(rect)
142 rect_cpt.setWidth(int(rect.width() * 0.9) - 2)
144 rect_c_nan = copy.deepcopy(rect)
145 rect_c_nan.setLeft(rect.left() + rect_cpt.width() + 4)
146 rect_c_nan.setWidth(int(rect.width() * 0.1) - 2)
148 levels = num.zeros(len(cpt.levels) * 2 + 4)
149 colors = num.ones((levels.shape[0], 4)) * 255
151 for il, level in enumerate(cpt.levels):
152 levels[il*2+2] = level.vmin + (
153 level.vmax - level.vmin) / rect_cpt.width() # ow interp errors
154 levels[il*2+3] = level.vmax
156 colors[il*2+2, :3] = level.color_min
157 colors[il*2+3, :3] = level.color_max
159 level_range = levels[-3] - levels[2]
160 levels[0], levels[1] = levels[2] - level_range * 0.05, levels[2]
161 levels[-2], levels[-1] = levels[-3], levels[-3] + level_range * 0.05
163 if cpt.color_below:
164 colors[:2, :3] = cpt.color_below
165 else:
166 colors[:2] = (0, 0, 0, 0)
168 if cpt.color_above:
169 colors[-2:, :3] = cpt.color_above
170 else:
171 colors[-2:] = (0, 0, 0, 0)
173 levels_interp = num.linspace(levels[0], levels[-1], rect_cpt.width())
174 interpolator = interp1d(levels, colors.T)
176 colors_interp = interpolator(
177 levels_interp).T.astype(num.uint8).tobytes()
179 colors_interp = num.tile(
180 colors_interp, rect_cpt.height())
182 img = qg.QImage(
183 colors_interp, rect_cpt.width(), rect_cpt.height(),
184 qg.QImage.Format_RGBA8888)
186 painter.drawImage(rect_cpt, img)
188 c = cpt.color_nan
189 qcolor_nan = qg.QColor(*c if c is not None else (0, 0, 0))
190 qcolor_nan.setAlpha(255 if c is not None else 0)
192 painter.fillRect(rect_c_nan, qcolor_nan)
195class CPTStyleDelegate(qw.QItemDelegate):
197 def __init__(self, parent=None):
198 qw.QItemDelegate.__init__(self, parent)
200 def paint(self, painter, option, index):
201 data = index.model().data(index, qc.Qt.UserRole)
203 if isinstance(data, automap.CPT):
204 painter.save()
205 rect = option.rect
206 _paint_cpt_rect(painter, data, rect)
207 painter.restore()
209 else:
210 qw.QItemDelegate.paint(self, painter, option, index)
213class CPTComboBox(qw.QComboBox):
214 def __init__(self):
215 super().__init__()
217 self.setItemDelegate(CPTStyleDelegate(parent=self))
218 self.setInsertPolicy(qw.QComboBox.InsertAtBottom)
220 def paintEvent(self, e):
221 data = self.itemData(self.currentIndex(), qc.Qt.UserRole)
223 if isinstance(data, automap.CPT):
224 spainter = qw.QStylePainter(self)
225 spainter.setPen(self.palette().color(qg.QPalette.Text))
227 opt = qw.QStyleOptionComboBox()
228 self.initStyleOption(opt)
229 spainter.drawComplexControl(qw.QStyle.CC_ComboBox, opt)
231 painter = qg.QPainter(self)
232 painter.save()
234 rect = spainter.style().subElementRect(
235 qw.QStyle.SE_ComboBoxFocusRect, opt, self)
237 _paint_cpt_rect(painter, data, rect)
239 painter.restore()
241 else:
242 qw.QComboBox.paintEvent(self, e)
245class MyDockWidgetTitleBarButton(qw.QPushButton):
247 def __init__(self, *args, **kwargs):
248 qw.QPushButton.__init__(self, *args, **kwargs)
249 self.setFlat(True)
250 self.setSizePolicy(
251 qw.QSizePolicy.Fixed, qw.QSizePolicy.Fixed)
253 def sizeHint(self):
254 s = qw.QPushButton.sizeHint(self)
255 return qc.QSize(s.height(), s.height())
258class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton):
260 toggled = qc.pyqtSignal(bool)
262 def __init__(self, text_checked, text_unchecked, *args, **kwargs):
263 MyDockWidgetTitleBarButton.__init__(
264 self, text_checked, *args, **kwargs)
266 self._checked = True
267 self._text_checked = text_checked
268 self._text_unchecked = text_unchecked
269 self.update_text()
270 self.clicked.connect(self.toggle)
272 def set_checked(self, checked):
273 self._checked = checked
274 self.update_text()
276 def toggle(self):
277 self._checked = not self._checked
278 self.update_text()
279 self.toggled.emit(self._checked)
281 def update_text(self):
282 if self._checked:
283 self.setText(self._text_checked)
284 else:
285 self.setText(self._text_unchecked)
288class MyDockWidgetTitleBarLabel(qw.QLabel):
290 title_changed = qc.pyqtSignal()
291 representation_changed = qc.pyqtSignal()
293 def __init__(self, title, *args, **kwargs):
294 qw.QLabel.__init__(self, '', *args, **kwargs)
295 self._slug = ''
296 self._slug_abbreviated_length = 0
297 self._title = ''
298 self.set_title(title)
300 def set_title(self, title):
301 self._title = title
302 self.title_changed.emit()
303 self.update_text()
305 def get_title(self):
306 return self._title
308 def get_abbreviated_slug(self):
309 slug = self._slug[:self._slug_abbreviated_length]
310 if len(slug) != 0 and slug != self._slug:
311 return slug + '...'
312 else:
313 return slug
315 def update_text(self):
316 slug = self.get_abbreviated_slug()
317 self.setText('<strong>%s</strong>%s' % (
318 self._title,
319 ' <small>%s</small>' % slug if slug else ''))
320 self.representation_changed.emit()
322 def event(self, ev):
323 ev.ignore()
324 return qw.QLabel.event(self, ev)
326 def set_slug(self, slug):
327 self._slug = slug
328 self.setToolTip(slug)
329 self.update_text()
330 self.title_changed.emit()
332 def set_slug_abbreviated_length(self, n):
333 self._slug_abbreviated_length = n
334 self.update_text()
336 def get_slug(self):
337 return self._slug
339 def get_full_title(self):
340 slug = self._slug
341 return '%s%s' % (
342 self._title, ' [%s]' % slug if slug else '')
344 def get_abbreviated_title(self):
345 slug = self.get_abbreviated_slug()
346 return '%s%s' % (self._title, ' [%s]' % slug if slug else '')
349class MyDockWidgetTitleBar(qw.QFrame):
351 def __init__(self, title_label, title_controls=[]):
352 qw.QFrame.__init__(self)
354 if isinstance(title_label, str):
355 title_label = MyDockWidgetTitleBarLabel(title_label)
357 title_label.setSizePolicy(
358 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)
360 self._title_label = title_label
362 button_hide = MyDockWidgetTitleBarButton('\u2501')
363 button_hide.setStatusTip('Hide Panel')
365 layout = qw.QGridLayout()
366 layout.setSpacing(0)
367 layout.addWidget(title_label, 0, 0)
368 layout.addWidget(button_hide, 0, 1)
369 for i, button in enumerate(title_controls):
370 layout.addWidget(button, 0, 2 + i)
372 self.setLayout(layout)
373 self.setBackgroundRole(qg.QPalette.Mid)
374 self.setAutoFillBackground(True)
375 self.button_hide = button_hide
377 def event(self, ev):
378 ev.ignore()
379 return qw.QFrame.event(self, ev)
382class MyDockWidget(qw.QDockWidget):
384 def __init__(self, parent, title_label, title_controls=[], **kwargs):
386 qw.QDockWidget.__init__(self, parent, **kwargs)
388 self.setFeatures(
389 qw.QDockWidget.DockWidgetClosable
390 | qw.QDockWidget.DockWidgetMovable
391 | qw.QDockWidget.DockWidgetFloatable
392 | qw.QDockWidget.DockWidgetClosable)
394 self._visible = False
395 self._blocked = False
397 tb = MyDockWidgetTitleBar(title_label, title_controls)
398 tb.button_hide.clicked.connect(self.hide)
399 self.setTitleBarWidget(tb)
400 self.titlebar = tb
402 def update_title():
403 lab = self.titlebar._title_label.get_abbreviated_title()
404 self.setWindowTitle(lab)
406 self.titlebar._title_label.representation_changed.connect(update_title)
407 update_title()
409 def setVisible(self, visible):
410 self._visible = visible
411 if not self._blocked:
412 qw.QDockWidget.setVisible(self, self._visible)
414 def show(self):
415 self.setVisible(True)
417 def hide(self):
418 self.setVisible(False)
420 def setBlocked(self, blocked):
421 self._blocked = blocked
422 if blocked:
423 qw.QDockWidget.setVisible(self, False)
424 else:
425 qw.QDockWidget.setVisible(self, self._visible)
427 def block(self):
428 self.setBlocked(True)
430 def unblock(self):
431 self.setBlocked(False)
434class MyScrollArea(qw.QScrollArea):
436 def sizeHint(self):
437 s = qc.QSize()
438 s.setWidth(self.widget().sizeHint().width())
439 s.setHeight(self.widget().sizeHint().height())
440 return s
442 def minimumSizeHint(self):
443 s = qc.QSize()
444 s.setWidth(self.widget().minimumSizeHint().width())
445 s.setHeight(0)
446 return s
448 def resizeEvent(self, event):
449 s = event.size()
450 s.setHeight(self.widget().height())
451 self.widget().resize(s)
454class MyDoubleSpinBox(qw.QDoubleSpinBox):
455 def __init__(self, *args, **kwargs):
456 qw.QDoubleSpinBox.__init__(self, *args, **kwargs)
458 self.setLocale(qc.QLocale(' English'))