1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6import copy 

7 

8import math 

9import numpy as num 

10from scipy.interpolate import interp1d 

11 

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 

16 

17 

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 

22 

23 

24def get_palette(): 

25 return qw.QApplication.palette() 

26 

27 

28def errorize(widget): 

29 widget.setStyleSheet(''' 

30 QLineEdit { 

31 background: rgb(200, 150, 150); 

32 }''') 

33 

34 

35def de_errorize(widget): 

36 if isinstance(widget, qw.QWidget): 

37 widget.setStyleSheet('') 

38 

39 

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) 

44 

45 return cb 

46 

47 

48def string_choices_to_combobox(cls): 

49 return strings_to_combobox(cls.choices) 

50 

51 

52def time_or_none_to_str(t): 

53 if t is None: 

54 return '' 

55 else: 

56 return util.time_to_str(t) 

57 

58 

59def cover_region(lat, lon, delta, step=None, avoid_poles=False): 

60 if step is None: 

61 step = plot.nice_value(delta / 10.) 

62 

63 assert step <= 20. 

64 

65 def fl_major(x): 

66 return math.floor(x / step) * step 

67 

68 def ce_major(x): 

69 return math.ceil(x / step) * step 

70 

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. 

77 

78 lat_min = max(lat_min_lim, fl_major(lat - delta)) 

79 lat_max = min(lat_max_lim, ce_major(lat + delta)) 

80 

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 

92 

93 return lat_min, lat_max, lon_min, lon_max, lon_closed 

94 

95 

96qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

97 qw.QFileDialog.DontUseSheet 

98 

99 

100def _paint_cpt_rect(painter, cpt, rect): 

101 rect.adjust(+5, +2, -5, -2) 

102 

103 rect_cpt = copy.deepcopy(rect) 

104 rect_cpt.setWidth(int(rect.width() * 0.9) - 2) 

105 

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) 

109 

110 levels = num.zeros(len(cpt.levels) * 2 + 4) 

111 colors = num.ones((levels.shape[0], 4)) * 255 

112 

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 

117 

118 colors[il*2+2, :3] = level.color_min 

119 colors[il*2+3, :3] = level.color_max 

120 

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 

124 

125 if cpt.color_below: 

126 colors[:2, :3] = cpt.color_below 

127 else: 

128 colors[:2] = (0, 0, 0, 0) 

129 

130 if cpt.color_above: 

131 colors[-2:, :3] = cpt.color_above 

132 else: 

133 colors[-2:] = (0, 0, 0, 0) 

134 

135 levels_interp = num.linspace(levels[0], levels[-1], rect_cpt.width()) 

136 interpolator = interp1d(levels, colors.T) 

137 

138 colors_interp = interpolator( 

139 levels_interp).T.astype(num.uint8).tobytes() 

140 

141 colors_interp = num.tile( 

142 colors_interp, rect_cpt.height()) 

143 

144 img = qg.QImage( 

145 colors_interp, rect_cpt.width(), rect_cpt.height(), 

146 qg.QImage.Format_RGBA8888) 

147 

148 painter.drawImage(rect_cpt, img) 

149 

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) 

153 

154 painter.fillRect(rect_c_nan, qcolor_nan) 

155 

156 

157class CPTStyleDelegate(qw.QItemDelegate): 

158 

159 def __init__(self, parent=None): 

160 qw.QItemDelegate.__init__(self, parent) 

161 

162 def paint(self, painter, option, index): 

163 data = index.model().data(index, qc.Qt.UserRole) 

164 

165 if isinstance(data, automap.CPT): 

166 painter.save() 

167 rect = option.rect 

168 _paint_cpt_rect(painter, data, rect) 

169 painter.restore() 

170 

171 else: 

172 qw.QItemDelegate.paint(self, painter, option, index) 

173 

174 

175class CPTComboBox(qw.QComboBox): 

176 def __init__(self): 

177 super().__init__() 

178 

179 self.setItemDelegate(CPTStyleDelegate(parent=self)) 

180 self.setInsertPolicy(qw.QComboBox.InsertAtBottom) 

181 

182 def paintEvent(self, e): 

183 data = self.itemData(self.currentIndex(), qc.Qt.UserRole) 

184 

185 if isinstance(data, automap.CPT): 

186 spainter = qw.QStylePainter(self) 

187 spainter.setPen(self.palette().color(qg.QPalette.Text)) 

188 

189 opt = qw.QStyleOptionComboBox() 

190 self.initStyleOption(opt) 

191 spainter.drawComplexControl(qw.QStyle.CC_ComboBox, opt) 

192 

193 painter = qg.QPainter(self) 

194 painter.save() 

195 

196 rect = spainter.style().subElementRect( 

197 qw.QStyle.SE_ComboBoxFocusRect, opt, self) 

198 

199 _paint_cpt_rect(painter, data, rect) 

200 

201 painter.restore() 

202 

203 else: 

204 qw.QComboBox.paintEvent(self, e) 

205 

206 

207class MyDockWidgetTitleBarButton(qw.QPushButton): 

208 

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) 

214 

215 def sizeHint(self): 

216 s = qw.QPushButton.sizeHint(self) 

217 return qc.QSize(s.height(), s.height()) 

218 

219 

220class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton): 

221 

222 toggled = qc.pyqtSignal(bool) 

223 

224 def __init__(self, text_checked, text_unchecked, *args, **kwargs): 

225 MyDockWidgetTitleBarButton.__init__( 

226 self, text_checked, *args, **kwargs) 

227 

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) 

233 

234 def set_checked(self, checked): 

235 self._checked = checked 

236 self.update_text() 

237 

238 def toggle(self): 

239 self._checked = not self._checked 

240 self.update_text() 

241 self.toggled.emit(self._checked) 

242 

243 def update_text(self): 

244 if self._checked: 

245 self.setText(self._text_checked) 

246 else: 

247 self.setText(self._text_unchecked) 

248 

249 

250class MyDockWidgetTitleBarLabel(qw.QLabel): 

251 

252 title_changed = qc.pyqtSignal() 

253 representation_changed = qc.pyqtSignal() 

254 

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) 

261 

262 def set_title(self, title): 

263 self._title = title 

264 self.title_changed.emit() 

265 self.update_text() 

266 

267 def get_title(self): 

268 return self._title 

269 

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 

276 

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() 

283 

284 def event(self, ev): 

285 ev.ignore() 

286 return qw.QLabel.event(self, ev) 

287 

288 def set_slug(self, slug): 

289 self._slug = slug 

290 self.setToolTip(slug) 

291 self.update_text() 

292 self.title_changed.emit() 

293 

294 def set_slug_abbreviated_length(self, n): 

295 self._slug_abbreviated_length = n 

296 self.update_text() 

297 

298 def get_slug(self): 

299 return self._slug 

300 

301 def get_full_title(self): 

302 slug = self._slug 

303 return '%s%s' % ( 

304 self._title, ' [%s]' % slug if slug else '') 

305 

306 def get_abbreviated_title(self): 

307 slug = self.get_abbreviated_slug() 

308 return '%s%s' % (self._title, ' [%s]' % slug if slug else '') 

309 

310 

311class MyDockWidgetTitleBar(qw.QFrame): 

312 

313 def __init__(self, title_label, title_controls=[]): 

314 qw.QFrame.__init__(self) 

315 

316 if isinstance(title_label, str): 

317 title_label = MyDockWidgetTitleBarLabel(title_label) 

318 

319 title_label.setSizePolicy( 

320 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum) 

321 

322 self._title_label = title_label 

323 

324 button_hide = MyDockWidgetTitleBarButton('-') 

325 button_hide.setStatusTip('Hide Panel') 

326 

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) 

333 

334 self.setLayout(layout) 

335 self.setBackgroundRole(qg.QPalette.Mid) 

336 self.setAutoFillBackground(True) 

337 self.button_hide = button_hide 

338 

339 def event(self, ev): 

340 ev.ignore() 

341 return qw.QFrame.event(self, ev) 

342 

343 

344class MyDockWidget(qw.QDockWidget): 

345 

346 def __init__(self, parent, title_label, title_controls=[], **kwargs): 

347 

348 qw.QDockWidget.__init__(self, parent, **kwargs) 

349 

350 self.setFeatures( 

351 qw.QDockWidget.DockWidgetClosable 

352 | qw.QDockWidget.DockWidgetMovable 

353 | qw.QDockWidget.DockWidgetFloatable 

354 | qw.QDockWidget.DockWidgetClosable) 

355 

356 self._visible = False 

357 self._blocked = False 

358 

359 tb = MyDockWidgetTitleBar(title_label, title_controls) 

360 tb.button_hide.clicked.connect(self.hide) 

361 self.setTitleBarWidget(tb) 

362 self.titlebar = tb 

363 

364 def update_title(): 

365 lab = self.titlebar._title_label.get_abbreviated_title() 

366 self.setWindowTitle(lab) 

367 

368 self.titlebar._title_label.representation_changed.connect(update_title) 

369 update_title() 

370 

371 def setVisible(self, visible): 

372 self._visible = visible 

373 if not self._blocked: 

374 qw.QDockWidget.setVisible(self, self._visible) 

375 

376 def show(self): 

377 self.setVisible(True) 

378 

379 def hide(self): 

380 self.setVisible(False) 

381 

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) 

388 

389 def block(self): 

390 self.setBlocked(True) 

391 

392 def unblock(self): 

393 self.setBlocked(False) 

394 

395 

396class MyDoubleSpinBox(qw.QDoubleSpinBox): 

397 def __init__(self, *args, **kwargs): 

398 qw.QDoubleSpinBox.__init__(self, *args, **kwargs) 

399 

400 self.setLocale(qc.QLocale(' English'))