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 automap, plot, util
13from pyrocko.geometry import d2r
14from pyrocko.gui.qt_compat import qg, qw, qc
15from pyrocko.gui.util import tmin_effective, tmax_effective, get_app # noqa
18def get_err_palette():
19 err_palette = qg.QPalette()
20 err_palette.setColor(qg.QPalette.Text, qg.QColor(255, 200, 200))
21 return err_palette
24def get_palette():
25 return qw.QApplication.palette()
28def errorize(widget):
29 widget.setStyleSheet('''
30 QLineEdit {
31 background: rgb(200, 150, 150);
32 }''')
35def de_errorize(widget):
36 if isinstance(widget, qw.QWidget):
37 widget.setStyleSheet('')
40def strings_to_combobox(list_of_str):
41 cb = qw.QComboBox()
42 for i, s in enumerate(list_of_str):
43 cb.insertItem(i, s)
45 return cb
48def string_choices_to_combobox(cls):
49 return strings_to_combobox(cls.choices)
52def time_or_none_to_str(t):
53 if t is None:
54 return ''
55 else:
56 return util.time_to_str(t)
59def cover_region(lat, lon, delta, step=None, avoid_poles=False):
60 if step is None:
61 step = plot.nice_value(delta / 10.)
63 assert step <= 20.
65 def fl_major(x):
66 return math.floor(x / step) * step
68 def ce_major(x):
69 return math.ceil(x / step) * step
71 if avoid_poles:
72 lat_min_lim = -90. + step
73 lat_max_lim = 90. - step
74 else:
75 lat_min_lim = -90.
76 lat_max_lim = 90.
78 lat_min = max(lat_min_lim, fl_major(lat - delta))
79 lat_max = min(lat_max_lim, ce_major(lat + delta))
81 lon_closed = False
82 if abs(lat)+delta < 89.:
83 factor = 1.0 / math.cos((abs(lat)+delta) * d2r)
84 lon_min = fl_major(lon - delta * factor)
85 lon_max = ce_major(lon + delta * factor)
86 if lon_max >= lon_min + 360. - step*1e-5:
87 lon_min, lon_max = -180., 180. - step
88 lon_closed = True
89 else:
90 lon_min, lon_max = -180., 180. - step
91 lon_closed = True
93 return lat_min, lat_max, lon_min, lon_max, lon_closed
96qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \
97 qw.QFileDialog.DontUseSheet
100def _paint_cpt_rect(painter, cpt, rect):
101 rect.adjust(+5, +2, -5, -2)
103 rect_cpt = copy.deepcopy(rect)
104 rect_cpt.setWidth(int(rect.width() * 0.9) - 2)
106 rect_c_nan = copy.deepcopy(rect)
107 rect_c_nan.setLeft(rect.left() + rect_cpt.width() + 4)
108 rect_c_nan.setWidth(int(rect.width() * 0.1) - 2)
110 levels = num.zeros(len(cpt.levels) * 2 + 4)
111 colors = num.ones((levels.shape[0], 4)) * 255
113 for il, level in enumerate(cpt.levels):
114 levels[il*2+2] = level.vmin + (
115 level.vmax - level.vmin) / rect_cpt.width() # ow interp errors
116 levels[il*2+3] = level.vmax
118 colors[il*2+2, :3] = level.color_min
119 colors[il*2+3, :3] = level.color_max
121 level_range = levels[-3] - levels[2]
122 levels[0], levels[1] = levels[2] - level_range * 0.05, levels[2]
123 levels[-2], levels[-1] = levels[-3], levels[-3] + level_range * 0.05
125 if cpt.color_below:
126 colors[:2, :3] = cpt.color_below
127 else:
128 colors[:2] = (0, 0, 0, 0)
130 if cpt.color_above:
131 colors[-2:, :3] = cpt.color_above
132 else:
133 colors[-2:] = (0, 0, 0, 0)
135 levels_interp = num.linspace(levels[0], levels[-1], rect_cpt.width())
136 interpolator = interp1d(levels, colors.T)
138 colors_interp = interpolator(
139 levels_interp).T.astype(num.uint8).tobytes()
141 colors_interp = num.tile(
142 colors_interp, rect_cpt.height())
144 img = qg.QImage(
145 colors_interp, rect_cpt.width(), rect_cpt.height(),
146 qg.QImage.Format_RGBA8888)
148 painter.drawImage(rect_cpt, img)
150 c = cpt.color_nan
151 qcolor_nan = qg.QColor(*c if c is not None else (0, 0, 0))
152 qcolor_nan.setAlpha(255 if c is not None else 0)
154 painter.fillRect(rect_c_nan, qcolor_nan)
157class CPTStyleDelegate(qw.QItemDelegate):
159 def __init__(self, parent=None):
160 qw.QItemDelegate.__init__(self, parent)
162 def paint(self, painter, option, index):
163 data = index.model().data(index, qc.Qt.UserRole)
165 if isinstance(data, automap.CPT):
166 painter.save()
167 rect = option.rect
168 _paint_cpt_rect(painter, data, rect)
169 painter.restore()
171 else:
172 qw.QItemDelegate.paint(self, painter, option, index)
175class CPTComboBox(qw.QComboBox):
176 def __init__(self):
177 super().__init__()
179 self.setItemDelegate(CPTStyleDelegate(parent=self))
180 self.setInsertPolicy(qw.QComboBox.InsertAtBottom)
182 def paintEvent(self, e):
183 data = self.itemData(self.currentIndex(), qc.Qt.UserRole)
185 if isinstance(data, automap.CPT):
186 spainter = qw.QStylePainter(self)
187 spainter.setPen(self.palette().color(qg.QPalette.Text))
189 opt = qw.QStyleOptionComboBox()
190 self.initStyleOption(opt)
191 spainter.drawComplexControl(qw.QStyle.CC_ComboBox, opt)
193 painter = qg.QPainter(self)
194 painter.save()
196 rect = spainter.style().subElementRect(
197 qw.QStyle.SE_ComboBoxFocusRect, opt, self)
199 _paint_cpt_rect(painter, data, rect)
201 painter.restore()
203 else:
204 qw.QComboBox.paintEvent(self, e)
207class MyDockWidgetTitleBarButton(qw.QPushButton):
209 def __init__(self, *args, **kwargs):
210 qw.QPushButton.__init__(self, *args, **kwargs)
211 self.setFlat(True)
212 self.setSizePolicy(
213 qw.QSizePolicy.Fixed, qw.QSizePolicy.Fixed)
215 def sizeHint(self):
216 s = qw.QPushButton.sizeHint(self)
217 return qc.QSize(s.height(), s.height())
220class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton):
222 toggled = qc.pyqtSignal(bool)
224 def __init__(self, text_checked, text_unchecked, *args, **kwargs):
225 MyDockWidgetTitleBarButton.__init__(
226 self, text_checked, *args, **kwargs)
228 self._checked = True
229 self._text_checked = text_checked
230 self._text_unchecked = text_unchecked
231 self.update_text()
232 self.clicked.connect(self.toggle)
234 def set_checked(self, checked):
235 self._checked = checked
236 self.update_text()
238 def toggle(self):
239 self._checked = not self._checked
240 self.update_text()
241 self.toggled.emit(self._checked)
243 def update_text(self):
244 if self._checked:
245 self.setText(self._text_checked)
246 else:
247 self.setText(self._text_unchecked)
250class MyDockWidgetTitleBarLabel(qw.QLabel):
252 title_changed = qc.pyqtSignal()
253 representation_changed = qc.pyqtSignal()
255 def __init__(self, title, *args, **kwargs):
256 qw.QLabel.__init__(self, '', *args, **kwargs)
257 self._slug = ''
258 self._slug_abbreviated_length = 0
259 self._title = ''
260 self.set_title(title)
262 def set_title(self, title):
263 self._title = title
264 self.title_changed.emit()
265 self.update_text()
267 def get_title(self):
268 return self._title
270 def get_abbreviated_slug(self):
271 slug = self._slug[:self._slug_abbreviated_length]
272 if len(slug) != 0 and slug != self._slug:
273 return slug + '...'
274 else:
275 return slug
277 def update_text(self):
278 slug = self.get_abbreviated_slug()
279 self.setText('<strong>%s</strong>%s' % (
280 self._title,
281 ' <small>%s</small>' % slug if slug else ''))
282 self.representation_changed.emit()
284 def event(self, ev):
285 ev.ignore()
286 return qw.QLabel.event(self, ev)
288 def set_slug(self, slug):
289 self._slug = slug
290 self.setToolTip(slug)
291 self.update_text()
292 self.title_changed.emit()
294 def set_slug_abbreviated_length(self, n):
295 self._slug_abbreviated_length = n
296 self.update_text()
298 def get_slug(self):
299 return self._slug
301 def get_full_title(self):
302 slug = self._slug
303 return '%s%s' % (
304 self._title, ' [%s]' % slug if slug else '')
306 def get_abbreviated_title(self):
307 slug = self.get_abbreviated_slug()
308 return '%s%s' % (self._title, ' [%s]' % slug if slug else '')
311class MyDockWidgetTitleBar(qw.QFrame):
313 def __init__(self, title_label, title_controls=[]):
314 qw.QFrame.__init__(self)
316 if isinstance(title_label, str):
317 title_label = MyDockWidgetTitleBarLabel(title_label)
319 title_label.setSizePolicy(
320 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum)
322 self._title_label = title_label
324 button_hide = MyDockWidgetTitleBarButton('\u2501')
325 button_hide.setStatusTip('Hide Panel')
327 layout = qw.QGridLayout()
328 layout.setSpacing(0)
329 layout.addWidget(title_label, 0, 0)
330 layout.addWidget(button_hide, 0, 1)
331 for i, button in enumerate(title_controls):
332 layout.addWidget(button, 0, 2 + i)
334 self.setLayout(layout)
335 self.setBackgroundRole(qg.QPalette.Mid)
336 self.setAutoFillBackground(True)
337 self.button_hide = button_hide
339 def event(self, ev):
340 ev.ignore()
341 return qw.QFrame.event(self, ev)
344class MyDockWidget(qw.QDockWidget):
346 def __init__(self, parent, title_label, title_controls=[], **kwargs):
348 qw.QDockWidget.__init__(self, parent, **kwargs)
350 self.setFeatures(
351 qw.QDockWidget.DockWidgetClosable
352 | qw.QDockWidget.DockWidgetMovable
353 | qw.QDockWidget.DockWidgetFloatable
354 | qw.QDockWidget.DockWidgetClosable)
356 self._visible = False
357 self._blocked = False
359 tb = MyDockWidgetTitleBar(title_label, title_controls)
360 tb.button_hide.clicked.connect(self.hide)
361 self.setTitleBarWidget(tb)
362 self.titlebar = tb
364 def update_title():
365 lab = self.titlebar._title_label.get_abbreviated_title()
366 self.setWindowTitle(lab)
368 self.titlebar._title_label.representation_changed.connect(update_title)
369 update_title()
371 def setVisible(self, visible):
372 self._visible = visible
373 if not self._blocked:
374 qw.QDockWidget.setVisible(self, self._visible)
376 def show(self):
377 self.setVisible(True)
379 def hide(self):
380 self.setVisible(False)
382 def setBlocked(self, blocked):
383 self._blocked = blocked
384 if blocked:
385 qw.QDockWidget.setVisible(self, False)
386 else:
387 qw.QDockWidget.setVisible(self, self._visible)
389 def block(self):
390 self.setBlocked(True)
392 def unblock(self):
393 self.setBlocked(False)
396class MyDoubleSpinBox(qw.QDoubleSpinBox):
397 def __init__(self, *args, **kwargs):
398 qw.QDoubleSpinBox.__init__(self, *args, **kwargs)
400 self.setLocale(qc.QLocale(' English'))