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

17 

18 

19g_viewer = None 

20 

21 

22def get_viewer(): 

23 return g_viewer 

24 

25 

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.') 

30 

31 g_viewer = viewer 

32 

33 

34def release_viewer(): 

35 set_viewer(None) 

36 

37 

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 

42 

43 

44def get_palette(): 

45 return qw.QApplication.palette() 

46 

47 

48def errorize(widget): 

49 widget.setStyleSheet(''' 

50 QLineEdit { 

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

52 }''') 

53 

54 

55def de_errorize(widget): 

56 if isinstance(widget, qw.QWidget): 

57 widget.setStyleSheet('') 

58 

59 

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) 

64 

65 return cb 

66 

67 

68def string_choices_to_combobox(cls): 

69 return strings_to_combobox(cls.choices) 

70 

71 

72def time_or_none_to_str(t): 

73 if t is None: 

74 return '' 

75 else: 

76 return util.time_to_str(t) 

77 

78 

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

80 if step is None: 

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

82 

83 assert step <= 20. 

84 

85 def fl_major(x): 

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

87 

88 def ce_major(x): 

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

90 

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. 

97 

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

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

100 

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 

112 

113 return lat_min, lat_max, lon_min, lon_max, lon_closed 

114 

115 

116qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

117 qw.QFileDialog.DontUseSheet 

118 

119 

120def _paint_cpt_rect(painter, cpt, rect): 

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

122 

123 rect_cpt = copy.deepcopy(rect) 

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

125 

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) 

129 

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

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

132 

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 

137 

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

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

140 

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 

144 

145 if cpt.color_below: 

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

147 else: 

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

149 

150 if cpt.color_above: 

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

152 else: 

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

154 

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

156 interpolator = interp1d(levels, colors.T) 

157 

158 colors_interp = interpolator( 

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

160 

161 colors_interp = num.tile( 

162 colors_interp, rect_cpt.height()) 

163 

164 img = qg.QImage( 

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

166 qg.QImage.Format_RGBA8888) 

167 

168 painter.drawImage(rect_cpt, img) 

169 

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) 

173 

174 painter.fillRect(rect_c_nan, qcolor_nan) 

175 

176 

177class CPTStyleDelegate(qw.QItemDelegate): 

178 

179 def __init__(self, parent=None): 

180 qw.QItemDelegate.__init__(self, parent) 

181 

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

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

184 

185 if isinstance(data, automap.CPT): 

186 painter.save() 

187 rect = option.rect 

188 _paint_cpt_rect(painter, data, rect) 

189 painter.restore() 

190 

191 else: 

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

193 

194 

195class CPTComboBox(qw.QComboBox): 

196 def __init__(self): 

197 super().__init__() 

198 

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

200 self.setInsertPolicy(qw.QComboBox.InsertAtBottom) 

201 

202 def paintEvent(self, e): 

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

204 

205 if isinstance(data, automap.CPT): 

206 spainter = qw.QStylePainter(self) 

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

208 

209 opt = qw.QStyleOptionComboBox() 

210 self.initStyleOption(opt) 

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

212 

213 painter = qg.QPainter(self) 

214 painter.save() 

215 

216 rect = spainter.style().subElementRect( 

217 qw.QStyle.SE_ComboBoxFocusRect, opt, self) 

218 

219 _paint_cpt_rect(painter, data, rect) 

220 

221 painter.restore() 

222 

223 else: 

224 qw.QComboBox.paintEvent(self, e) 

225 

226 

227class MyDockWidgetTitleBarButton(qw.QPushButton): 

228 

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) 

234 

235 def sizeHint(self): 

236 s = qw.QPushButton.sizeHint(self) 

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

238 

239 

240class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton): 

241 

242 toggled = qc.pyqtSignal(bool) 

243 

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

245 MyDockWidgetTitleBarButton.__init__( 

246 self, text_checked, *args, **kwargs) 

247 

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) 

253 

254 def set_checked(self, checked): 

255 self._checked = checked 

256 self.update_text() 

257 

258 def toggle(self): 

259 self._checked = not self._checked 

260 self.update_text() 

261 self.toggled.emit(self._checked) 

262 

263 def update_text(self): 

264 if self._checked: 

265 self.setText(self._text_checked) 

266 else: 

267 self.setText(self._text_unchecked) 

268 

269 

270class MyDockWidgetTitleBarLabel(qw.QLabel): 

271 

272 title_changed = qc.pyqtSignal() 

273 representation_changed = qc.pyqtSignal() 

274 

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) 

281 

282 def set_title(self, title): 

283 self._title = title 

284 self.title_changed.emit() 

285 self.update_text() 

286 

287 def get_title(self): 

288 return self._title 

289 

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 

296 

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

303 

304 def event(self, ev): 

305 ev.ignore() 

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

307 

308 def set_slug(self, slug): 

309 self._slug = slug 

310 self.setToolTip(slug) 

311 self.update_text() 

312 self.title_changed.emit() 

313 

314 def set_slug_abbreviated_length(self, n): 

315 self._slug_abbreviated_length = n 

316 self.update_text() 

317 

318 def get_slug(self): 

319 return self._slug 

320 

321 def get_full_title(self): 

322 slug = self._slug 

323 return '%s%s' % ( 

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

325 

326 def get_abbreviated_title(self): 

327 slug = self.get_abbreviated_slug() 

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

329 

330 

331class MyDockWidgetTitleBar(qw.QFrame): 

332 

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

334 qw.QFrame.__init__(self) 

335 

336 if isinstance(title_label, str): 

337 title_label = MyDockWidgetTitleBarLabel(title_label) 

338 

339 title_label.setSizePolicy( 

340 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum) 

341 

342 self._title_label = title_label 

343 

344 button_hide = MyDockWidgetTitleBarButton('\u2501') 

345 button_hide.setStatusTip('Hide Panel') 

346 

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) 

353 

354 self.setLayout(layout) 

355 self.setBackgroundRole(qg.QPalette.Mid) 

356 self.setAutoFillBackground(True) 

357 self.button_hide = button_hide 

358 

359 def event(self, ev): 

360 ev.ignore() 

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

362 

363 

364class MyDockWidget(qw.QDockWidget): 

365 

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

367 

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

369 

370 self.setFeatures( 

371 qw.QDockWidget.DockWidgetClosable 

372 | qw.QDockWidget.DockWidgetMovable 

373 | qw.QDockWidget.DockWidgetFloatable 

374 | qw.QDockWidget.DockWidgetClosable) 

375 

376 self._visible = False 

377 self._blocked = False 

378 

379 tb = MyDockWidgetTitleBar(title_label, title_controls) 

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

381 self.setTitleBarWidget(tb) 

382 self.titlebar = tb 

383 

384 def update_title(): 

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

386 self.setWindowTitle(lab) 

387 

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

389 update_title() 

390 

391 def setVisible(self, visible): 

392 self._visible = visible 

393 if not self._blocked: 

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

395 

396 def show(self): 

397 self.setVisible(True) 

398 

399 def hide(self): 

400 self.setVisible(False) 

401 

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) 

408 

409 def block(self): 

410 self.setBlocked(True) 

411 

412 def unblock(self): 

413 self.setBlocked(False) 

414 

415 

416class MyDoubleSpinBox(qw.QDoubleSpinBox): 

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

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

419 

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