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
19def get_err_palette():
20 err_palette = qg.QPalette()
21 err_palette.setColor(qg.QPalette.Text, qg.QColor(255, 200, 200))
22 return err_palette
25def get_palette():
26 return qw.QApplication.palette()
29def errorize(widget):
30 widget.setStyleSheet('''
31 QLineEdit {
32 background: rgb(200, 150, 150);
33 }''')
36def de_errorize(widget):
37 if isinstance(widget, qw.QWidget):
38 widget.setStyleSheet('')
41def strings_to_combobox(list_of_str):
42 cb = qw.QComboBox()
43 for i, s in enumerate(list_of_str):
44 cb.insertItem(i, s)
46 return cb
49def string_choices_to_combobox(cls):
50 return strings_to_combobox(cls.choices)
53def time_or_none_to_str(t):
54 if t is None:
55 return ''
56 else:
57 return util.time_to_str(t)
60def cover_region(lat, lon, delta, step=None, avoid_poles=False):
61 if step is None:
62 step = plot.nice_value(delta / 10.)
64 assert step <= 20.
66 def fl_major(x):
67 return math.floor(x / step) * step
69 def ce_major(x):
70 return math.ceil(x / step) * step
72 if avoid_poles:
73 lat_min_lim = -90. + step
74 lat_max_lim = 90. - step
75 else:
76 lat_min_lim = -90.
77 lat_max_lim = 90.
79 lat_min = max(lat_min_lim, fl_major(lat - delta))
80 lat_max = min(lat_max_lim, ce_major(lat + delta))
82 lon_closed = False
83 if abs(lat)+delta < 89.:
84 factor = 1.0 / math.cos((abs(lat)+delta) * d2r)
85 lon_min = fl_major(lon - delta * factor)
86 lon_max = ce_major(lon + delta * factor)
87 if lon_max >= lon_min + 360. - step*1e-5:
88 lon_min, lon_max = -180., 180. - step
89 lon_closed = True
90 else:
91 lon_min, lon_max = -180., 180. - step
92 lon_closed = True
94 return lat_min, lat_max, lon_min, lon_max, lon_closed
97qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \
98 qw.QFileDialog.DontUseSheet
101def _paint_cpt_rect(painter, cpt, rect):
102 rect.adjust(+5, +2, -5, -2)
104 rect_cpt = copy.deepcopy(rect)
105 rect_cpt.setWidth(int(rect.width() * 0.9) - 2)
107 rect_c_nan = copy.deepcopy(rect)
108 rect_c_nan.setLeft(rect.left() + rect_cpt.width() + 4)
109 rect_c_nan.setWidth(int(rect.width() * 0.1) - 2)
111 levels = num.zeros(len(cpt.levels) * 2 + 4)
112 colors = num.ones((levels.shape[0], 4)) * 255
114 for il, level in enumerate(cpt.levels):
115 levels[il*2+2] = level.vmin + (
116 level.vmax - level.vmin) / rect_cpt.width() # ow interp errors
117 levels[il*2+3] = level.vmax
119 colors[il*2+2, :3] = level.color_min
120 colors[il*2+3, :3] = level.color_max
122 level_range = levels[-3] - levels[2]
123 levels[0], levels[1] = levels[2] - level_range * 0.05, levels[2]
124 levels[-2], levels[-1] = levels[-3], levels[-3] + level_range * 0.05
126 if cpt.color_below:
127 colors[:2, :3] = cpt.color_below
128 else:
129 colors[:2] = (0, 0, 0, 0)
131 if cpt.color_above:
132 colors[-2:, :3] = cpt.color_above
133 else:
134 colors[-2:] = (0, 0, 0, 0)
136 levels_interp = num.linspace(levels[0], levels[-1], rect_cpt.width())
137 interpolator = interp1d(levels, colors.T)
139 colors_interp = interpolator(
140 levels_interp).T.astype(num.uint8).tobytes()
142 colors_interp = num.tile(
143 colors_interp, rect_cpt.height())
145 img = qg.QImage(
146 colors_interp, rect_cpt.width(), rect_cpt.height(),
147 qg.QImage.Format_RGBA8888)
149 painter.drawImage(rect_cpt, img)
151 c = cpt.color_nan
152 qcolor_nan = qg.QColor(*c if c is not None else (0, 0, 0))
153 qcolor_nan.setAlpha(255 if c is not None else 0)
155 painter.fillRect(rect_c_nan, qcolor_nan)
158class CPTStyleDelegate(qw.QItemDelegate):
160 def __init__(self, parent=None):
161 qw.QItemDelegate.__init__(self, parent)
163 def paint(self, painter, option, index):
164 data = index.model().data(index, qc.Qt.UserRole)
166 if isinstance(data, automap.CPT):
167 painter.save()
168 rect = option.rect
169 _paint_cpt_rect(painter, data, rect)
170 painter.restore()
172 else:
173 qw.QItemDelegate.paint(self, painter, option, index)
176class CPTComboBox(qw.QComboBox):
177 def __init__(self):
178 super().__init__()
180 self.setItemDelegate(CPTStyleDelegate(parent=self))
181 self.setInsertPolicy(qw.QComboBox.InsertAtBottom)
183 def paintEvent(self, e):
184 data = self.itemData(self.currentIndex(), qc.Qt.UserRole)
186 if isinstance(data, automap.CPT):
187 spainter = qw.QStylePainter(self)
188 spainter.setPen(self.palette().color(qg.QPalette.Text))
190 opt = qw.QStyleOptionComboBox()
191 self.initStyleOption(opt)
192 spainter.drawComplexControl(qw.QStyle.CC_ComboBox, opt)
194 painter = qg.QPainter(self)
195 painter.save()
197 rect = spainter.style().subElementRect(
198 qw.QStyle.SE_ComboBoxFocusRect, opt, self)
200 _paint_cpt_rect(painter, data, rect)
202 painter.restore()
204 else:
205 qw.QComboBox.paintEvent(self, e)
208class MyDockWidgetTitleBarButton(qw.QPushButton):
210 def __init__(self, *args, **kwargs):
211 qw.QPushButton.__init__(self, *args, **kwargs)
212 self.setFlat(True)
213 self.setSizePolicy(
214 qw.QSizePolicy.Fixed, qw.QSizePolicy.Fixed)
216 def sizeHint(self):
217 s = qw.QPushButton.sizeHint(self)
218 return qc.QSize(s.height(), s.height())
221class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton):
223 toggled = qc.pyqtSignal(bool)
225 def __init__(self, text_checked, text_unchecked, *args, **kwargs):
226 MyDockWidgetTitleBarButton.__init__(
227 self, text_checked, *args, **kwargs)
229 self._checked = True
230 self._text_checked = text_checked
231 self._text_unchecked = text_unchecked
232 self.update_text()
233 self.clicked.connect(self.toggle)
235 def set_checked(self, checked):
236 self._checked = checked
237 self.update_text()
239 def toggle(self):
240 self._checked = not self._checked
241 self.update_text()
242 self.toggled.emit(self._checked)
244 def update_text(self):
245 if self._checked:
246 self.setText(self._text_checked)
247 else:
248 self.setText(self._text_unchecked)
251class MyDockWidgetTitleBarLabel(qw.QLabel):
253 title_changed = qc.pyqtSignal()
254 representation_changed = qc.pyqtSignal()
256 def __init__(self, title, *args, **kwargs):
257 qw.QLabel.__init__(self, '', *args, **kwargs)
258 self._slug = ''
259 self._slug_abbreviated_length = 0
260 self._title = ''
261 self.set_title(title)
263 def set_title(self, title):
264 self._title = title
265 self.title_changed.emit()
266 self.update_text()
268 def get_title(self):
269 return self._title
271 def get_abbreviated_slug(self):
272 slug = self._slug[:self._slug_abbreviated_length]
273 if len(slug) != 0 and slug != self._slug:
274 return slug + '...'
275 else:
276 return slug
278 def update_text(self):
279 slug = self.get_abbreviated_slug()
280 self.setText('<strong>%s</strong>%s' % (
281 self._title,
282 ' <small>%s</small>' % slug if slug else ''))
283 self.representation_changed.emit()
285 def event(self, ev):
286 ev.ignore()
287 return qw.QLabel.event(self, ev)
289 def set_slug(self, slug):
290 self._slug = slug
291 self.setToolTip(slug)
292 self.update_text()
293 self.title_changed.emit()
295 def set_slug_abbreviated_length(self, n):
296 self._slug_abbreviated_length = n
297 self.update_text()
299 def get_slug(self):
300 return self._slug
302 def get_full_title(self):
303 slug = self._slug
304 return '%s%s' % (
305 self._title, ' [%s]' % slug if slug else '')
307 def get_abbreviated_title(self):
308 slug = self.get_abbreviated_slug()
309 return '%s%s' % (self._title, ' [%s]' % slug if slug else '')
312class MyDockWidgetTitleBar(qw.QFrame):
314 def __init__(self, title_label, title_controls=[]):
315 qw.QFrame.__init__(self)
317 if isinstance(title_label, str):
318 title_label = MyDockWidgetTitleBarLabel(title_label)
320 title_label.setSizePolicy(
321 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)
323 self._title_label = title_label
325 button_hide = MyDockWidgetTitleBarButton('\u2501')
326 button_hide.setStatusTip('Hide Panel')
328 layout = qw.QGridLayout()
329 layout.setSpacing(0)
330 layout.addWidget(title_label, 0, 0)
331 layout.addWidget(button_hide, 0, 1)
332 for i, button in enumerate(title_controls):
333 layout.addWidget(button, 0, 2 + i)
335 self.setLayout(layout)
336 self.setBackgroundRole(qg.QPalette.Mid)
337 self.setAutoFillBackground(True)
338 self.button_hide = button_hide
340 def event(self, ev):
341 ev.ignore()
342 return qw.QFrame.event(self, ev)
345class MyDockWidget(qw.QDockWidget):
347 def __init__(self, parent, title_label, title_controls=[], **kwargs):
349 qw.QDockWidget.__init__(self, parent, **kwargs)
351 self.setFeatures(
352 qw.QDockWidget.DockWidgetClosable
353 | qw.QDockWidget.DockWidgetMovable
354 | qw.QDockWidget.DockWidgetFloatable
355 | qw.QDockWidget.DockWidgetClosable)
357 self._visible = False
358 self._blocked = False
360 tb = MyDockWidgetTitleBar(title_label, title_controls)
361 tb.button_hide.clicked.connect(self.hide)
362 self.setTitleBarWidget(tb)
363 self.titlebar = tb
365 def update_title():
366 lab = self.titlebar._title_label.get_abbreviated_title()
367 self.setWindowTitle(lab)
369 self.titlebar._title_label.representation_changed.connect(update_title)
370 update_title()
372 def setVisible(self, visible):
373 self._visible = visible
374 if not self._blocked:
375 qw.QDockWidget.setVisible(self, self._visible)
377 def show(self):
378 self.setVisible(True)
380 def hide(self):
381 self.setVisible(False)
383 def setBlocked(self, blocked):
384 self._blocked = blocked
385 if blocked:
386 qw.QDockWidget.setVisible(self, False)
387 else:
388 qw.QDockWidget.setVisible(self, self._visible)
390 def block(self):
391 self.setBlocked(True)
393 def unblock(self):
394 self.setBlocked(False)
397class MyDoubleSpinBox(qw.QDoubleSpinBox):
398 def __init__(self, *args, **kwargs):
399 qw.QDoubleSpinBox.__init__(self, *args, **kwargs)
401 self.setLocale(qc.QLocale(' English'))