Browse Source

Pyqt5 (#214)

* PyQt5 migration started

* bugfix setup.py and snuffler

* PyQt5 migration

* Migrating QtWebKit to QtWebEngine

* Refering to PyQt5 in docs

* pile_viewer: improve redrawing and enable mouse wheel on pyqt5

* PyQt5 migration: add fallback to use QtWebKit if QtWebEngine not avail

* Qt5 migration: continued

* pyqt5 migration: fix open / save dialogs

* OpenGL fix for Nvidia driver

* PyQt5 migration // Updated test_gui

* Upd test_gui
python3-testing-appstore
Mi! 4 years ago
committed by GitHub
parent
commit
4f66a437e8
  1. 2
      .travis.yml
  2. 2
      doc/source/install/details.rst
  3. 9
      doc/source/install/system/anaconda2.rst
  4. 16
      doc/source/install/system/deb.rst
  5. 2
      doc/source/install/system/mac.rst
  6. 2
      src/dataset/topo/srtmgl3.py
  7. 10
      src/gui/marker.py
  8. 156
      src/gui/marker_editor.py
  9. 34
      src/gui/moment_tensor_viewer.py
  10. 422
      src/gui/pile_viewer.py
  11. 92
      src/gui/snuffler.py
  12. 188
      src/gui/snuffling.py
  13. 104
      src/gui/util.py
  14. 11
      src/util.py
  15. 2
      test/common.py
  16. 63
      test/test_gui.py

2
.travis.yml

@ -40,7 +40,7 @@ before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then xcode-select --install || true ; fi
- pip install --upgrade pip setuptools wheel
- pip install --only-binary=numpy,scipy,matplotlib scipy numpy matplotlib
- pip install progressbar setuptools flake8 pyyaml coveralls coverage jinja2 future coverage nose
- pip install progressbar setuptools flake8 pyyaml coveralls jinja2 future coverage nose
- git clone https://github.com/pyrocko/fomosto-qseis
- cd fomosto-qseis; autoreconf -i; ./configure; make; export PATH=$PATH:`pwd`/src; cd ..

2
doc/source/install/details.rst

@ -21,7 +21,7 @@ The following software packages must be installed before Pyrocko can be installe
* `SciPy <http://scipy.org/>`_
* `matplotlib <http://matplotlib.sourceforge.net/>`_
* `pyyaml <https://bitbucket.org/xi/pyyaml>`_
* `PyQt4 <http://www.riverbankcomputing.co.uk/software/pyqt/intro>`_ (only needed for the GUI apps)
* `PyQt5 <http://www.riverbankcomputing.co.uk/software/pyqt/intro>`_ (only needed for the GUI apps)
* `future <https://pypi.python.org/pypi/future>`_ (Python2/3 compatibility layer)
* `requests <http://docs.python-requests.org/en/master/>`_ (HTTP for Humans)

9
doc/source/install/system/anaconda2.rst

@ -1,9 +1,16 @@
Installation under Anaconda2
............................
* **Anaconda2** thru conda
conda install -c pyrocko pyrocko
More Information available at <https://anaconda.org/pyrocko/pyrocko>_
* **Anaconda2** (4.2.0)::
conda install pyqt=4 # downgrades some packages
conda install pyqt=5 # downgrades some packages
easy_install pyavl
pip install progressbar
cd `conda info --root`

16
doc/source/install/system/deb.rst

@ -1,18 +1,30 @@
Installation on Debian based systems (Debian, Ubuntu, Mint)
...........................................................
* **Ubuntu** (12.04.1 LTS), **Debian** (7 wheezy), **Mint** (13 Maya)::
* Python 2 Installation on **Ubuntu** (12.04.1 LTS), **Debian** (7 wheezy), **Debian** (8 jessie), **Mint** (13 Maya)::
sudo apt-get install make git python-dev python-setuptools\
sudo apt-get install python-numpy python-numpy-dev python-scipy python-matplotlib
sudo apt-get install python-pyqt5 python-pyqt5.qtwebengine python-pyqt5.qtopengl python-pyqt5.qtsvg
sudo apt-get install python-future python-requests
sudo apt-get install python-qt4 python-qt4-gl
sudo apt-get install python-yaml python-progressbar python-jinja2
cd ~/src/ # or wherever you keep your source packages
git clone git://github.com/pyrocko/pyrocko.git pyrocko
cd pyrocko
sudo python setup.py install
* Python 3 Installation on **Ubuntu** (12.04.1 LTS), **Debian** (7 wheezy), **Debian** (8 jessie), **Mint** (13 Maya)::
sudo apt-get install make git python3-dev python3-setuptools
sudo apt-get install python3-numpy python3-numpy-dev python3-scipy python3-matplotlib
sudo apt-get install python3-pyqt5 python3-pyqt5.qtwebengine python3-pyqt5.qtopengl python3-pyqt5.qtsvg
sudo apt-get install python3-yaml python3-progressbar python3-jinja2
sudo easy_install pyavl
cd ~/src/ # or wherever you keep your source packages
git clone git://github.com/pyrocko/pyrocko.git pyrocko
cd pyrocko
sudo python3 setup.py install
For instructions on how to install Pyrocko on other systems or if the
installation with the above procedure fails, see :doc:`index` or
:doc:`/install/details`.

2
doc/source/install/system/mac.rst

@ -10,7 +10,7 @@ Installation on Mac OS X systems
sudo port install py27-scipy
sudo port install py27-matplotlib
sudo port install py27-yaml
sudo port install py27-pyqt4
sudo port install py27-pyqt5
sudo port install py27-setuptools
sudo port install py27-jinja2
sudo port install py27-requests

2
src/dataset/topo/srtmgl3.py

@ -43,7 +43,7 @@ class SRTMGL3(dataset.TiledGlobalDataset):
self,
name='SRTMGL3',
data_dir=op.join(op.dirname(__file__), 'data', 'SRTMGL3'),
raw_data_url='http://e4ftl01.cr.usgs.gov/SRTM/SRTMGL3.003/'
raw_data_url='https://e4ftl01.cr.usgs.gov/SRTM/SRTMGL3.003/'
'2000.02.11'):
dataset.TiledGlobalDataset.__init__(

10
src/gui/marker.py

@ -353,8 +353,8 @@ class Marker(object):
draw_triangle=False,
**kwargs):
from PyQt4 import QtCore as qc
from PyQt4 import QtGui as qg
from PyQt5 import QtCore as qc
from PyQt5 import QtGui as qg
from . import util as gui_util
if self.selected or self.alerted or not self.nslc_ids:
@ -409,8 +409,8 @@ class Marker(object):
self, viewer, p, tr, time_projection, track_projection, gain,
outline_label=False):
from PyQt4 import QtCore as qc
from PyQt4 import QtGui as qg
from PyQt5 import QtCore as qc
from PyQt5 import QtGui as qg
from . import util as gui_util
if self.nslc_ids and not self.match_nslc(tr.nslc_id):
@ -584,7 +584,7 @@ class EventMarker(Marker):
self.draw_label(p, time_projection, y_projection)
def draw_label(self, p, time_projection, y_projection):
from PyQt4 import QtGui as qg
from PyQt5 import QtGui as qg
from . import util as gui_util
u = time_projection(self.tmin)

156
src/gui/marker_editor.py

@ -5,10 +5,9 @@
from __future__ import absolute_import
from builtins import zip, range
import sys
from PyQt4 import QtCore as qc
from PyQt4 import QtGui as qg
from PyQt5 import QtCore as qc
from PyQt5 import QtGui as qg
from PyQt5 import QtWidgets as qw
from .util import EventMarker, PhaseMarker, make_QPolygonF
from pyrocko.plot.beachball import mt2beachball, BeachballError
@ -24,37 +23,24 @@ def noop(x=None):
return x
if sys.version_info[0] >= 3:
qc.QString = str
qc.QVariant = noop
def toDateTime(val):
return val
def toDateTime(val):
return val
def isDateTime(val):
return isinstance(val, qc.QDateTime)
def toFloat(val):
try:
return float(val), True
except ValueError:
return 0.0, False
def isDateTime(val):
return isinstance(val, qc.QDateTime)
def toString(val):
return str(val)
else:
def toDateTime(val):
return val.toDateTime()
def toFloat(val):
try:
return float(val), True
except ValueError:
return 0.0, False
def isDateTime(val):
return val.type() == qc.QVariant.DateTime
def toFloat(val):
return val.toFloat()
def toString(val):
return str(val)
def toString(val):
return val.toString()
logger = logging.getLogger('pyrocko.gui.marker_editor')
@ -72,7 +58,7 @@ _header_sizes[1] = 190
_header_sizes[11] = 20
class BeachballWidget(qg.QWidget):
class BeachballWidget(qw.QWidget):
def __init__(self, moment_tensor=None, *args, **kwargs):
qg.QWidget.__init__(self, *args, **kwargs)
@ -118,11 +104,11 @@ class BeachballWidget(qg.QWidget):
return qg.QPixmap().grabWidget(self, self.rect())
class MarkerItemDelegate(qg.QStyledItemDelegate):
class MarkerItemDelegate(qw.QStyledItemDelegate):
'''Takes care of the table's style.'''
def __init__(self, *args, **kwargs):
qg.QStyledItemDelegate.__init__(self, *args, **kwargs)
qw.QStyledItemDelegate.__init__(self, *args, **kwargs)
self.c_alignment = qc.Qt.AlignHCenter
self.bbcache = qg.QPixmapCache()
@ -146,7 +132,7 @@ class MarkerItemDelegate(qg.QStyledItemDelegate):
if index.column() == _column_mapping['MT']:
mt = self.get_mt_from_index(index)
if mt:
key = qc.QString(''.join([str(round(x, 1)) for x in mt.m6()]))
key = ''.join([str(round(x, 1)) for x in mt.m6()])
pixmap = qg.QPixmap()
found = self.bbcache.find(key, pixmap)
if found:
@ -161,7 +147,7 @@ class MarkerItemDelegate(qg.QStyledItemDelegate):
painter.restore()
else:
qg.QStyledItemDelegate.paint(self, painter, option, index)
qw.QStyledItemDelegate.paint(self, painter, option, index)
def displayText(self, value, locale):
if isDateTime(value):
@ -180,11 +166,11 @@ class MarkerItemDelegate(qg.QStyledItemDelegate):
return None
class MarkerSortFilterProxyModel(qg.QSortFilterProxyModel):
class MarkerSortFilterProxyModel(qc.QSortFilterProxyModel):
'''Sorts the table's columns.'''
def __init__(self, *args, **kwargs):
qg.QSortFilterProxyModel.__init__(self, *args, **kwargs)
qc.QSortFilterProxyModel.__init__(self, *args, **kwargs)
self.sort(1, qc.Qt.DescendingOrder)
def lessThan(self, left, right):
@ -211,12 +197,12 @@ class MarkerSortFilterProxyModel(qg.QSortFilterProxyModel):
return qc.QVariant()
class MarkerTableView(qg.QTableView):
class MarkerTableView(qw.QTableView):
def __init__(self, *args, **kwargs):
qg.QTableView.__init__(self, *args, **kwargs)
self.setSelectionBehavior(qg.QAbstractItemView.SelectRows)
self.setHorizontalScrollMode(qg.QAbstractItemView.ScrollPerPixel)
self.setEditTriggers(qg.QAbstractItemView.DoubleClicked)
qw.QTableView.__init__(self, *args, **kwargs)
self.setSelectionBehavior(qw.QAbstractItemView.SelectRows)
self.setHorizontalScrollMode(qw.QAbstractItemView.ScrollPerPixel)
self.setEditTriggers(qw.QAbstractItemView.DoubleClicked)
self.setSortingEnabled(True)
self.setStyleSheet(
'QTableView{selection-background-color: \
@ -229,13 +215,10 @@ class MarkerTableView(qg.QTableView):
self.verticalHeader().hide()
self.pile_viewer = None
self.connect(self, qc.SIGNAL('clicked(QModelIndex)'), self.clicked)
self.connect(
self,
qc.SIGNAL('doubleClicked(QModelIndex)'),
self.double_clicked)
self.clicked.connect(self.table_clicked)
self.doubleClicked.connect(self.table_double_clicked)
self.header_menu = qg.QMenu(self)
self.header_menu = qw.QMenu(self)
show_initially = ['Type', 'Time', 'Magnitude']
self.menu_labels = ['Type', 'Time', 'Magnitude', 'Label', 'Depth [km]',
@ -248,30 +231,30 @@ class MarkerTableView(qg.QTableView):
self.column_actions = {}
for hd in self.menu_labels:
a = qg.QAction(qc.QString(hd), self.header_menu)
self.connect(a, qc.SIGNAL('triggered(bool)'), self.toggle_columns)
a = qw.QAction(hd, self.header_menu)
a.triggered.connect(
self.toggle_columns)
a.setCheckable(True)
a.setChecked(hd in show_initially)
self.header_menu.addAction(a)
self.column_actions[hd] = a
a = qg.QAction('Numbering', self.header_menu)
a = qw.QAction('Numbering', self.header_menu)
a.setCheckable(True)
a.setChecked(False)
self.connect(a, qc.SIGNAL('triggered(bool)'), self.toggle_numbering)
a.triggered.connect(
self.toggle_numbering)
self.header_menu.addAction(a)
header = self.horizontalHeader()
header.setContextMenuPolicy(qc.Qt.CustomContextMenu)
self.connect(
header,
qc.SIGNAL('customContextMenuRequested(QPoint)'),
header.customContextMenuRequested.connect(
self.show_context_menu)
self.active_event_index = None
self.right_click_menu = qg.QMenu(self)
print_action = qg.QAction('Print Table', self.right_click_menu)
self.right_click_menu = qw.QMenu(self)
print_action = qw.QAction('Print Table', self.right_click_menu)
print_action.triggered.connect(self.print_menu)
self.right_click_menu.addAction(print_action)
@ -299,17 +282,17 @@ class MarkerTableView(qg.QTableView):
def keyPressEvent(self, key_event):
'''Propagate ``key_event`` to pile_viewer, unless up/down pressed.'''
if key_event.key() in [qc.Qt.Key_Up, qc.Qt.Key_Down]:
qg.QTableView.keyPressEvent(self, key_event)
qw.QTableView.keyPressEvent(self, key_event)
self.pile_viewer.go_to_selection()
else:
self.pile_viewer.keyPressEvent(key_event)
def clicked(self, model_index):
def table_clicked(self, model_index):
'''Ignore mouse clicks.'''
pass
def contextMenuEvent(self, event):
self.right_click_menu.popup(qg.QCursor.pos())
self.right_click_menu.popup(qw.QCursor.pos())
def toggle_numbering(self, want):
if want:
@ -340,7 +323,7 @@ class MarkerTableView(qg.QTableView):
painter.end()
self.setVerticalScrollBarPolicy(scrollbarpolicy)
def double_clicked(self, model_index):
def table_double_clicked(self, model_index):
if model_index.column() in self.editable_columns:
return
else:
@ -396,16 +379,13 @@ class MarkerTableModel(qc.QAbstractTableModel):
'''Set a pile_viewer and connect to signals.'''
self.pile_viewer = viewer
self.connect(self.pile_viewer,
qc.SIGNAL('markers_added(int,int)'),
self.pile_viewer.markers_added.connect(
self.markers_added)
self.connect(self.pile_viewer,
qc.SIGNAL('markers_removed(int, int)'),
self.pile_viewer.markers_removed.connect(
self.markers_removed)
self.connect(self.pile_viewer,
qc.SIGNAL('changed_marker_selection'),
self.pile_viewer.changed_marker_selection.connect(
self.update_distances_and_angles)
def rowCount(self, parent):
@ -445,9 +425,9 @@ class MarkerTableModel(qc.QAbstractTableModel):
elif index.column() == _column_mapping['T']:
if isinstance(marker, EventMarker):
s = qc.QString('E')
s = 'E'
elif isinstance(marker, PhaseMarker):
s = qc.QString('P')
s = 'P'
elif index.column() == _column_mapping['M']:
if isinstance(marker, EventMarker):
@ -459,9 +439,9 @@ class MarkerTableModel(qc.QAbstractTableModel):
elif index.column() == _column_mapping['Label']:
if isinstance(marker, EventMarker):
s = qc.QString(marker.label())
s = marker.label()
elif isinstance(marker, PhaseMarker):
s = qc.QString(marker.get_label())
s = marker.get_label()
elif index.column() == _column_mapping['Depth [km]']:
if isinstance(marker, EventMarker):
@ -575,12 +555,12 @@ class MarkerTableModel(qc.QAbstractTableModel):
istart = self.index(0, _column_mapping['Dist [km]'])
istop = self.index(nmarkers-1, _column_mapping['Kagan Angle [deg]'])
self.emit(qc.SIGNAL('dataChanged(QModelIndex, QModelIndex)'),
self.dataChanged.emit(
istart,
istop)
def done(self):
self.emit(qc.SIGNAL('dataChanged'))
self.dataChanged.emit()
return True
def setData(self, index, value, role):
@ -646,11 +626,11 @@ class MarkerTableModel(qc.QAbstractTableModel):
return qc.Qt.ItemFlags(33)
class MarkerEditor(qg.QFrame):
class MarkerEditor(qw.QFrame):
def __init__(self, *args, **kwargs):
qg.QFrame.__init__(self, *args, **kwargs)
layout = qg.QGridLayout()
qw.QFrame.__init__(self, *args, **kwargs)
layout = qw.QGridLayout()
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
self.marker_table_view = MarkerTableView(self)
@ -669,16 +649,14 @@ class MarkerEditor(qg.QFrame):
header = self.marker_table_view.horizontalHeader()
for i_s, s in enumerate(_header_sizes):
header.setResizeMode(i_s, qg.QHeaderView.Interactive)
header.setSectionResizeMode(i_s, qw.QHeaderView.Interactive)
header.resizeSection(i_s, s)
header.setStretchLastSection(True)
self.selection_model = qg.QItemSelectionModel(self.proxy_filter)
self.selection_model = qc.QItemSelectionModel(self.proxy_filter)
self.marker_table_view.setSelectionModel(self.selection_model)
self.connect(
self.selection_model,
qc.SIGNAL('selectionChanged(QItemSelection,QItemSelection)'),
self.selection_model.selectionChanged.connect(
self.set_selected_markers)
layout.addWidget(self.marker_table_view, 0, 0)
@ -692,14 +670,10 @@ class MarkerEditor(qg.QFrame):
self.pile_viewer = viewer
self.marker_model.set_viewer(viewer)
self.marker_table_view.set_viewer(viewer)
self.connect(
self.pile_viewer,
qc.SIGNAL('changed_marker_selection'),
self.pile_viewer.changed_marker_selection.connect(
self.update_selection_model)
self.connect(
self.pile_viewer,
qc.SIGNAL('active_event_marker_changed(int)'),
self.pile_viewer.active_event_marker_changed.connect(
self.marker_table_view.set_active_event_index)
self.marker_table_view.toggle_columns()
@ -725,17 +699,17 @@ class MarkerEditor(qg.QFrame):
:param indices: list of indices of selected markers.'''
self.selection_model.clearSelection()
selections = qg.QItemSelection()
selection_flags = qg.QItemSelectionModel.SelectionFlags(
(qg.QItemSelectionModel.Select |
qg.QItemSelectionModel.Rows |
qg.QItemSelectionModel.Current))
selections = qc.QItemSelection()
selection_flags = qc.QItemSelectionModel.SelectionFlags(
(qc.QItemSelectionModel.Select |
qc.QItemSelectionModel.Rows |
qc.QItemSelectionModel.Current))
for chunk in indices:
mi_start = self.marker_model.index(min(chunk), 0)
mi_stop = self.marker_model.index(max(chunk), 0)
row_selection = self.proxy_filter.mapSelectionFromSource(
qg.QItemSelection(mi_start, mi_stop))
qc.QItemSelection(mi_start, mi_stop))
selections.merge(row_selection, selection_flags)
if len(indices) != 0:

34
src/gui/moment_tensor_viewer.py

@ -4,8 +4,9 @@
# ---|P------/S----------~Lg----------
from __future__ import absolute_import
from PyQt4 import QtCore as qc
from PyQt4 import QtGui as qg
from PyQt5 import QtCore as qc
from PyQt5 import QtGui as qg
from PyQt5 import QtWidgets as qw
from .util import make_QPolygonF, LinValControl
from .pile_viewer import Projection
@ -14,10 +15,10 @@ from pyrocko import beachball, moment_tensor as mtm
from pyrocko import plot
class BeachballView(qg.QWidget):
class BeachballView(qw.QWidget):
def __init__(self, *args):
qg.QWidget.__init__(self, *args)
qw.QWidget.__init__(self, *args)
mt = mtm.MomentTensor(m=mtm.symmat6(1., -1., 2., 0., -2., 1.))
self._mt = mt
self.set_moment_tensor(mt)
@ -29,8 +30,8 @@ class BeachballView(qg.QWidget):
def paintEvent(self, paint_ev):
'''Called by QT whenever widget needs to be painted.'''
painter = qg.QPainter(self)
painter.setRenderHint(qg.QPainter.Antialiasing)
painter = qw.QPainter(self)
painter.setRenderHint(qw.QPainter.Antialiasing)
self.drawit(painter)
def drawit(self, p):
@ -65,10 +66,10 @@ class BeachballView(qg.QWidget):
lines, lines_lower, lines_upper) in beachball.eig2gx(eig):
color = group_to_color[group]
brush = qg.QBrush(qg.QColor(*color))
brush = qw.QBrush(qw.QColor(*color))
p.setBrush(brush)
pen = qg.QPen(qg.QColor(*color))
pen = qw.QPen(qw.QColor(*color))
pen.setWidth(1)
p.setPen(pen)
@ -78,7 +79,7 @@ class BeachballView(qg.QWidget):
p.drawPolygon(points)
color = (0, 0, 0)
pen = qg.QPen(qg.QColor(*color))
pen = qw.QPen(qw.QColor(*color))
pen.setWidth(2)
p.setPen(pen)
@ -88,10 +89,12 @@ class BeachballView(qg.QWidget):
p.drawPolyline(points)
class MomentTensorEditor(qg.QFrame):
class MomentTensorEditor(qw.QFrame):
moment_tensor_changed = qc.pyqtSignal(object)
def __init__(self, *args):
qg.QFrame.__init__(self, *args)
qw.QFrame.__init__(self, *args)
self._mt = mtm.MomentTensor(m=mtm.symmat6(1., -1., 2., 0., -2., 1.))
@ -103,7 +106,7 @@ class MomentTensorEditor(qg.QFrame):
(LinValControl, 'Dip 2', 0., 90., 0., 4),
(LinValControl, 'Slip-Rake 2', -180., 180., 0., 5)]
layout = qg.QGridLayout()
layout = qw.QGridLayout()
self.setLayout(layout)
val_controls = []
@ -113,8 +116,7 @@ class MomentTensorEditor(qg.QFrame):
val_controls.append(val_control)
for icol, widget in enumerate(val_control.widgets()):
layout.addWidget(widget, irow, icol)
self.connect(
val_control, qc.SIGNAL('valchange(PyQt_PyObject,int)'),
val_control.valchanged.connect(
self.valchange)
self.val_controls = val_controls
@ -139,5 +141,5 @@ class MomentTensorEditor(qg.QFrame):
self.adjust_values()
self.emit(
qc.SIGNAL('moment_tensor_changed(PyQt_PyObject)'), self._mt)
self.moment_tensor_changed.emit(
self._mt)

422
src/gui/pile_viewer.py

@ -36,11 +36,12 @@ from .util import (ValControl, LinValControl, Marker, EventMarker,
PhaseMarker, make_QPolygonF, draw_label, Label,
Progressbars)
from PyQt4 import QtCore as qc
from PyQt4 import QtGui as qg
from PyQt4 import QtOpenGL as qgl
from PyQt4 import QtSvg as qsvg
# from PyQt4.QtSvg import *
from PyQt5 import QtCore as qc
from PyQt5 import QtGui as qg
from PyQt5 import QtWidgets as qw
from PyQt5 import QtOpenGL as qgl
from PyQt5 import QtSvg as qsvg
# from PyQt5.QtSvg import *
import scipy.stats as sstats
import platform
@ -49,10 +50,10 @@ if sys.version_info[0] >= 3:
qc.QString = str
if platform.mac_ver() != ('', ('', '', ''), ''):
qfiledialog_options = qg.QFileDialog.DontUseNativeDialog
qfiledialog_options = qw.QFileDialog.DontUseNativeDialog
macosx = True
else:
qfiledialog_options = qg.QFileDialog.DontUseSheet
qfiledialog_options = qw.QFileDialog.DontUseSheet
macosx = False
logger = logging.getLogger('pyrocko.gui.pile_viewer')
@ -537,7 +538,7 @@ class TimeAx(TimeScaler):
ushift = 0.
for l0x in (l0, l0_brief, ''):
label0 = qc.QString(l0x)
label0 = l0x
rect0 = fm.boundingRect(label0)
if rect0.width() <= pinc_approx*0.9:
break
@ -552,7 +553,7 @@ class TimeAx(TimeScaler):
vmin+rect0.height()+ticklen), label0)
if l1:
label1 = qc.QString(l1)
label1 = l1
rect1 = fm.boundingRect(label1)
if uumin+pad < umin-rect1.width()/2. and \
umin+rect1.width()/2. < uumax-pad:
@ -565,7 +566,7 @@ class TimeAx(TimeScaler):
l1_hits += 1
if l2:
label2 = qc.QString(l2)
label2 = l2
rect2 = fm.boundingRect(label2)
if uumin+pad < umin-rect2.width()/2. and \
umin+rect2.width()/2. < uumax-pad:
@ -592,7 +593,7 @@ class TimeAx(TimeScaler):
l1 = None
if l1_hits == 0 and l1:
label1 = qc.QString(l1)
label1 = l1
rect1 = fm.boundingRect(label1)
p.drawText(qc.QPointF(
uumin+pad,
@ -602,7 +603,7 @@ class TimeAx(TimeScaler):
l1_hits += 1
if l2_hits == 0 and l2:
label2 = qc.QString(l2)
label2 = l2
rect2 = fm.boundingRect(label2)
p.drawText(qc.QPointF(
uumin+pad,
@ -656,14 +657,14 @@ class Projection(object):
def add_radiobuttongroup(menu, menudef, obj, target, default=None):
group = qg.QActionGroup(menu)
group = qw.QActionGroup(menu)
menuitems = []
for l, v in menudef:
k = qg.QAction(l, menu)
k = qw.QAction(l, menu)
group.addAction(k)
menu.addAction(k)
k.setCheckable(True)
obj.connect(group, qc.SIGNAL('triggered(QAction*)'), target)
group.triggered.connect(target)
menuitems.append((k, v))
if default is not None and l == default:
k.setChecked(True)
@ -701,8 +702,20 @@ def MakePileViewerMainClass(base):
class PileViewerMain(base):
want_input = qc.pyqtSignal()
about_to_close = qc.pyqtSignal()
pile_has_changed_signal = qc.pyqtSignal()
tracks_range_changed = qc.pyqtSignal(int, int, int)
markers_added = qc.pyqtSignal(int, int)
markers_removed = qc.pyqtSignal(int, int)
changed_marker_selection = qc.pyqtSignal(list)
active_event_marker_changed = qc.pyqtSignal(int)
def __init__(self, pile, ntracks_shown_max, panel_parent, *args):
if base == qgl.QGLWidget:
from OpenGL import GL # noqa
base.__init__(
self, qgl.QGLFormat(qgl.QGL.SampleBuffers), *args)
else:
@ -742,45 +755,43 @@ def MakePileViewerMainClass(base):
self.tax = TimeAx()
self.setBackgroundRole(qg.QPalette.Base)
self.setAutoFillBackground(True)
poli = qg.QSizePolicy(
qg.QSizePolicy.Expanding,
qg.QSizePolicy.Expanding)
poli = qw.QSizePolicy(
qw.QSizePolicy.Expanding,
qw.QSizePolicy.Expanding)
self.setSizePolicy(poli)
self.setMinimumSize(300, 200)
self.setFocusPolicy(qc.Qt.StrongFocus)
self.menu = qg.QMenu(self)
self.menu = qw.QMenu(self)
mi = qg.QAction('Open waveform files...', self.menu)
mi = qw.QAction('Open waveform files...', self.menu)
self.menu.addAction(mi)
self.connect(mi, qc.SIGNAL("triggered(bool)"), self.open_waveforms)
mi.triggered.connect(self.open_waveforms)
mi = qg.QAction('Open waveform directory...', self.menu)
mi = qw.QAction('Open waveform directory...', self.menu)
self.menu.addAction(mi)
self.connect(
mi, qc.SIGNAL("triggered(bool)"), self.open_waveform_directory)
mi.triggered.connect(self.open_waveform_directory)
mi = qg.QAction('Open station files...', self.menu)
mi = qw.QAction('Open station files...', self.menu)
self.menu.addAction(mi)
self.connect(mi, qc.SIGNAL("triggered(bool)"), self.open_stations)
mi.triggered.connect(self.open_stations)
mi = qg.QAction('Write markers...', self.menu)
mi = qw.QAction('Save markers...', self.menu)
self.menu.addAction(mi)
self.connect(mi, qc.SIGNAL("triggered(bool)"), self.write_markers)
mi.triggered.connect(self.write_markers)
mi = qg.QAction('Write selected markers...', self.menu)
mi = qw.QAction('Save selected markers...', self.menu)
self.menu.addAction(mi)
self.connect(
mi, qc.SIGNAL("triggered(bool)"), self.write_selected_markers)
mi.triggered.connect(self.write_selected_markers)
mi = qg.QAction('Read markers...', self.menu)
mi = qw.QAction('Open marker file...', self.menu)
self.menu.addAction(mi)
self.connect(mi, qc.SIGNAL("triggered(bool)"), self.read_markers)
mi.triggered.connect(self.read_markers)
mi = qg.QAction('Read events...', self.menu)
mi = qw.QAction('Open event file...', self.menu)
self.menu.addAction(mi)
self.connect(mi, qc.SIGNAL("triggered(bool)"), self.read_events)
mi.triggered.connect(self.read_events)
self.menu.addSeparator()
@ -855,12 +866,10 @@ def MakePileViewerMainClass(base):
self._ssort = lambda tr: ()
self.menuitem_distances_3d = qg.QAction('3D distances', self.menu)
self.menuitem_distances_3d = qw.QAction('3D distances', self.menu)
self.menuitem_distances_3d.setCheckable(True)
self.menuitem_distances_3d.setChecked(False)
self.connect(
self.menuitem_distances_3d,
qc.SIGNAL("toggled(bool)"),
self.menuitem_distances_3d.toggled.connect(
self.distances_3d_changed)
self.menu.addAction(self.menuitem_distances_3d)
@ -909,84 +918,84 @@ def MakePileViewerMainClass(base):
self.menu.addSeparator()
self.menuitem_antialias = qg.QAction('Antialiasing', self.menu)
self.menuitem_antialias = qw.QAction('Antialiasing', self.menu)
self.menuitem_antialias.setCheckable(True)
self.menu.addAction(self.menuitem_antialias)
self.menuitem_liberal_fetch = qg.QAction(
self.menuitem_liberal_fetch = qw.QAction(
'Liberal Fetch Optimization', self.menu)
self.menuitem_liberal_fetch.setCheckable(True)
self.menu.addAction(self.menuitem_liberal_fetch)
self.menuitem_cliptraces = qg.QAction('Clip Traces', self.menu)
self.menuitem_cliptraces = qw.QAction('Clip Traces', self.menu)
self.menuitem_cliptraces.setCheckable(True)
self.menuitem_cliptraces.setChecked(True)
self.menu.addAction(self.menuitem_cliptraces)
self.menuitem_showboxes = qg.QAction('Show Boxes', self.menu)
self.menuitem_showboxes = qw.QAction('Show Boxes', self.menu)
self.menuitem_showboxes.setCheckable(True)
self.menuitem_showboxes.setChecked(True)
self.menu.addAction(self.menuitem_showboxes)
self.menuitem_colortraces = qg.QAction('Color Traces', self.menu)
self.menuitem_colortraces = qw.QAction('Color Traces', self.menu)
self.menuitem_colortraces.setCheckable(True)
self.menuitem_colortraces.setChecked(False)
self.menu.addAction(self.menuitem_colortraces)
self.menuitem_showscalerange = qg.QAction(
self.menuitem_showscalerange = qw.QAction(
'Show Scale Ranges', self.menu)
self.menuitem_showscalerange.setCheckable(True)
self.menu.addAction(self.menuitem_showscalerange)
self.menuitem_showscaleaxis = qg.QAction(
self.menuitem_showscaleaxis = qw.QAction(
'Show Scale Axes', self.menu)
self.menuitem_showscaleaxis.setCheckable(True)
self.menu.addAction(self.menuitem_showscaleaxis)
self.menuitem_showzeroline = qg.QAction(
self.menuitem_showzeroline = qw.QAction(
'Show Zero Lines', self.menu)
self.menuitem_showzeroline.setCheckable(True)
self.menu.addAction(self.menuitem_showzeroline)
self.menuitem_fixscalerange = qg.QAction(
self.menuitem_fixscalerange = qw.QAction(
'Fix Scale Ranges', self.menu)
self.menuitem_fixscalerange.setCheckable(True)
self.menu.addAction(self.menuitem_fixscalerange)
self.menuitem_allowdownsampling = qg.QAction(
self.menuitem_allowdownsampling = qw.QAction(
'Allow Downsampling', self.menu)
self.menuitem_allowdownsampling.setCheckable(True)
self.menuitem_allowdownsampling.setChecked(True)
self.menu.addAction(self.menuitem_allowdownsampling)
self.menuitem_degap = qg.QAction('Allow Degapping', self.menu)
self.menuitem_degap = qw.QAction('Allow Degapping', self.menu)
self.menuitem_degap.setCheckable(True)
self.menuitem_degap.setChecked(True)
self.menu.addAction(self.menuitem_degap)
self.menuitem_demean = qg.QAction('Demean', self.menu)
self.menuitem_demean = qw.QAction('Demean', self.menu)
self.menuitem_demean.setCheckable(True)
self.menuitem_demean.setChecked(True)
self.menu.addAction(self.menuitem_demean)
self.menuitem_fft_filtering = qg.QAction(
self.menuitem_fft_filtering = qw.QAction(
'FFT Filtering', self.menu)
self.menuitem_fft_filtering.setCheckable(True)
self.menuitem_fft_filtering.setChecked(False)
self.menu.addAction(self.menuitem_fft_filtering)
self.menuitem_lphp = qg.QAction(
self.menuitem_lphp = qw.QAction(
'Bandpass is Lowpass + Highpass', self.menu)
self.menuitem_lphp.setCheckable(True)
self.menuitem_lphp.setChecked(True)
self.menu.addAction(self.menuitem_lphp)
self.menuitem_watch = qg.QAction('Watch Files', self.menu)
self.menuitem_watch = qw.QAction('Watch Files', self.menu)
self.menuitem_watch.setCheckable(True)
self.menuitem_watch.setChecked(False)
self.menu.addAction(self.menuitem_watch)
self.visible_length_menu = qg.QMenu('Visible Length', self.menu)
self.visible_length_menu = qw.QMenu('Visible Length', self.menu)
menudef = [(x.key, x.value) for x in
self.config.visible_length_setting]
@ -999,70 +1008,58 @@ def MakePileViewerMainClass(base):
self.menu.addMenu(self.visible_length_menu)
self.menu.addSeparator()
self.snufflings_menu = qg.QMenu('Run Snuffling', self.menu)
self.snufflings_menu = qw.QMenu('Run Snuffling', self.menu)
self.menu.addMenu(self.snufflings_menu)
self.toggle_panel_menu = qg.QMenu('Panels', self.menu)
self.toggle_panel_menu = qw.QMenu('Panels', self.menu)
self.menu.addMenu(self.toggle_panel_menu)
self.menuitem_reload = qg.QAction('Reload Snufflings', self.menu)
self.menuitem_reload = qw.QAction('Reload Snufflings', self.menu)
self.menu.addAction(self.menuitem_reload)
self.connect(
self.menuitem_reload,
qc.SIGNAL("triggered(bool)"),
self.menuitem_reload.triggered.connect(
self.setup_snufflings)
self.menu.addSeparator()
self.menuitem_test = qg.QAction('Test', self.menu)
self.menuitem_test.setCheckable(True)
self.menuitem_test.setChecked(False)
self.menu.addAction(self.menuitem_test)
self.connect(
self.menuitem_test,
qc.SIGNAL("toggled(bool)"),
self.toggletest)
# Disable ShadowPileTest
if False:
self.menuitem_test = qw.QAction('Test', self.menu)
self.menuitem_test.setCheckable(True)
self.menuitem_test.setChecked(False)
self.menu.addAction(self.menuitem_test)
self.menuitem_test.triggered.connect(
self.toggletest)
self.menuitem_print = qg.QAction('Print', self.menu)
self.menuitem_print = qw.QAction('Print', self.menu)
self.menu.addAction(self.menuitem_print)
self.connect(
self.menuitem_print,
qc.SIGNAL("triggered(bool)"),
self.menuitem_print.triggered.connect(
self.printit)
self.menuitem_svg = qg.QAction('Save as SVG|PNG', self.menu)
self.menuitem_svg = qw.QAction('Save as SVG|PNG', self.menu)
self.menu.addAction(self.menuitem_svg)
self.connect(
self.menuitem_svg,
qc.SIGNAL("triggered(bool)"),
self.menuitem_svg.triggered.connect(
self.savesvg)
self.snuffling_help_menu = qg.QMenu('Help', self.menu)
self.snuffling_help_menu = qw.QMenu('Help', self.menu)
self.menu.addMenu(self.snuffling_help_menu)
self.menuitem_help = qg.QAction(
self.menuitem_help = qw.QAction(
'Snuffler Controls', self.snuffling_help_menu)
self.snuffling_help_menu.addAction(self.menuitem_help)
self.connect(
self.menuitem_help, qc.SIGNAL('triggered(bool)'), self.help)
self.menuitem_help.triggered.connect(self.help)
self.snuffling_help_menu.addSeparator()
self.menuitem_about = qg.QAction('About', self.menu)
self.menuitem_about = qw.QAction('About', self.menu)
self.menu.addAction(self.menuitem_about)
self.connect(
self.menuitem_about, qc.SIGNAL('triggered(bool)'), self.about)
self.menuitem_about.triggered.connect(self.about)
self.menuitem_close = qg.QAction('Close', self.menu)
self.menuitem_close = qw.QAction('Close', self.menu)
self.menu.addAction(self.menuitem_close)
self.connect(
self.menuitem_close,
qc.SIGNAL("triggered(bool)"),
self.myclose)
self.menuitem_close.triggered.connect(self.myclose)
self.menu.addSeparator()
self.connect(
self.menu, qc.SIGNAL('triggered(QAction*)'), self.update)
self.menu.triggered.connect(self.update)
self.time_projection = Projection()
self.set_time_range(self.pile.get_tmin(), self.pile.get_tmax())
@ -1082,7 +1079,7 @@ def MakePileViewerMainClass(base):
self.old_processed_traces = None
self.timer = qc.QTimer(self)
self.connect(self.timer, qc.SIGNAL("timeout()"), self.periodical)
self.timer.timeout.connect(self.periodical)
self.timer.setInterval(1000)
self.timer.start()
self.pile.add_listener(self)
@ -1101,6 +1098,9 @@ def MakePileViewerMainClass(base):
self.timer_draw = Timer()
self.timer_cutout = Timer()
self.time_spent_painting = 0.0
self.time_last_painted = time.time()
self.timer_update_soon = None
self.interactive_range_change_time = 0.0
self.interactive_range_change_delay_time = 10.0
@ -1124,8 +1124,16 @@ def MakePileViewerMainClass(base):
self.closing = False
def update(self):
self.timer_update_soon = None
qw.QWidget.update(self)
def update_soon(self):
if not self.timer_update_soon:
self.timer_update_soon = qc.QTimer.singleShot(50, self.update)
def fail(self, reason):
box = qg.QMessageBox(self)
box = qw.QMessageBox(self)
box.setText(reason)
box.exec_()
@ -1273,7 +1281,7 @@ def MakePileViewerMainClass(base):
if self.active_event_marker:
self.active_event_marker.set_active(False)
self.emit(qc.SIGNAL('active_event_marker_changed(int)'), -1)
self.active_event_marker_changed.emit(-1)
def set_active_event_marker(self, event_marker):
if self.active_event_marker:
@ -1285,7 +1293,7 @@ def MakePileViewerMainClass(base):
self.set_origin(event)
i_active = self.markers.index(self.active_event_marker)
self.emit(qc.SIGNAL('active_event_marker_changed(int)'), i_active)
self.active_event_marker_changed.emit(i_active)
def set_active_event(self, event):
for marker in self.markers:
@ -1447,7 +1455,7 @@ def MakePileViewerMainClass(base):
def update_progress(label, i, n):
abort = False
qg.qApp.processEvents()
qw.qApp.processEvents()
if n != 0:
perc = i*100/n
else:
@ -1486,29 +1494,29 @@ def MakePileViewerMainClass(base):
self._paths_to_load.extend(paths)
qc.QTimer.singleShot(200, self.load_queued)
def open_waveforms(self, _=None):
def open_waveforms(self):
caption = 'Select one or more files to open'
fns = qg.QFileDialog.getOpenFileNames(
fns, _ = qw.QFileDialog.getOpenFileNames(
self, caption, options=qfiledialog_options)
self.load(list(str(fn) for fn in fns))
def open_waveform_directory(self, _=None):
if fns:
self.load(list(str(fn) for fn in fns))
def open_waveform_directory(self):
caption = 'Select directory to scan for waveform files'
fn = qg.QFileDialog.getExistingDirectory(
dn = qw.QFileDialog.getExistingDirectory(
self, caption, options=qfiledialog_options)
self.load([str(fn)])
if dn:
self.load([str(dn)])
def open_stations(self, fns=None):
caption = 'Select one or more files to open'
if not fns:
fns = qg.QFileDialog.getOpenFileNames(
fns, _ = qw.QFileDialog.getOpenFileNames(
self, caption, options=qfiledialog_options)
stations = [pyrocko.model.load_stations(str(x)) for x in fns]
@ -1540,7 +1548,7 @@ def MakePileViewerMainClass(base):
def pile_changed(self, what):
self.pile_has_changed = True
self.emit(qc.SIGNAL('pile_has_changed_signal()'))
self.pile_has_changed_signal.emit()
if self.automatic_updates:
self.update()
@ -1662,7 +1670,7 @@ def MakePileViewerMainClass(base):
def write_markers(self, fn=None):
caption = "Choose a file name to write markers"
if not fn:
fn = qg.QFileDialog.getSaveFileName(
fn, _ = qw.QFileDialog.getSaveFileName(
self, caption, options=qfiledialog_options)
if fn:
Marker.save_markers(
@ -1672,7 +1680,7 @@ def MakePileViewerMainClass(base):
def write_selected_markers(self, fn=None):
caption = "Choose a file name to write selected markers"
if not fn:
fn = qg.QFileDialog.getSaveFileName(
fn, _ = qw.QFileDialog.getSaveFileName(
self, caption, options=qfiledialog_options)
if fn:
Marker.save_markers(
@ -1687,7 +1695,7 @@ def MakePileViewerMainClass(base):
'''
caption = "Selet one or more files to open"
if not fn:
fn = qg.QFileDialog.getOpenFileName(
fn, _ = qw.QFileDialog.getOpenFileName(
self, caption, options=qfiledialog_options)
if fn:
self.add_events(pyrocko.model.Event.load_catalog(fn))
@ -1700,7 +1708,7 @@ def MakePileViewerMainClass(base):
'''
caption = "Selet one or more files to open"
if not fn:
fn = qg.QFileDialog.getOpenFileName(
fn, _ = qw.QFileDialog.getOpenFileName(
self, caption, options=qfiledialog_options)
if fn:
self.add_markers(Marker.load_markers(fn))
@ -1713,16 +1721,15 @@ def MakePileViewerMainClass(base):
def add_marker(self, marker):
if marker not in self.markers:
self.markers.append(marker)
self.emit(
qc.SIGNAL('markers_added(int,int)'),
self.markers_added.emit(
len(self.markers)-1, len(self.markers)-1)
def add_markers(self, markers):
len_before = len(self.markers)
for m in markers:
self.markers.append(m)
self.emit(
qc.SIGNAL('markers_added(int,int)'),
if m not in self.markers:
self.markers.append(m)
self.markers_added.emit(
len_before, len(self.markers)-1)
def remove_marker(self, marker):
@ -1766,7 +1773,7 @@ def MakePileViewerMainClass(base):
pass
def remove_marker_from_menu(self, istart, istop):
self.emit(qc.SIGNAL('markers_removed(int, int)'), istart, istop)
self.markers_removed.emit(istart, istop)
def set_markers(self, markers):
self.markers = markers
@ -1811,6 +1818,10 @@ def MakePileViewerMainClass(base):
if self.picking:
self.stop_picking(mouse_ev.x(), mouse_ev.y())
self.emit_selected_markers()
if self.track_start:
self.update()
self.track_start = None
self.track_trange = None
self.update_status()
@ -1845,7 +1856,11 @@ def MakePileViewerMainClass(base):
tmin0 - dt - dtr*frac,
tmax0 - dt + dtr*(1.-frac))
self.update()
if self.time_last_painted < time.time() \
- self.time_spent_painting * 2.:
self.update()
else:
self.update_soon()
else:
self.hoovering(point.x(), point.y())
@ -2085,7 +2100,7 @@ def MakePileViewerMainClass(base):
self.zoom_tracks(0., dtracks)
elif keytext == ':':
self.emit(qc.SIGNAL('want_input()'))
self.want_input.emit()
elif keytext == 'f':
if self.window().windowState() & qc.Qt.WindowFullScreen or \
@ -2138,7 +2153,7 @@ def MakePileViewerMainClass(base):
_indexes.sort()
_indexes = make_chunks(_indexes)
self.emit(qc.SIGNAL('changed_marker_selection'), _indexes)
self.changed_marker_selection.emit(_indexes)
def toggle_marker_editor(self):
self.panel_parent.toggle_marker_editor()
@ -2158,12 +2173,12 @@ def MakePileViewerMainClass(base):
fn = pyrocko.util.data_file('snuffler.png')
with open(pyrocko.util.data_file('snuffler_about.html')) as f:
txt = f.read()
label = qg.QLabel(txt % {'logo': fn})
label = qw.QLabel(txt % {'logo': fn})
label.setAlignment(qc.Qt.AlignVCenter | qc.Qt.AlignHCenter)
self.show_doc('About', [label], target='tab')
def help(self):
class MyScrollArea(qg.QScrollArea):
class MyScrollArea(qw.QScrollArea):
def sizeHint(self):
s = qc.QSize()
@ -2173,11 +2188,11 @@ def MakePileViewerMainClass(base):
with open(pyrocko.util.data_file(
'snuffler_help.html')) as f:
hcheat = qg.QLabel(f.read())
hcheat = qw.QLabel(f.read())
with open(pyrocko.util.data_file(
'snuffler_help_epilog.html')) as f:
hepilog = qg.QLabel(f.read())
hepilog = qw.QLabel(f.read())
for h in [hcheat, hepilog]:
h.setAlignment(qc.Qt.AlignTop | qc.Qt.AlignHCenter)
@ -2186,10 +2201,10 @@ def MakePileViewerMainClass(base):
self.show_doc('Help', [hcheat, hepilog], target='panel')
def show_doc(self, name, labels, target='panel'):
scroller = qg.QScrollArea()
frame = qg.QFrame(scroller)
scroller = qw.QScrollArea()
frame = qw.QFrame(scroller)
frame.setLineWidth(0)
layout = qg.QVBoxLayout()
layout = qw.QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
frame.setLayout(layout)
@ -2203,8 +2218,8 @@ def MakePileViewerMainClass(base):
qc.Qt.LinksAccessibleByMouse | qc.Qt.TextSelectableByMouse)
h.setBackgroundRole(qg.QPalette.Base)
layout.addWidget(h)
frame.connect(
h, qc.SIGNAL('linkActivated(QString)'), self.open_link)
h.linkActivated.connect(
self.open_link)
if self.panel_parent is not None:
if target == 'panel':
@ -2217,7 +2232,7 @@ def MakePileViewerMainClass(base):
qg.QDesktopServices.openUrl(qc.QUrl(link))
def wheelEvent(self, wheel_event):
self.wheel_pos += wheel_event.delta()
self.wheel_pos += wheel_event.angleDelta().y()
n = self.wheel_pos // 120
self.wheel_pos = self.wheel_pos % 120
if n == 0:
@ -2281,9 +2296,7 @@ def MakePileViewerMainClass(base):
else:
l, h = 0, self.ntracks
self.emit(
qc.SIGNAL('tracks_range_changed(int,int,int)'),
self.ntracks, l, h)
self.tracks_range_changed.emit(self.ntracks, l, h)
def set_tracks_range(self, range, start=None):
@ -2300,9 +2313,7 @@ def MakePileViewerMainClass(base):
self.shown_tracks_range = l, h
self.shown_tracks_start = start
self.emit(
qc.SIGNAL('tracks_range_changed(int,int,int)'),
self.ntracks, l, h)
self.tracks_range_changed.emit(self.ntracks, l, h)
def scroll_tracks(self, shift):
shown = self.shown_tracks_range
@ -2427,13 +2438,14 @@ def MakePileViewerMainClass(base):
self.set_time_range(tmin, tmax)
def printit(self):
printer = qg.QPrinter()
printer.setOrientation(qg.QPrinter.Landscape)