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 cover_region(lat, lon, delta, step=None, avoid_poles=False):
80 if step is None:
81 step = plot.nice_value(delta / 10.)
83 assert step <= 20.
85 def fl_major(x):
86 return math.floor(x / step) * step
88 def ce_major(x):
89 return math.ceil(x / step) * step
91 if avoid_poles:
92 lat_min_lim = -90. + step
93 lat_max_lim = 90. - step
94 else:
95 lat_min_lim = -90.
96 lat_max_lim = 90.
98 lat_min = max(lat_min_lim, fl_major(lat - delta))
99 lat_max = min(lat_max_lim, ce_major(lat + delta))
101 lon_closed = False
102 if abs(lat)+delta < 89.:
103 factor = 1.0 / math.cos((abs(lat)+delta) * d2r)
104 lon_min = fl_major(lon - delta * factor)
105 lon_max = ce_major(lon + delta * factor)
106 if lon_max >= lon_min + 360. - step*1e-5:
107 lon_min, lon_max = -180., 180. - step
108 lon_closed = True
109 else:
110 lon_min, lon_max = -180., 180. - step
111 lon_closed = True
113 return lat_min, lat_max, lon_min, lon_max, lon_closed
116qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \
117 qw.QFileDialog.DontUseSheet
120def _paint_cpt_rect(painter, cpt, rect):
121 rect.adjust(+5, +2, -5, -2)
123 rect_cpt = copy.deepcopy(rect)
124 rect_cpt.setWidth(int(rect.width() * 0.9) - 2)
126 rect_c_nan = copy.deepcopy(rect)
127 rect_c_nan.setLeft(rect.left() + rect_cpt.width() + 4)
128 rect_c_nan.setWidth(int(rect.width() * 0.1) - 2)
130 levels = num.zeros(len(cpt.levels) * 2 + 4)
131 colors = num.ones((levels.shape[0], 4)) * 255
133 for il, level in enumerate(cpt.levels):
134 levels[il*2+2] = level.vmin + (
135 level.vmax - level.vmin) / rect_cpt.width() # ow interp errors
136 levels[il*2+3] = level.vmax
138 colors[il*2+2, :3] = level.color_min
139 colors[il*2+3, :3] = level.color_max
141 level_range = levels[-3] - levels[2]
142 levels[0], levels[1] = levels[2] - level_range * 0.05, levels[2]
143 levels[-2], levels[-1] = levels[-3], levels[-3] + level_range * 0.05
145 if cpt.color_below:
146 colors[:2, :3] = cpt.color_below
147 else:
148 colors[:2] = (0, 0, 0, 0)
150 if cpt.color_above:
151 colors[-2:, :3] = cpt.color_above
152 else:
153 colors[-2:] = (0, 0, 0, 0)
155 levels_interp = num.linspace(levels[0], levels[-1], rect_cpt.width())
156 interpolator = interp1d(levels, colors.T)
158 colors_interp = interpolator(
159 levels_interp).T.astype(num.uint8).tobytes()
161 colors_interp = num.tile(
162 colors_interp, rect_cpt.height())
164 img = qg.QImage(
165 colors_interp, rect_cpt.width(), rect_cpt.height(),
166 qg.QImage.Format_RGBA8888)
168 painter.drawImage(rect_cpt, img)
170 c = cpt.color_nan
171 qcolor_nan = qg.QColor(*c if c is not None else (0, 0, 0))
172 qcolor_nan.setAlpha(255 if c is not None else 0)
174 painter.fillRect(rect_c_nan, qcolor_nan)
177class CPTStyleDelegate(qw.QItemDelegate):
179 def __init__(self, parent=None):
180 qw.QItemDelegate.__init__(self, parent)
182 def paint(self, painter, option, index):
183 data = index.model().data(index, qc.Qt.UserRole)
185 if isinstance(data, automap.CPT):
186 painter.save()
187 rect = option.rect
188 _paint_cpt_rect(painter, data, rect)
189 painter.restore()
191 else:
192 qw.QItemDelegate.paint(self, painter, option, index)
195class CPTComboBox(qw.QComboBox):
196 def __init__(self):
197 super().__init__()
199 self.setItemDelegate(CPTStyleDelegate(parent=self))
200 self.setInsertPolicy(qw.QComboBox.InsertAtBottom)
202 def paintEvent(self, e):
203 data = self.itemData(self.currentIndex(), qc.Qt.UserRole)
205 if isinstance(data, automap.CPT):
206 spainter = qw.QStylePainter(self)
207 spainter.setPen(self.palette().color(qg.QPalette.Text))
209 opt = qw.QStyleOptionComboBox()
210 self.initStyleOption(opt)
211 spainter.drawComplexControl(qw.QStyle.CC_ComboBox, opt)
213 painter = qg.QPainter(self)
214 painter.save()
216 rect = spainter.style().subElementRect(
217 qw.QStyle.SE_ComboBoxFocusRect, opt, self)
219 _paint_cpt_rect(painter, data, rect)
221 painter.restore()
223 else:
224 qw.QComboBox.paintEvent(self, e)
227class MyDockWidgetTitleBarButton(qw.QPushButton):
229 def __init__(self, *args, **kwargs):
230 qw.QPushButton.__init__(self, *args, **kwargs)
231 self.setFlat(True)
232 self.setSizePolicy(
233 qw.QSizePolicy.Fixed, qw.QSizePolicy.Fixed)
235 def sizeHint(self):
236 s = qw.QPushButton.sizeHint(self)
237 return qc.QSize(s.height(), s.height())
240class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton):
242 toggled = qc.pyqtSignal(bool)
244 def __init__(self, text_checked, text_unchecked, *args, **kwargs):
245 MyDockWidgetTitleBarButton.__init__(
246 self, text_checked, *args, **kwargs)
248 self._checked = True
249 self._text_checked = text_checked
250 self._text_unchecked = text_unchecked
251 self.update_text()
252 self.clicked.connect(self.toggle)
254 def set_checked(self, checked):
255 self._checked = checked
256 self.update_text()
258 def toggle(self):
259 self._checked = not self._checked
260 self.update_text()
261 self.toggled.emit(self._checked)
263 def update_text(self):
264 if self._checked:
265 self.setText(self._text_checked)
266 else:
267 self.setText(self._text_unchecked)
270class MyDockWidgetTitleBarLabel(qw.QLabel):
272 title_changed = qc.pyqtSignal()
273 representation_changed = qc.pyqtSignal()
275 def __init__(self, title, *args, **kwargs):
276 qw.QLabel.__init__(self, '', *args, **kwargs)
277 self._slug = ''
278 self._slug_abbreviated_length = 0
279 self._title = ''
280 self.set_title(title)
282 def set_title(self, title):
283 self._title = title
284 self.title_changed.emit()
285 self.update_text()
287 def get_title(self):
288 return self._title
290 def get_abbreviated_slug(self):
291 slug = self._slug[:self._slug_abbreviated_length]
292 if len(slug) != 0 and slug != self._slug:
293 return slug + '...'
294 else:
295 return slug
297 def update_text(self):
298 slug = self.get_abbreviated_slug()
299 self.setText('<strong>%s</strong>%s' % (
300 self._title,
301 ' <small>%s</small>' % slug if slug else ''))
302 self.representation_changed.emit()
304 def event(self, ev):
305 ev.ignore()
306 return qw.QLabel.event(self, ev)
308 def set_slug(self, slug):
309 self._slug = slug
310 self.setToolTip(slug)
311 self.update_text()
312 self.title_changed.emit()
314 def set_slug_abbreviated_length(self, n):
315 self._slug_abbreviated_length = n
316 self.update_text()
318 def get_slug(self):
319 return self._slug
321 def get_full_title(self):
322 slug = self._slug
323 return '%s%s' % (
324 self._title, ' [%s]' % slug if slug else '')
326 def get_abbreviated_title(self):
327 slug = self.get_abbreviated_slug()
328 return '%s%s' % (self._title, ' [%s]' % slug if slug else '')
331class MyDockWidgetTitleBar(qw.QFrame):
333 def __init__(self, title_label, title_controls=[]):
334 qw.QFrame.__init__(self)
336 if isinstance(title_label, str):
337 title_label = MyDockWidgetTitleBarLabel(title_label)
339 title_label.setSizePolicy(
340 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)
342 self._title_label = title_label
344 button_hide = MyDockWidgetTitleBarButton('\u2501')
345 button_hide.setStatusTip('Hide Panel')
347 layout = qw.QGridLayout()
348 layout.setSpacing(0)
349 layout.addWidget(title_label, 0, 0)
350 layout.addWidget(button_hide, 0, 1)
351 for i, button in enumerate(title_controls):
352 layout.addWidget(button, 0, 2 + i)
354 self.setLayout(layout)
355 self.setBackgroundRole(qg.QPalette.Mid)
356 self.setAutoFillBackground(True)
357 self.button_hide = button_hide
359 def event(self, ev):
360 ev.ignore()
361 return qw.QFrame.event(self, ev)
364class MyDockWidget(qw.QDockWidget):
366 def __init__(self, parent, title_label, title_controls=[], **kwargs):
368 qw.QDockWidget.__init__(self, parent, **kwargs)
370 self.setFeatures(
371 qw.QDockWidget.DockWidgetClosable
372 | qw.QDockWidget.DockWidgetMovable
373 | qw.QDockWidget.DockWidgetFloatable
374 | qw.QDockWidget.DockWidgetClosable)
376 self._visible = False
377 self._blocked = False
379 tb = MyDockWidgetTitleBar(title_label, title_controls)
380 tb.button_hide.clicked.connect(self.hide)
381 self.setTitleBarWidget(tb)
382 self.titlebar = tb
384 def update_title():
385 lab = self.titlebar._title_label.get_abbreviated_title()
386 self.setWindowTitle(lab)
388 self.titlebar._title_label.representation_changed.connect(update_title)
389 update_title()
391 def setVisible(self, visible):
392 self._visible = visible
393 if not self._blocked:
394 qw.QDockWidget.setVisible(self, self._visible)
396 def show(self):
397 self.setVisible(True)
399 def hide(self):
400 self.setVisible(False)
402 def setBlocked(self, blocked):
403 self._blocked = blocked
404 if blocked:
405 qw.QDockWidget.setVisible(self, False)
406 else:
407 qw.QDockWidget.setVisible(self, self._visible)
409 def block(self):
410 self.setBlocked(True)
412 def unblock(self):
413 self.setBlocked(False)
416class MyDoubleSpinBox(qw.QDoubleSpinBox):
417 def __init__(self, *args, **kwargs):
418 qw.QDoubleSpinBox.__init__(self, *args, **kwargs)
420 self.setLocale(qc.QLocale(' English'))