1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5'''
6Effective seismological trace viewer.
7'''
8from __future__ import absolute_import
10import os
11import sys
12import signal
13import logging
14import time
15import re
16import zlib
17import struct
18import pickle
21from pyrocko.streaming import serial_hamster
22from pyrocko.streaming import slink
23from pyrocko.streaming import edl
24from pyrocko.streaming import datacube
26from pyrocko import pile # noqa
27from pyrocko import util # noqa
28from pyrocko import model # noqa
29from pyrocko import config # noqa
30from pyrocko import io # noqa
32from . import pile_viewer # noqa
34from .qt_compat import qc, qg, qw, qn
36logger = logging.getLogger('pyrocko.gui.snuffler_app')
39class AcquisitionThread(qc.QThread):
40 def __init__(self, post_process_sleep=0.0):
41 qc.QThread.__init__(self)
42 self.mutex = qc.QMutex()
43 self.queue = []
44 self.post_process_sleep = post_process_sleep
45 self._sun_is_shining = True
47 def run(self):
48 while True:
49 try:
50 self.acquisition_start()
51 while self._sun_is_shining:
52 t0 = time.time()
53 self.process()
54 t1 = time.time()
55 if self.post_process_sleep != 0.0:
56 time.sleep(max(0, self.post_process_sleep-(t1-t0)))
58 self.acquisition_stop()
59 break
61 except (
62 edl.ReadError,
63 serial_hamster.SerialHamsterError,
64 slink.SlowSlinkError) as e:
66 logger.error(str(e))
67 logger.error('Acquistion terminated, restart in 5 s')
68 self.acquisition_stop()
69 time.sleep(5)
70 if not self._sun_is_shining:
71 break
73 def stop(self):
74 self._sun_is_shining = False
76 logger.debug("Waiting for thread to terminate...")
77 self.wait()
78 logger.debug("Thread has terminated.")
80 def got_trace(self, tr):
81 self.mutex.lock()
82 self.queue.append(tr)
83 self.mutex.unlock()
85 def poll(self):
86 self.mutex.lock()
87 items = self.queue[:]
88 self.queue[:] = []
89 self.mutex.unlock()
90 return items
93class SlinkAcquisition(
94 slink.SlowSlink, AcquisitionThread):
96 def __init__(self, *args, **kwargs):
97 slink.SlowSlink.__init__(self, *args, **kwargs)
98 AcquisitionThread.__init__(self)
100 def got_trace(self, tr):
101 AcquisitionThread.got_trace(self, tr)
104class CamAcquisition(
105 serial_hamster.CamSerialHamster, AcquisitionThread):
107 def __init__(self, *args, **kwargs):
108 serial_hamster.CamSerialHamster.__init__(self, *args, **kwargs)
109 AcquisitionThread.__init__(self, post_process_sleep=0.1)
111 def got_trace(self, tr):
112 AcquisitionThread.got_trace(self, tr)
115class USBHB628Acquisition(
116 serial_hamster.USBHB628Hamster, AcquisitionThread):
118 def __init__(self, deltat=0.02, *args, **kwargs):
119 serial_hamster.USBHB628Hamster.__init__(
120 self, deltat=deltat, *args, **kwargs)
121 AcquisitionThread.__init__(self)
123 def got_trace(self, tr):
124 AcquisitionThread.got_trace(self, tr)
127class SchoolSeismometerAcquisition(
128 serial_hamster.SerialHamster, AcquisitionThread):
130 def __init__(self, *args, **kwargs):
131 serial_hamster.SerialHamster.__init__(self, *args, **kwargs)
132 AcquisitionThread.__init__(self, post_process_sleep=0.01)
134 def got_trace(self, tr):
135 AcquisitionThread.got_trace(self, tr)
138class EDLAcquisition(
139 edl.EDLHamster, AcquisitionThread):
141 def __init__(self, *args, **kwargs):
142 edl.EDLHamster.__init__(self, *args, **kwargs)
143 AcquisitionThread.__init__(self)
145 def got_trace(self, tr):
146 AcquisitionThread.got_trace(self, tr)
149class CubeAcquisition(
150 datacube.SerialCube, AcquisitionThread):
152 def __init__(self, *args, **kwargs):
153 datacube.SerialCube.__init__(self, *args, **kwargs)
154 AcquisitionThread.__init__(self)
156 def got_trace(self, tr):
157 AcquisitionThread.got_trace(self, tr)
160def setup_acquisition_sources(args):
162 sources = []
163 iarg = 0
164 while iarg < len(args):
165 arg = args[iarg]
167 msl = re.match(r'seedlink://([a-zA-Z0-9.-]+)(:(\d+))?(/(.*))?', arg)
168 mca = re.match(r'cam://([^:]+)', arg)
169 mus = re.match(r'hb628://([^:?]+)(\?([^?]+))?', arg)
170 msc = re.match(r'school://([^:]+)', arg)
171 med = re.match(r'edl://([^:]+)', arg)
172 mcu = re.match(r'cube://([^:]+)', arg)
174 if msl:
175 host = msl.group(1)
176 port = msl.group(3)
177 if not port:
178 port = '18000'
180 sl = SlinkAcquisition(host=host, port=port)
181 if msl.group(5):
182 stream_patterns = msl.group(5).split(',')
184 if '_' not in msl.group(5):
185 try:
186 streams = sl.query_streams()
187 except slink.SlowSlinkError as e:
188 logger.fatal(str(e))
189 sys.exit(1)
191 streams = list(set(
192 util.match_nslcs(stream_patterns, streams)))
194 for stream in streams:
195 sl.add_stream(*stream)
196 else:
197 for stream in stream_patterns:
198 sl.add_raw_stream_selector(stream)
200 sources.append(sl)
201 elif mca:
202 port = mca.group(1)
203 cam = CamAcquisition(port=port, deltat=0.0314504)
204 sources.append(cam)
205 elif mus:
206 port = mus.group(1)
207 try:
208 d = {}
209 if mus.group(3):
210 d = dict(urlparse.parse_qsl(mus.group(3))) # noqa
212 deltat = 1.0/float(d.get('rate', '50'))
213 channels = [(int(c), c) for c in d.get('channels', '01234567')]
214 hb628 = USBHB628Acquisition(
215 port=port,
216 deltat=deltat,
217 channels=channels,
218 buffersize=16,
219 lookback=50)
221 sources.append(hb628)
222 except Exception:
223 raise
224 sys.exit('invalid acquisition source: %s' % arg)
226 elif msc:
227 port = msc.group(1)
228 sco = SchoolSeismometerAcquisition(port=port)
229 sources.append(sco)
230 elif med:
231 port = med.group(1)
232 edl = EDLAcquisition(port=port)
233 sources.append(edl)
234 elif mcu:
235 device = mcu.group(1)
236 cube = CubeAcquisition(device=device)
237 sources.append(cube)
239 if msl or mca or mus or msc or med or mcu:
240 args.pop(iarg)
241 else:
242 iarg += 1
244 return sources
247class PollInjector(qc.QObject):
249 def __init__(self, *args, **kwargs):
250 qc.QObject.__init__(self)
251 self._injector = pile.Injector(*args, **kwargs)
252 self._sources = []
253 self.startTimer(1000)
255 def add_source(self, source):
256 self._sources.append(source)
258 def remove_source(self, source):
259 self._sources.remove(source)
261 def timerEvent(self, ev):
262 for source in self._sources:
263 trs = source.poll()
264 for tr in trs:
265 self._injector.inject(tr)
267 # following methods needed because mulitple inheritance does not seem
268 # to work anymore with QObject in Python3 or PyQt5
270 def set_fixation_length(self, length):
271 return self._injector.set_fixation_length(length)
273 def set_save_path(
274 self,
275 path='dump_%(network)s.%(station)s.%(location)s.%(channel)s_'
276 '%(tmin)s_%(tmax)s.mseed'):
278 return self._injector.set_save_path(path)
280 def fixate_all(self):
281 return self._injector.fixate_all()
283 def free(self):
284 return self._injector.free()
287class Connection(qc.QObject):
289 received = qc.pyqtSignal(object, object)
290 disconnected = qc.pyqtSignal(object)
292 def __init__(self, parent, sock):
293 qc.QObject.__init__(self, parent)
294 self.socket = sock
295 self.readyRead.connect(
296 self.handle_read)
297 self.disconnected.connect(
298 self.handle_disconnected)
299 self.nwanted = 8
300 self.reading_size = True
301 self.handler = None
302 self.nbytes_received = 0
303 self.nbytes_sent = 0
304 self.compressor = zlib.compressobj()
305 self.decompressor = zlib.decompressobj()
307 def handle_read(self):
308 while True:
309 navail = self.socket.bytesAvailable()
310 if navail < self.nwanted:
311 return
313 data = self.socket.read(self.nwanted)
314 self.nbytes_received += len(data)
315 if self.reading_size:
316 self.nwanted = struct.unpack('>Q', data)[0]
317 self.reading_size = False
318 else:
319 obj = pickle.loads(self.decompressor.decompress(data))
320 if obj is None:
321 self.socket.disconnectFromHost()
322 else:
323 self.handle_received(obj)
324 self.nwanted = 8
325 self.reading_size = True
327 def handle_received(self, obj):
328 self.received.emit(self, obj)
330 def ship(self, obj):
331 data = self.compressor.compress(pickle.dumps(obj))
332 data_end = self.compressor.flush(zlib.Z_FULL_FLUSH)
333 self.socket.write(struct.pack('>Q', len(data)+len(data_end)))
334 self.socket.write(data)
335 self.socket.write(data_end)
336 self.nbytes_sent += len(data)+len(data_end) + 8
338 def handle_disconnected(self):
339 self.disconnected.emit(self)
341 def close(self):
342 self.socket.close()
345class ConnectionHandler(qc.QObject):
346 def __init__(self, parent):
347 qc.QObject.__init__(self, parent)
348 self.queue = []
349 self.connection = None
351 def connected(self):
352 return self.connection is None
354 def set_connection(self, connection):
355 self.connection = connection
356 connection.received.connect(
357 self._handle_received)
359 connection.connect(
360 self.handle_disconnected)
362 for obj in self.queue:
363 self.connection.ship(obj)
365 self.queue = []
367 def _handle_received(self, conn, obj):
368 self.handle_received(obj)
370 def handle_received(self, obj):
371 pass
373 def handle_disconnected(self):
374 self.connection = None
376 def ship(self, obj):
377 if self.connection:
378 self.connection.ship(obj)
379 else:
380 self.queue.append(obj)
383class SimpleConnectionHandler(ConnectionHandler):
384 def __init__(self, parent, **mapping):
385 ConnectionHandler.__init__(self, parent)
386 self.mapping = mapping
388 def handle_received(self, obj):
389 command = obj[0]
390 args = obj[1:]
391 self.mapping[command](*args)
394class MyMainWindow(qw.QMainWindow):
396 def __init__(self, app, *args):
397 qg.QMainWindow.__init__(self, *args)
398 self.app = app
400 def keyPressEvent(self, ev):
401 self.app.pile_viewer.get_view().keyPressEvent(ev)
404class SnufflerTabs(qw.QTabWidget):
405 def __init__(self, parent):
406 qw.QTabWidget.__init__(self, parent)
407 if hasattr(self, 'setTabsClosable'):
408 self.setTabsClosable(True)
410 self.tabCloseRequested.connect(
411 self.removeTab)
413 if hasattr(self, 'setDocumentMode'):
414 self.setDocumentMode(True)
416 def hide_close_button_on_first_tab(self):
417 tbar = self.tabBar()
418 if hasattr(tbar, 'setTabButton'):
419 tbar.setTabButton(0, qw.QTabBar.LeftSide, None)
420 tbar.setTabButton(0, qw.QTabBar.RightSide, None)
422 def append_tab(self, widget, name):
423 widget.setParent(self)
424 self.insertTab(self.count(), widget, name)
425 self.setCurrentIndex(self.count()-1)
427 def remove_tab(self, widget):
428 self.removeTab(self.indexOf(widget))
430 def tabInserted(self, index):
431 if index == 0:
432 self.hide_close_button_on_first_tab()
434 self.tabbar_visibility()
435 self.setFocus()
437 def removeTab(self, index):
438 w = self.widget(index)
439 w.close()
440 qw.QTabWidget.removeTab(self, index)
442 def tabRemoved(self, index):
443 self.tabbar_visibility()
445 def tabbar_visibility(self):
446 if self.count() <= 1:
447 self.tabBar().hide()
448 elif self.count() > 1:
449 self.tabBar().show()
451 def keyPressEvent(self, event):
452 if event.text() == 'd':
453 i = self.currentIndex()
454 if i != 0:
455 self.tabCloseRequested.emit(i)
456 else:
457 self.parent().keyPressEvent(event)
460class SnufflerStartWizard(qw.QWizard):
462 def __init__(self, parent):
463 qw.QWizard.__init__(self, parent)
465 self.setOption(self.NoBackButtonOnStartPage)
466 self.setOption(self.NoBackButtonOnLastPage)
467 self.setOption(self.NoCancelButton)
468 self.addPageSurvey()
469 self.addPageHelp()
470 self.setWindowTitle('Welcome to Pyrocko')
472 def getSystemInfo(self):
473 import numpy
474 import scipy
475 import pyrocko
476 import platform
477 import uuid
478 data = {
479 'node-uuid': uuid.getnode(),
480 'platform.architecture': platform.architecture(),
481 'platform.system': platform.system(),
482 'platform.release': platform.release(),
483 'python': platform.python_version(),
484 'pyrocko': pyrocko.__version__,
485 'numpy': numpy.__version__,
486 'scipy': scipy.__version__,
487 'qt': qc.PYQT_VERSION_STR,
488 }
489 return data
491 def addPageSurvey(self):
492 import pprint
493 webtk = 'DSFGK234ADF4ASDF'
494 sys_info = self.getSystemInfo()
496 p = qw.QWizardPage()
497 p.setCommitPage(True)
498 p.setTitle('Thank you for installing Pyrocko!')
500 lyt = qw.QVBoxLayout()
501 lyt.addWidget(qw.QLabel(
502 '<p>Your feedback is important for'
503 ' the development and improvement of Pyrocko.</p>'
504 '<p>Do you want to send this system information anon'
505 'ymously to <a href="https://pyrocko.org">'
506 'https://pyrocko.org</a>?</p>'))
508 text_data = qw.QLabel(
509 '<code style="font-size: small;">%s</code>' %
510 pprint.pformat(
511 sys_info,
512 indent=1).replace('\n', '<br>')
513 )
514 text_data.setStyleSheet('padding: 10px;')
515 lyt.addWidget(text_data)
517 lyt.addWidget(qw.QLabel(
518 'This message won\'t be shown again.\n\n'
519 'We appreciate your contribution!\n- The Pyrocko Developers'
520 ))
522 p.setLayout(lyt)
523 p.setButtonText(self.CommitButton, 'No')
525 yes_btn = qw.QPushButton(p)
526 yes_btn.setText('Yes')
528 @qc.pyqtSlot()
529 def send_data():
530 import requests
531 import json
532 try:
533 requests.post('https://pyrocko.org/%s' % webtk,
534 data=json.dumps(sys_info))
535 except Exception as e:
536 print(e)
537 self.button(self.NextButton).clicked.emit(True)
539 self.customButtonClicked.connect(send_data)
541 self.setButton(self.CustomButton1, yes_btn)
542 self.setOption(self.HaveCustomButton1, True)
544 self.addPage(p)
545 return p
547 def addPageHelp(self):
548 p = qw.QWizardPage()
549 p.setTitle('Welcome to Snuffler!')
551 text = qw.QLabel('''<html>
552<h3>- <i>The Seismogram browser and workbench.</i></h3>
553<p>Looks like you are starting the Snuffler for the first time.<br>
554It allows you to browse and process large archives of waveform data.</p>
555<p>Basic processing is complemented by Snufflings (<i>Plugins</i>):</p>
556<ul>
557 <li><b>Download seismograms</b> from Geofon, IRIS and others</li>
558 <li><b>Earthquake catalog</b> access to Geofon, GobalCMT, USGS...</li>
559 <li><b>Cake</b>, Calculate synthetic arrival times</li>
560 <li><b>Seismosizer</b>, generate synthetic seismograms on-the-fly</li>
561 <li>
562 <b>Map</b>, swiftly inspect stations and events on interactive maps
563 </li>
564</ul>
565<p>And more, see <a href="https://pyrocko.org/">https://pyrocko.org/</a></p>
566<p><b>NOTE:</b><br>If you installed snufflings from the
567<a href="https://github.com/pyrocko/contrib-snufflings">user contributed
568snufflings repository</a><br>you also have to pull an update from there.
569</p>
570<p style="width: 100%; background-color: #e9b96e; margin: 5px; padding: 50;"
571 align="center">
572 <b>You can always press <code>?</code> for help!</b>
573</p>
574</html>''')
576 lyt = qw.QVBoxLayout()
577 lyt.addWidget(text)
579 def remove_custom_button():
580 self.setOption(self.HaveCustomButton1, False)
582 p.initializePage = remove_custom_button
584 p.setLayout(lyt)
585 self.addPage(p)
586 return p
589class SnufflerWindow(qw.QMainWindow):
591 def __init__(
592 self, pile, stations=None, events=None, markers=None, ntracks=12,
593 marker_editor_sortable=True, follow=None, controls=True,
594 opengl=None, instant_close=False):
596 qw.QMainWindow.__init__(self)
598 self.instant_close = instant_close
600 self.dockwidget_to_toggler = {}
601 self.dockwidgets = []
603 self.setWindowTitle("Snuffler")
605 self.pile_viewer = pile_viewer.PileViewer(
606 pile, ntracks_shown_max=ntracks, use_opengl=opengl,
607 marker_editor_sortable=marker_editor_sortable,
608 panel_parent=self)
610 self.marker_editor = self.pile_viewer.marker_editor()
611 self.add_panel(
612 'Markers', self.marker_editor, visible=False,
613 where=qc.Qt.RightDockWidgetArea)
614 if stations:
615 self.get_view().add_stations(stations)
617 if events:
618 self.get_view().add_events(events)
620 if len(events) == 1:
621 self.get_view().set_active_event(events[0])
623 if markers:
624 self.get_view().add_markers(markers)
625 self.get_view().associate_phases_to_events()
627 self.tabs = SnufflerTabs(self)
628 self.setCentralWidget(self.tabs)
629 self.add_tab('Main', self.pile_viewer)
631 self.pile_viewer.setup_snufflings()
632 self.setMenuBar(self.pile_viewer.menu)
634 self.main_controls = self.pile_viewer.controls()
635 self.add_panel('Main Controls', self.main_controls, visible=controls)
636 self.show()
638 self.get_view().setFocus(qc.Qt.OtherFocusReason)
640 sb = self.statusBar()
641 sb.clearMessage()
642 sb.showMessage('Welcome to Snuffler! Press <?> for help.')
644 snuffler_config = self.pile_viewer.viewer.config
646 if snuffler_config.first_start:
647 wizard = SnufflerStartWizard(self)
649 @qc.pyqtSlot()
650 def wizard_finished(result):
651 if result == wizard.Accepted:
652 snuffler_config.first_start = False
653 config.write_config(snuffler_config, 'snuffler')
655 wizard.finished.connect(wizard_finished)
657 wizard.show()
659 if follow:
660 self.get_view().follow(float(follow))
662 self.closing = False
664 def sizeHint(self):
665 return qc.QSize(1024, 768)
666 # return qc.QSize(800, 600) # used for screen shots in tutorial
668 def keyPressEvent(self, ev):
669 self.get_view().keyPressEvent(ev)
671 def get_view(self):
672 return self.pile_viewer.get_view()
674 def get_panel_parent_widget(self):
675 return self
677 def add_tab(self, name, widget):
678 self.tabs.append_tab(widget, name)
680 def remove_tab(self, widget):
681 self.tabs.remove_tab(widget)
683 def add_panel(self, name, panel, visible=False, volatile=False,
684 where=qc.Qt.BottomDockWidgetArea):
686 if not self.dockwidgets:
687 self.dockwidgets = []
689 dws = [x for x in self.dockwidgets if self.dockWidgetArea(x) == where]
691 dockwidget = qw.QDockWidget(name, self)
692 self.dockwidgets.append(dockwidget)
693 dockwidget.setWidget(panel)
694 panel.setParent(dockwidget)
695 self.addDockWidget(where, dockwidget)
697 if dws:
698 self.tabifyDockWidget(dws[-1], dockwidget)
700 self.toggle_panel(dockwidget, visible)
702 mitem = qw.QAction(name, None)
704 def toggle_panel(checked):
705 self.toggle_panel(dockwidget, True)
707 mitem.triggered.connect(toggle_panel)
709 if volatile:
710 def visibility(visible):
711 if not visible:
712 self.remove_panel(panel)
714 dockwidget.visibilityChanged.connect(
715 visibility)
717 self.get_view().add_panel_toggler(mitem)
718 self.dockwidget_to_toggler[dockwidget] = mitem
720 def toggle_panel(self, dockwidget, visible):
721 if visible is None:
722 visible = not dockwidget.isVisible()
724 dockwidget.setVisible(visible)
725 if visible:
726 w = dockwidget.widget()
727 minsize = w.minimumSize()
728 w.setMinimumHeight(w.sizeHint().height() + 5)
730 def reset_minimum_size():
731 import sip
732 if not sip.isdeleted(w):
733 w.setMinimumSize(minsize)
735 qc.QTimer.singleShot(200, reset_minimum_size)
737 dockwidget.setFocus()
738 dockwidget.raise_()
740 def toggle_marker_editor(self):
741 self.toggle_panel(self.marker_editor.parent(), None)
743 def toggle_main_controls(self):
744 self.toggle_panel(self.main_controls.parent(), None)
746 def remove_panel(self, panel):
747 dockwidget = panel.parent()
748 self.removeDockWidget(dockwidget)
749 dockwidget.setParent(None)
750 mitem = self.dockwidget_to_toggler[dockwidget]
751 self.get_view().remove_panel_toggler(mitem)
753 def return_tag(self):
754 return self.get_view().return_tag
756 def confirm_close(self):
757 ret = qw.QMessageBox.question(
758 self,
759 'Snuffler',
760 'Close Snuffler window?',
761 qw.QMessageBox.Cancel | qw.QMessageBox.Ok,
762 qw.QMessageBox.Ok)
764 return ret == qw.QMessageBox.Ok
766 def closeEvent(self, event):
767 if self.instant_close or self.confirm_close():
768 self.closing = True
769 self.pile_viewer.cleanup()
770 event.accept()
771 else:
772 event.ignore()
774 def is_closing(self):
775 return self.closing
778class Snuffler(qw.QApplication):
780 def __init__(self):
781 qw.QApplication.__init__(self, sys.argv)
782 self.lastWindowClosed.connect(self.myQuit)
783 self.server = None
784 self.loader = None
786 def install_sigint_handler(self):
787 self._old_signal_handler = signal.signal(
788 signal.SIGINT,
789 self.myCloseAllWindows)
791 def uninstall_sigint_handler(self):
792 signal.signal(signal.SIGINT, self._old_signal_handler)
794 def start_server(self):
795 self.connections = []
796 s = qn.QTcpServer(self)
797 s.listen(qn.QHostAddress.LocalHost)
798 s.newConnection.connect(
799 self.handle_accept)
800 self.server = s
802 def start_loader(self):
803 self.loader = SimpleConnectionHandler(
804 self,
805 add_files=self.add_files,
806 update_progress=self.update_progress)
807 ticket = os.urandom(32)
808 self.forker.spawn('loader', self.server.serverPort(), ticket)
809 self.connection_handlers[ticket] = self.loader
811 def handle_accept(self):
812 sock = self.server.nextPendingConnection()
813 con = Connection(self, sock)
814 self.connections.append(con)
816 con.disconnected.connect(
817 self.handle_disconnected)
819 con.received.connect(
820 self.handle_received_ticket)
822 def handle_disconnected(self, connection):
823 self.connections.remove(connection)
824 connection.close()
825 del connection
827 def handle_received_ticket(self, connection, object):
828 if not isinstance(object, str):
829 self.handle_disconnected(connection)
831 ticket = object
832 if ticket in self.connection_handlers:
833 h = self.connection_handlers[ticket]
834 connection.received.disconnect(
835 self.handle_received_ticket)
837 h.set_connection(connection)
838 else:
839 self.handle_disconnected(connection)
841 def snuffler_windows(self):
842 return [w for w in self.topLevelWidgets()
843 if isinstance(w, SnufflerWindow) and not w.is_closing()]
845 def event(self, e):
846 if isinstance(e, qg.QFileOpenEvent):
847 paths = [str(e.file())]
848 wins = self.snuffler_windows()
849 if wins:
850 wins[0].get_view().load_soon(paths)
852 return True
853 else:
854 return qw.QApplication.event(self, e)
856 def load(self, pathes, cachedirname, pattern, format):
857 if not self.loader:
858 self.start_loader()
860 self.loader.ship(
861 ('load', pathes, cachedirname, pattern, format))
863 def add_files(self, files):
864 p = self.pile_viewer.get_pile()
865 p.add_files(files)
866 self.pile_viewer.update_contents()
868 def update_progress(self, task, percent):
869 self.pile_viewer.progressbars.set_status(task, percent)
871 def myCloseAllWindows(self, *args):
872 self.closeAllWindows()
874 def myQuit(self, *args):
875 self.quit()