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

283 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-10-04 09:52 +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, 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 time_to_lineedit(state, attribute, widget): 

80 widget.setText(time_or_none_to_str(getattr(state, attribute))) 

81 

82 

83def lineedit_to_time(widget, state, attribute): 

84 from pyrocko.util import str_to_time_fillup 

85 

86 s = str(widget.text()) 

87 if not s.strip(): 

88 setattr(state, attribute, None) 

89 else: 

90 try: 

91 setattr(state, attribute, str_to_time_fillup(s)) 

92 except Exception: 

93 raise ValueError( 

94 'Use time format: YYYY-MM-DD HH:MM:SS.FFF') 

95 

96 

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

98 if step is None: 

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

100 

101 assert step <= 20. 

102 

103 def fl_major(x): 

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

105 

106 def ce_major(x): 

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

108 

109 if avoid_poles: 

110 lat_min_lim = -90. + step 

111 lat_max_lim = 90. - step 

112 else: 

113 lat_min_lim = -90. 

114 lat_max_lim = 90. 

115 

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

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

118 

119 lon_closed = False 

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

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

122 lon_min = fl_major(lon - delta * factor) 

123 lon_max = ce_major(lon + delta * factor) 

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

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

126 lon_closed = True 

127 else: 

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

129 lon_closed = True 

130 

131 return lat_min, lat_max, lon_min, lon_max, lon_closed 

132 

133 

134qfiledialog_options = qw.QFileDialog.DontUseNativeDialog | \ 

135 qw.QFileDialog.DontUseSheet 

136 

137 

138def _paint_cpt_rect(painter, cpt, rect): 

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

140 

141 rect_cpt = copy.deepcopy(rect) 

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

143 

144 rect_c_nan = copy.deepcopy(rect) 

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

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

147 

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

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

150 

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

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

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

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

155 

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

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

158 

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

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

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

162 

163 if cpt.color_below: 

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

165 else: 

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

167 

168 if cpt.color_above: 

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

170 else: 

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

172 

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

174 interpolator = interp1d(levels, colors.T) 

175 

176 colors_interp = interpolator( 

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

178 

179 colors_interp = num.tile( 

180 colors_interp, rect_cpt.height()) 

181 

182 img = qg.QImage( 

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

184 qg.QImage.Format_RGBA8888) 

185 

186 painter.drawImage(rect_cpt, img) 

187 

188 c = cpt.color_nan 

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

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

191 

192 painter.fillRect(rect_c_nan, qcolor_nan) 

193 

194 

195class CPTStyleDelegate(qw.QItemDelegate): 

196 

197 def __init__(self, parent=None): 

198 qw.QItemDelegate.__init__(self, parent) 

199 

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

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

202 

203 if isinstance(data, automap.CPT): 

204 painter.save() 

205 rect = option.rect 

206 _paint_cpt_rect(painter, data, rect) 

207 painter.restore() 

208 

209 else: 

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

211 

212 

213class CPTComboBox(qw.QComboBox): 

214 def __init__(self): 

215 super().__init__() 

216 

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

218 self.setInsertPolicy(qw.QComboBox.InsertAtBottom) 

219 

220 def paintEvent(self, e): 

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

222 

223 if isinstance(data, automap.CPT): 

224 spainter = qw.QStylePainter(self) 

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

226 

227 opt = qw.QStyleOptionComboBox() 

228 self.initStyleOption(opt) 

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

230 

231 painter = qg.QPainter(self) 

232 painter.save() 

233 

234 rect = spainter.style().subElementRect( 

235 qw.QStyle.SE_ComboBoxFocusRect, opt, self) 

236 

237 _paint_cpt_rect(painter, data, rect) 

238 

239 painter.restore() 

240 

241 else: 

242 qw.QComboBox.paintEvent(self, e) 

243 

244 

245class MyDockWidgetTitleBarButton(qw.QPushButton): 

246 

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

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

249 self.setFlat(True) 

250 self.setSizePolicy( 

251 qw.QSizePolicy.Fixed, qw.QSizePolicy.Fixed) 

252 

253 def sizeHint(self): 

254 s = qw.QPushButton.sizeHint(self) 

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

256 

257 

258class MyDockWidgetTitleBarButtonToggle(MyDockWidgetTitleBarButton): 

259 

260 toggled = qc.pyqtSignal(bool) 

261 

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

263 MyDockWidgetTitleBarButton.__init__( 

264 self, text_checked, *args, **kwargs) 

265 

266 self._checked = True 

267 self._text_checked = text_checked 

268 self._text_unchecked = text_unchecked 

269 self.update_text() 

270 self.clicked.connect(self.toggle) 

271 

272 def set_checked(self, checked): 

273 self._checked = checked 

274 self.update_text() 

275 

276 def toggle(self): 

277 self._checked = not self._checked 

278 self.update_text() 

279 self.toggled.emit(self._checked) 

280 

281 def update_text(self): 

282 if self._checked: 

283 self.setText(self._text_checked) 

284 else: 

285 self.setText(self._text_unchecked) 

286 

287 

288class MyDockWidgetTitleBarLabel(qw.QLabel): 

289 

290 title_changed = qc.pyqtSignal() 

291 representation_changed = qc.pyqtSignal() 

292 

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

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

295 self._slug = '' 

296 self._slug_abbreviated_length = 0 

297 self._title = '' 

298 self.set_title(title) 

299 

300 def set_title(self, title): 

301 self._title = title 

302 self.title_changed.emit() 

303 self.update_text() 

304 

305 def get_title(self): 

306 return self._title 

307 

308 def get_abbreviated_slug(self): 

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

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

311 return slug + '...' 

312 else: 

313 return slug 

314 

315 def update_text(self): 

316 slug = self.get_abbreviated_slug() 

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

318 self._title, 

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

320 self.representation_changed.emit() 

321 

322 def event(self, ev): 

323 ev.ignore() 

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

325 

326 def set_slug(self, slug): 

327 self._slug = slug 

328 self.setToolTip(slug) 

329 self.update_text() 

330 self.title_changed.emit() 

331 

332 def set_slug_abbreviated_length(self, n): 

333 self._slug_abbreviated_length = n 

334 self.update_text() 

335 

336 def get_slug(self): 

337 return self._slug 

338 

339 def get_full_title(self): 

340 slug = self._slug 

341 return '%s%s' % ( 

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

343 

344 def get_abbreviated_title(self): 

345 slug = self.get_abbreviated_slug() 

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

347 

348 

349class MyDockWidgetTitleBar(qw.QFrame): 

350 

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

352 qw.QFrame.__init__(self) 

353 

354 if isinstance(title_label, str): 

355 title_label = MyDockWidgetTitleBarLabel(title_label) 

356 

357 title_label.setSizePolicy( 

358 qw.QSizePolicy.Expanding, qw.QSizePolicy.Minimum) 

359 

360 self._title_label = title_label 

361 

362 button_hide = MyDockWidgetTitleBarButton('\u2501') 

363 button_hide.setStatusTip('Hide Panel') 

364 

365 layout = qw.QGridLayout() 

366 layout.setSpacing(0) 

367 layout.addWidget(title_label, 0, 0) 

368 layout.addWidget(button_hide, 0, 1) 

369 for i, button in enumerate(title_controls): 

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

371 

372 self.setLayout(layout) 

373 self.setBackgroundRole(qg.QPalette.Mid) 

374 self.setAutoFillBackground(True) 

375 self.button_hide = button_hide 

376 

377 def event(self, ev): 

378 ev.ignore() 

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

380 

381 

382class MyDockWidget(qw.QDockWidget): 

383 

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

385 

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

387 

388 self.setFeatures( 

389 qw.QDockWidget.DockWidgetClosable 

390 | qw.QDockWidget.DockWidgetMovable 

391 | qw.QDockWidget.DockWidgetFloatable 

392 | qw.QDockWidget.DockWidgetClosable) 

393 

394 self._visible = False 

395 self._blocked = False 

396 

397 tb = MyDockWidgetTitleBar(title_label, title_controls) 

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

399 self.setTitleBarWidget(tb) 

400 self.titlebar = tb 

401 

402 def update_title(): 

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

404 self.setWindowTitle(lab) 

405 

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

407 update_title() 

408 

409 def setVisible(self, visible): 

410 self._visible = visible 

411 if not self._blocked: 

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

413 

414 def show(self): 

415 self.setVisible(True) 

416 

417 def hide(self): 

418 self.setVisible(False) 

419 

420 def setBlocked(self, blocked): 

421 self._blocked = blocked 

422 if blocked: 

423 qw.QDockWidget.setVisible(self, False) 

424 else: 

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

426 

427 def block(self): 

428 self.setBlocked(True) 

429 

430 def unblock(self): 

431 self.setBlocked(False) 

432 

433 

434class MyScrollArea(qw.QScrollArea): 

435 

436 def sizeHint(self): 

437 s = qc.QSize() 

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

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

440 return s 

441 

442 def minimumSizeHint(self): 

443 s = qc.QSize() 

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

445 s.setHeight(0) 

446 return s 

447 

448 def resizeEvent(self, event): 

449 s = event.size() 

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

451 self.widget().resize(s) 

452 

453 

454class MyDoubleSpinBox(qw.QDoubleSpinBox): 

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

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

457 

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