Coverage for /usr/local/lib/python3.13/dist-packages/pyrocko/gui/sparrow/common.py: 90%

264 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-12-04 10:41 +0000

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 

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

17 errorize, de_errorize, time_or_none_to_str, time_to_lineedit, \ 

18 lineedit_to_time # noqa 

19 

20 

21g_viewer = None 

22 

23 

24def get_viewer(): 

25 return g_viewer 

26 

27 

28def set_viewer(viewer): 

29 global g_viewer 

30 if viewer is not None and g_viewer: 

31 raise Exception('Global viewer object already set.') 

32 

33 g_viewer = viewer 

34 

35 

36def release_viewer(): 

37 set_viewer(None) 

38 

39 

40def get_err_palette(): 

41 err_palette = qg.QPalette() 

42 err_palette.setColor(qg.QPalette.Text, qg.QColor(255, 200, 200)) 

43 return err_palette 

44 

45 

46def get_palette(): 

47 return qw.QApplication.palette() 

48 

49 

50def strings_to_combobox(list_of_str): 

51 cb = qw.QComboBox() 

52 for i, s in enumerate(list_of_str): 

53 cb.insertItem(i, s) 

54 

55 return cb 

56 

57 

58def string_choices_to_combobox(cls): 

59 return strings_to_combobox(cls.choices) 

60 

61 

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

63 if step is None: 

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

65 

66 assert step <= 20. 

67 

68 def fl_major(x): 

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

70 

71 def ce_major(x): 

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

73 

74 if avoid_poles: 

75 lat_min_lim = -90. + step 

76 lat_max_lim = 90. - step 

77 else: 

78 lat_min_lim = -90. 

79 lat_max_lim = 90. 

80 

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

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

83 

84 lon_closed = False 

85 if abs(lat)+delta < 89.: 

86 factor = 1.0 / math.cos((abs(lat)+delta) * d2r) 

87 lon_min = fl_major(lon - delta * factor) 

88 lon_max = ce_major(lon + delta * factor) 

89 if lon_max >= lon_min + 360. - step*1e-5: 

90 lon_min, lon_max = -180., 180. - step 

91 lon_closed = True 

92 else: 

93 lon_min, lon_max = -180., 180. - step 

94 lon_closed = True 

95 

96 return lat_min, lat_max, lon_min, lon_max, lon_closed 

97 

98 

99qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

100 qw.QFileDialog.DontUseSheet 

101 

102 

103def _paint_cpt_rect(painter, cpt, rect): 

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

105 

106 rect_cpt = copy.deepcopy(rect) 

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

108 

109 rect_c_nan = copy.deepcopy(rect) 

110 rect_c_nan.setLeft(rect.left() + rect_cpt.width() + 4) 

111 rect_c_nan.setWidth(int(rect.width() * 0.1) - 2) 

112 

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

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

115 

116 for il, level in enumerate(cpt.levels): 

117 levels[il*2+2] = level.vmin + ( 

118 level.vmax - level.vmin) / rect_cpt.width() # ow interp errors 

119 levels[il*2+3] = level.vmax 

120 

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

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

123 

124 level_range = levels[-3] - levels[2] 

125 levels[0], levels[1] = levels[2] - level_range * 0.05, levels[2] 

126 levels[-2], levels[-1] = levels[-3], levels[-3] + level_range * 0.05 

127 

128 if cpt.color_below: 

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

130 else: 

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

132 

133 if cpt.color_above: 

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

135 else: 

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

137 

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

139 interpolator = interp1d(levels, colors.T) 

140 

141 colors_interp = interpolator( 

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

143 

144 colors_interp = num.tile( 

145 colors_interp, rect_cpt.height()) 

146 

147 img = qg.QImage( 

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

149 qg.QImage.Format_RGBA8888) 

150 

151 painter.drawImage(rect_cpt, img) 

152 

153 c = cpt.color_nan 

154 qcolor_nan = qg.QColor(*map(int, c) if c is not None else (0, 0, 0)) 

155 qcolor_nan.setAlpha(255 if c is not None else 0) 

156 

157 painter.fillRect(rect_c_nan, qcolor_nan) 

158 

159 

160class CPTStyleDelegate(qw.QItemDelegate): 

161 

162 def __init__(self, parent=None): 

163 qw.QItemDelegate.__init__(self, parent) 

164 

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

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

167 

168 if isinstance(data, automap.CPT): 

169 painter.save() 

170 rect = option.rect 

171 _paint_cpt_rect(painter, data, rect) 

172 painter.restore() 

173 

174 else: 

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

176 

177 

178class CPTComboBox(qw.QComboBox): 

179 def __init__(self): 

180 super().__init__() 

181 

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

183 self.setInsertPolicy(qw.QComboBox.InsertAtBottom) 

184 

185 def paintEvent(self, e): 

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

187 

188 if isinstance(data, automap.CPT): 

189 spainter = qw.QStylePainter(self) 

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

191 

192 opt = qw.QStyleOptionComboBox() 

193 self.initStyleOption(opt) 

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

195 

196 painter = qg.QPainter(self) 

197 painter.save() 

198 

199 rect = spainter.style().subElementRect( 

200 qw.QStyle.SE_ComboBoxFocusRect, opt, self) 

201 

202 _paint_cpt_rect(painter, data, rect) 

203 

204 painter.restore() 

205 

206 else: 

207 qw.QComboBox.paintEvent(self, e) 

208 

209 

210class MyDockWidgetTitleBarButton(qw.QPushButton): 

211 

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

213 qw.QPushButton.__init__(self, *args, **kwargs) 

214 self.setFlat(True) 

215 self.setSizePolicy( 

216 qw.QSizePolicy.Fixed, qw.QSizePolicy.Fixed) 

217 

218 def sizeHint(self): 

219 s = qw.QPushButton.sizeHint(self) 

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

221 

222 

223class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton): 

224 

225 toggled = qc.pyqtSignal(bool) 

226 

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

228 MyDockWidgetTitleBarButton.__init__( 

229 self, text_checked, *args, **kwargs) 

230 

231 self._checked = True 

232 self._text_checked = text_checked 

233 self._text_unchecked = text_unchecked 

234 self.update_text() 

235 self.clicked.connect(self.toggle) 

236 

237 def set_checked(self, checked): 

238 self._checked = checked 

239 self.update_text() 

240 

241 def toggle(self): 

242 self._checked = not self._checked 

243 self.update_text() 

244 self.toggled.emit(self._checked) 

245 

246 def update_text(self): 

247 if self._checked: 

248 self.setText(self._text_checked) 

249 else: 

250 self.setText(self._text_unchecked) 

251 

252 

253class MyDockWidgetTitleBarLabel(qw.QLabel): 

254 

255 title_changed = qc.pyqtSignal() 

256 representation_changed = qc.pyqtSignal() 

257 

258 def __init__(self, title, *args, **kwargs): 

259 qw.QLabel.__init__(self, '', *args, **kwargs) 

260 self._slug = '' 

261 self._slug_abbreviated_length = 0 

262 self._title = '' 

263 self.set_title(title) 

264 

265 def set_title(self, title): 

266 self._title = title 

267 self.title_changed.emit() 

268 self.update_text() 

269 

270 def get_title(self): 

271 return self._title 

272 

273 def get_abbreviated_slug(self): 

274 slug = self._slug[:self._slug_abbreviated_length] 

275 if len(slug) != 0 and slug != self._slug: 

276 return slug + '...' 

277 else: 

278 return slug 

279 

280 def update_text(self): 

281 slug = self.get_abbreviated_slug() 

282 self.setText('<strong>%s</strong>%s' % ( 

283 self._title, 

284 ' <small>%s</small>' % slug if slug else '')) 

285 self.representation_changed.emit() 

286 

287 def event(self, ev): 

288 ev.ignore() 

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

290 

291 def set_slug(self, slug): 

292 self._slug = slug 

293 self.setToolTip(slug) 

294 self.update_text() 

295 self.title_changed.emit() 

296 

297 def set_slug_abbreviated_length(self, n): 

298 self._slug_abbreviated_length = n 

299 self.update_text() 

300 

301 def get_slug(self): 

302 return self._slug 

303 

304 def get_full_title(self): 

305 slug = self._slug 

306 return '%s%s' % ( 

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

308 

309 def get_abbreviated_title(self): 

310 slug = self.get_abbreviated_slug() 

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

312 

313 

314class MyDockWidgetTitleBar(qw.QFrame): 

315 

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

317 qw.QFrame.__init__(self) 

318 

319 if isinstance(title_label, str): 

320 title_label = MyDockWidgetTitleBarLabel(title_label) 

321 

322 title_label.setSizePolicy( 

323 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum) 

324 

325 self._title_label = title_label 

326 

327 button_hide = MyDockWidgetTitleBarButton('\u2501') 

328 button_hide.setStatusTip('Hide Panel') 

329 

330 layout = qw.QGridLayout() 

331 layout.setSpacing(0) 

332 layout.addWidget(title_label, 0, 0) 

333 layout.addWidget(button_hide, 0, 1) 

334 for i, button in enumerate(title_controls): 

335 layout.addWidget(button, 0, 2 + i) 

336 

337 self.setLayout(layout) 

338 self.setBackgroundRole(qg.QPalette.Mid) 

339 self.setAutoFillBackground(True) 

340 self.button_hide = button_hide 

341 

342 def event(self, ev): 

343 ev.ignore() 

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

345 

346 

347class MyDockWidget(qw.QDockWidget): 

348 

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

350 

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

352 

353 self.setFeatures( 

354 qw.QDockWidget.DockWidgetClosable 

355 | qw.QDockWidget.DockWidgetMovable 

356 | qw.QDockWidget.DockWidgetFloatable 

357 | qw.QDockWidget.DockWidgetClosable) 

358 

359 self._visible = False 

360 self._blocked = False 

361 

362 tb = MyDockWidgetTitleBar(title_label, title_controls) 

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

364 self.setTitleBarWidget(tb) 

365 self.titlebar = tb 

366 

367 def update_title(): 

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

369 self.setWindowTitle(lab) 

370 

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

372 update_title() 

373 

374 def setVisible(self, visible): 

375 self._visible = visible 

376 if not self._blocked: 

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

378 

379 def show(self): 

380 self.setVisible(True) 

381 

382 def hide(self): 

383 self.setVisible(False) 

384 

385 def setBlocked(self, blocked): 

386 self._blocked = blocked 

387 if blocked: 

388 qw.QDockWidget.setVisible(self, False) 

389 else: 

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

391 

392 def block(self): 

393 self.setBlocked(True) 

394 

395 def unblock(self): 

396 self.setBlocked(False) 

397 

398 

399class MyScrollArea(qw.QScrollArea): 

400 

401 def sizeHint(self): 

402 s = qc.QSize() 

403 s.setWidth(self.widget().sizeHint().width()) 

404 s.setHeight(self.widget().sizeHint().height()) 

405 return s 

406 

407 def minimumSizeHint(self): 

408 s = qc.QSize() 

409 s.setWidth(self.widget().minimumSizeHint().width()) 

410 s.setHeight(0) 

411 return s 

412 

413 def resizeEvent(self, event): 

414 s = event.size() 

415 s.setHeight(self.widget().height()) 

416 self.widget().resize(s) 

417 

418 

419class MyDoubleSpinBox(qw.QDoubleSpinBox): 

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

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

422 

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