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 

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 

23 

24 

25def get_palette(): 

26 return qw.QApplication.palette() 

27 

28 

29def errorize(widget): 

30 widget.setStyleSheet(''' 

31 QLineEdit { 

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

33 }''') 

34 

35 

36def de_errorize(widget): 

37 if isinstance(widget, qw.QWidget): 

38 widget.setStyleSheet('') 

39 

40 

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) 

45 

46 return cb 

47 

48 

49def string_choices_to_combobox(cls): 

50 return strings_to_combobox(cls.choices) 

51 

52 

53def time_or_none_to_str(t): 

54 if t is None: 

55 return '' 

56 else: 

57 return util.time_to_str(t) 

58 

59 

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

61 if step is None: 

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

63 

64 assert step <= 20. 

65 

66 def fl_major(x): 

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

68 

69 def ce_major(x): 

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

71 

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. 

78 

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

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

81 

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 

93 

94 return lat_min, lat_max, lon_min, lon_max, lon_closed 

95 

96 

97qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

98 qw.QFileDialog.DontUseSheet 

99 

100 

101def _paint_cpt_rect(painter, cpt, rect): 

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

103 

104 rect_cpt = copy.deepcopy(rect) 

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

106 

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) 

110 

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

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

113 

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 

118 

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

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

121 

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 

125 

126 if cpt.color_below: 

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

128 else: 

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

130 

131 if cpt.color_above: 

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

133 else: 

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

135 

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

137 interpolator = interp1d(levels, colors.T) 

138 

139 colors_interp = interpolator( 

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

141 

142 colors_interp = num.tile( 

143 colors_interp, rect_cpt.height()) 

144 

145 img = qg.QImage( 

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

147 qg.QImage.Format_RGBA8888) 

148 

149 painter.drawImage(rect_cpt, img) 

150 

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) 

154 

155 painter.fillRect(rect_c_nan, qcolor_nan) 

156 

157 

158class CPTStyleDelegate(qw.QItemDelegate): 

159 

160 def __init__(self, parent=None): 

161 qw.QItemDelegate.__init__(self, parent) 

162 

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

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

165 

166 if isinstance(data, automap.CPT): 

167 painter.save() 

168 rect = option.rect 

169 _paint_cpt_rect(painter, data, rect) 

170 painter.restore() 

171 

172 else: 

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

174 

175 

176class CPTComboBox(qw.QComboBox): 

177 def __init__(self): 

178 super().__init__() 

179 

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

181 self.setInsertPolicy(qw.QComboBox.InsertAtBottom) 

182 

183 def paintEvent(self, e): 

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

185 

186 if isinstance(data, automap.CPT): 

187 spainter = qw.QStylePainter(self) 

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

189 

190 opt = qw.QStyleOptionComboBox() 

191 self.initStyleOption(opt) 

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

193 

194 painter = qg.QPainter(self) 

195 painter.save() 

196 

197 rect = spainter.style().subElementRect( 

198 qw.QStyle.SE_ComboBoxFocusRect, opt, self) 

199 

200 _paint_cpt_rect(painter, data, rect) 

201 

202 painter.restore() 

203 

204 else: 

205 qw.QComboBox.paintEvent(self, e) 

206 

207 

208class MyDockWidgetTitleBarButton(qw.QPushButton): 

209 

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) 

215 

216 def sizeHint(self): 

217 s = qw.QPushButton.sizeHint(self) 

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

219 

220 

221class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton): 

222 

223 toggled = qc.pyqtSignal(bool) 

224 

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

226 MyDockWidgetTitleBarButton.__init__( 

227 self, text_checked, *args, **kwargs) 

228 

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) 

234 

235 def set_checked(self, checked): 

236 self._checked = checked 

237 self.update_text() 

238 

239 def toggle(self): 

240 self._checked = not self._checked 

241 self.update_text() 

242 self.toggled.emit(self._checked) 

243 

244 def update_text(self): 

245 if self._checked: 

246 self.setText(self._text_checked) 

247 else: 

248 self.setText(self._text_unchecked) 

249 

250 

251class MyDockWidgetTitleBarLabel(qw.QLabel): 

252 

253 title_changed = qc.pyqtSignal() 

254 representation_changed = qc.pyqtSignal() 

255 

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) 

262 

263 def set_title(self, title): 

264 self._title = title 

265 self.title_changed.emit() 

266 self.update_text() 

267 

268 def get_title(self): 

269 return self._title 

270 

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 

277 

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

284 

285 def event(self, ev): 

286 ev.ignore() 

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

288 

289 def set_slug(self, slug): 

290 self._slug = slug 

291 self.setToolTip(slug) 

292 self.update_text() 

293 self.title_changed.emit() 

294 

295 def set_slug_abbreviated_length(self, n): 

296 self._slug_abbreviated_length = n 

297 self.update_text() 

298 

299 def get_slug(self): 

300 return self._slug 

301 

302 def get_full_title(self): 

303 slug = self._slug 

304 return '%s%s' % ( 

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

306 

307 def get_abbreviated_title(self): 

308 slug = self.get_abbreviated_slug() 

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

310 

311 

312class MyDockWidgetTitleBar(qw.QFrame): 

313 

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

315 qw.QFrame.__init__(self) 

316 

317 if isinstance(title_label, str): 

318 title_label = MyDockWidgetTitleBarLabel(title_label) 

319 

320 title_label.setSizePolicy( 

321 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum) 

322 

323 self._title_label = title_label 

324 

325 button_hide = MyDockWidgetTitleBarButton('\u2501') 

326 button_hide.setStatusTip('Hide Panel') 

327 

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) 

334 

335 self.setLayout(layout) 

336 self.setBackgroundRole(qg.QPalette.Mid) 

337 self.setAutoFillBackground(True) 

338 self.button_hide = button_hide 

339 

340 def event(self, ev): 

341 ev.ignore() 

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

343 

344 

345class MyDockWidget(qw.QDockWidget): 

346 

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

348 

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

350 

351 self.setFeatures( 

352 qw.QDockWidget.DockWidgetClosable 

353 | qw.QDockWidget.DockWidgetMovable 

354 | qw.QDockWidget.DockWidgetFloatable 

355 | qw.QDockWidget.DockWidgetClosable) 

356 

357 self._visible = False 

358 self._blocked = False 

359 

360 tb = MyDockWidgetTitleBar(title_label, title_controls) 

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

362 self.setTitleBarWidget(tb) 

363 self.titlebar = tb 

364 

365 def update_title(): 

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

367 self.setWindowTitle(lab) 

368 

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

370 update_title() 

371 

372 def setVisible(self, visible): 

373 self._visible = visible 

374 if not self._blocked: 

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

376 

377 def show(self): 

378 self.setVisible(True) 

379 

380 def hide(self): 

381 self.setVisible(False) 

382 

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) 

389 

390 def block(self): 

391 self.setBlocked(True) 

392 

393 def unblock(self): 

394 self.setBlocked(False) 

395 

396 

397class MyDoubleSpinBox(qw.QDoubleSpinBox): 

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

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

400 

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