source: SHX/trunk/SeismicHandler/modules/wx_.py @ 1141

Revision 1141, 85.0 KB checked in by klaus, 4 years ago (diff)

SHX magnification window

  • Property svn:eol-style set to native
Line 
1# -*- coding: utf-8 -*-
2
3#    This file is part of Seismic Handler eXtended (SHX). For terms of use and
4#    license information please see license.txt and visit
5#    http://www.seismic-handler.org/portal/wiki/Shx/LicenseTerms
6
7import wx
8import wx.lib.scrolledpanel as SP
9import time
10import math
11import os
12import numpy as np
13from functools import partial
14from SeismicHandler.basics.messages import msgs, subscribe_ui_event, \
15                                ui_event, log_message, get_style#, get_runtime
16from SeismicHandler.config import get_runtime
17from SeismicHandler.core import Traces, Overlays, traces_from_list
18from SeismicHandler.basics.codes import NAME, VERSION
19from SeismicHandler.basics.tools import get_default_attrib, timeit, timestore
20from SeismicHandler.basics.analysispar import PhaseList, AnalysisPar
21from obspy.core import read, UTCDateTime
22from obspy.sh.core import fromUTCDateTime, toUTCDateTime
23
24# width of station info
25STATION_INFO = 85
26# margins (top, right, bottom, left)
27MARGINS = [10, 10, 10, 10]
28# space for time-scale
29TIMESCALE = 40
30# offset for time string
31TIMESCALE_OFFSET_TEXT = 8
32# length of minor/major ticks
33TIMESCALE_TICKS_MINOR = 3
34TIMESCALE_TICKS_MAJOR = 7
35
36# regular checking of UI data
37TIME_SLOT = .03
38
39# holds window instance
40plotter = None
41
42class magnifyCanvas(wx.Panel):
43    """
44    A drawing canvas for the magnification trace. It has methods similar to
45    the traceCanvas class, but the methods are simpler due the fact that there
46    is only one trace displayed. On mouse operations there is no need for the
47    y (vertical) coordinate to be evaluated. Subclassing therefore is difficult
48    or the code must be reorganized. It is not advisable to run this in a
49    separate thread, the message communication could be too slow for immediate
50    reactions to mouse input.
51    """
52    def __init__( self, parent ):
53        wx.Panel.__init__( self, parent,
54            style=wx.BORDER_SIMPLE|wx.NO_FULL_REPAINT_ON_RESIZE)
55        self.Bind(wx.EVT_PAINT, self.OnPaint)
56        self.Bind(wx.EVT_SIZE, self.OnSize)
57        self.Bind(wx.EVT_MOTION, self.OnMouseMotion)
58        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
59        self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
60        self._magn_bitmap = None
61        self.trc = None
62        self.pixwidth = None
63        self.pixheight = None
64        self.pixel_duration = None
65        self._xor_line = None
66        self._last_defined_phase = None
67        self._mousedouble = None
68        self.clearWindow()
69   
70    def OnPaint( self, e ):
71        if self._magn_bitmap:
72            dc = wx.BufferedPaintDC(self, self._magn_bitmap)
73   
74    def OnSize( self, e ):
75        self.pixwidth, self.pixheight = self.GetVirtualSize()
76        self.refresh()
77   
78    def OnMouseMotion(self, evt):
79        if not self.trc:
80            return
81        x, y = evt.GetPositionTuple()
82        abstime, reltime = self._fromScreenCoo( x,  mode='both' )
83        # draw XOR marker
84        if evt.LeftIsDown():
85            self._drawXorLine( x )
86        # Put text instatus line of plotter window
87        stext = "%s - %g - %s - Z:%3.1f" % (fromUTCDateTime(abstime),reltime,
88            self.trc.id,(self.trc.stats.endtime-self.trc.stats.starttime))
89        plotter.SetStatusText( stext )
90        # let the event propagate
91        evt.Skip()
92
93    def OnMouseLeftDown(self, evt):
94        "Left moune button pressed down."
95        x, y = evt.GetPositionTuple()
96        abstime = self._fromScreenCoo( x,  mode='abs' )
97        if self.trc:
98            cphase = self._closePhase( abstime )
99            if cphase:
100                station = "%s.%s" % (self.trc.stats.network,
101                    self.trc.stats.station)
102                station = station.upper()
103                _sendShCommand(
104                    "@PHASE CLEAR %s %s %s" % (cphase[0],station,cphase[1]) \
105                    +"\n@PHASE DEFAULT_PHASE %s" % cphase[0]
106                )
107                self._last_defined_phase = cphase[0]
108                #self._drawSinglePick( trace, tracetime,color='white' )
109                self.refresh()
110        evt.Skip()
111
112    def OnMouseLeftUp(self, evt):
113        "Left mouse button released."
114        if not self.trc:
115            return
116        self._drawXorLine( None )  # clear possibly existing XOR line.
117        x, y = evt.GetPositionTuple()
118        abstime, reltime = self._fromScreenCoo( x,  mode='both' )
119        station = "%s.%s" % (self.trc.stats.network,self.trc.stats.station)
120
121        # Send SH command to define/clear phase if not waiting for user input.
122        if self._mousedouble:
123            if self._last_defined_phase:
124                _sendShCommand( "@PHASE CLEAR %s %s manual" % (
125                    self.last_defined_phase,station.upper()) )
126        else:
127            _sendShCommand( "@PHASE DEFINE %s %s ;;; %s" % (station.upper(),
128                fromUTCDateTime(abstime),self.trc.stats.channel[-1].upper()),
129                name="mouse evt" )
130            #self._drawSinglePick( trace, tracetime )
131            self.refresh()
132        self.mousedouble = False
133        evt.Skip()
134
135    def refresh( self ):
136        self._drawMagnifyTrace()
137        self._drawPicks()
138
139    def _closePhase( self, abstime ):
140        "Return name and type of closest phase or None."
141        if not self.trc:
142            return None
143        phaselist = PhaseList()
144        if not self.pixel_duration:
145            return None
146        toltime = 2*self.pixel_duration
147        station = "%s.%s" % (self.trc.stats.network,self.trc.stats.station)
148        for phase in phaselist.getPhaseList(station):
149            tdiff = abs( phase.picktime-abstime )
150            if tdiff < toltime:
151                return (phase.name,phaselist.picktypeName(phase.picktype))
152        return None
153   
154    def _drawMagnifyTrace( self ):       
155        # Bitmap holding the final figure, canvas->DC drawing to this bitmap.
156        if self.trc == None:
157            self.clearWindow()
158            return
159        norm = max( abs(max(self.trc.data)), abs(min(self.trc.data)) )
160        amplscale = 1./norm * self.pixheight/2.
161        self._magn_bitmap = wx.EmptyBitmap(self.pixwidth, self.pixheight)
162        mdc = wx.MemoryDC(self._magn_bitmap)
163        mdc.SetBrush(wx.TRANSPARENT_BRUSH)
164        mdc.Clear()
165        mdc.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL))
166        mdc.SetPen(wx.Pen((0,0,0), 1))
167        mdc.BeginDrawing()
168        mdc.DrawLines(
169            zip(
170                np.linspace( 0., self.pixwidth, len(self.trc.data) ),
171                self.trc.data*amplscale + self.pixheight/2
172            )
173        )
174        mdc.EndDrawing()
175        del mdc
176        self.Refresh()
177        self.Update()
178   
179    def _drawPicks( self ):
180        if self.trc == None:
181            return
182        phaselist = PhaseList()
183        _picks = {}
184        sname = "%s.%s" % (self.trc.stats.network,self.trc.stats.station)
185        for phase in phaselist.getPhaseList(sname):
186            if phase.comp and self.trc.stats.channel[-1] != phase.comp:
187                continue
188            pcol = phaselist.picktypeColor( phase.picktype )
189            if pcol not in _picks.keys():
190                _picks[pcol] = []
191            _picks[pcol].append(
192                ( phase.name, self._toScreenCoo(phase.picktime) )
193            )
194        dc = wx.ClientDC(self)
195        dc.SetBrush(wx.TRANSPARENT_BRUSH)
196        dc.SetFont( wx.Font( 8, wx.DEFAULT, wx.NORMAL, wx.NORMAL ) )
197        pickborder = 10
198        yup = self.pixheight - pickborder
199        ydn = pickborder
200        # draw picks
201        for pcolor in _picks.keys():
202            dc.SetPen(wx.Pen(pcolor, 1))
203            dc.SetTextForeground(pcolor)
204            for pick in _picks[pcolor]:
205                pname, xcoo = pick
206                dc.DrawLine( xcoo, yup, xcoo, ydn )
207                if pname != '_Mark_':
208                    dc.DrawText(pname, xcoo + 2, ydn - 10)
209   
210    def _drawXorLine( self, xcoo=None ):
211        """Draws line in XOR mode. Deletes previous line. To only clear old
212        element, pass None.
213        Should also draw into backing store bitmap (self._bitmap). Now,
214        a repaint doesn't restore the XOR drawing, resulting in a doubled
215        element.
216        """
217        color = 'orange'
218        size = 1
219        pickborder = 10
220        yup = self.pixheight - pickborder
221        ydn = pickborder
222        if xcoo == None:
223            coo = (None,None,None,None)
224        else:
225            coo = (xcoo,ydn,xcoo,yup)
226        dc = wx.ClientDC(self)
227        dc.SetLogicalFunction( wx.XOR )
228        dc.SetPen( wx.Pen(color,size,wx.SOLID) )
229        dc.SetBrush( wx.Brush(color,wx.TRANSPARENT) )
230        if self._xor_line != None:
231            # Clear old line.
232            dc.DrawLine( *self._xor_line )
233        if coo[0] == None:
234            self._xor_line = None
235        else:
236            # Draw new rectangle.
237            dc.DrawLine( *coo )
238            self._xor_line = coo
239
240    def magnifyTrace( self, trc, boxwidth=None, boxcenter=None, boxend=None ):
241        if trc == None:
242            self.clearWindow()
243        self.pixwidth, self.pixheight = self.GetVirtualSize()
244        self.pixel_duration = self._fromScreenCoo( 1, mode='rel' )
245        if boxcenter != None:
246            if not boxwidth:
247                return
248            w2 = boxwidth/2
249            torig = trc.get_info('t-origin')
250            if torig:
251                boxcenter -= torig
252            ta = boxcenter - w2
253            tb = boxcenter + w2
254            self.trc = trc.slice( ta, tb )
255        elif boxend != None:
256            if not boxwidth:
257                return
258            torig = trc.get_info('t-origin')
259            if torig:
260                boxend -= torig
261            ta = boxend - boxwidth
262            self.trc = trc.slice( ta, boxend )
263        else:
264            self.trc = trc
265        self._drawMagnifyTrace()
266        self._drawPicks()
267   
268    def clearWindow( self ):
269        dc = wx.AutoBufferedPaintDCFactory(self)
270        dc.Clear()
271        return
272   
273    def _toScreenCoo( self, abstime ):
274        if not self.trc or not self.pixwidth:
275            return 0.
276        return ((abstime-self.trc.stats.starttime) \
277            /(self.trc.stats.endtime-self.trc.stats.starttime) * self.pixwidth)
278   
279    def _fromScreenCoo( self, xpix, mode='abs' ):
280        if not self.trc or not self.pixwidth:
281            return 0.
282        reltime = (float(xpix)/float(self.pixwidth)) \
283            * (self.trc.stats.endtime-self.trc.stats.starttime)
284        if mode == 'abs':
285            return self.trc.stats.starttime + reltime
286        elif mode == 'rel':
287            return reltime
288        else:
289            return (self.trc.stats.starttime + reltime,reltime)
290       
291       
292
293class traceCanvas(SP.ScrolledPanel):
294    """
295    All drawing methods for plotting traces. Include all mouse-related
296    functions.
297    """
298    #@timeit
299    def __init__(self, parent, fnames=[]):
300        SP.ScrolledPanel.__init__(self, parent,
301            style=wx.BORDER_SIMPLE|wx.NO_FULL_REPAINT_ON_RESIZE)
302
303        # class data
304        self._bitmap = None
305        self.parent = parent
306        self.traces = Traces
307        self.ZoomWindow = None
308        self.zoomwdwwidth = 0
309        self.Scrolled = 0
310        self.mousedouble = 0
311        self.refresh = False
312        self.AllowDoubleClick = True
313        self.do_screenshot = False
314        self.pixel_duration = None
315        self.last_defined_phase = None
316        self.wheelpos = 0
317        self.timewindow = (None,None)
318        self.last_number_of_traces = 0
319        self._xor_line = None
320        self._xor_rect = None
321
322        # temporary layer for mouse motion
323        self.overlay_drag = wx.Overlay()
324        # layer for picks and zoom box
325        self.overlay_picks = wx.Overlay()
326
327        # application defaults
328        self.relativeAxis = True
329        self.traceOrder = 0
330        self.phasename = "P"
331        self.space = None
332        self.interactive = "trace_time"
333
334        self.ppi = wx.ScreenDC().GetPPI()
335
336        self._setup()
337
338        def disable_event(*pargs,**kwargs):
339            pass # the sauce, please
340
341        # event binding
342        self.Bind(wx.EVT_PAINT, self.OnPaint)
343        self.Bind(wx.EVT_SIZE, self.OnSize)
344        self.Bind(wx.EVT_IDLE, self.OnIdle)
345        self.Bind(wx.EVT_MOTION, self.OnMouseMotion)
346        self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
347        self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseLeftDouble)
348        self.Bind(wx.EVT_RIGHT_DCLICK, self.OnMouseRightDouble)
349        self.Bind(wx.EVT_RIGHT_UP, self.OnMouseRightUp)
350        self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
351        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
352        self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
353        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
354        self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyDown)
355        self.Bind(wx.EVT_ERASE_BACKGROUND, disable_event)
356
357        # messaging system
358#        msgs.subscribe(self.OnNotifyHeight, 'GUT.displayheight')
359#        msgs.subscribe(self.OnNotifyPhaseName, 'GUI.phasename')
360#        msgs.subscribe(self.OnNotifyTraceOrder, 'GUI.traceorder')
361#        msgs.subscribe(self.OnNotifyTimeAxis, 'GUT.timeaxis')
362        msgs.subscribe(self.__runtime_changer, "setruntime")
363
364        # init
365        self.OnSize(None)
366        self.SetFocus()
367
368    def __runtime_changer(self, name, value):
369        """
370        Helper method for acting on certain runtime events. Called via message
371        system.
372        """
373        if name != "RUNTIME:styles":
374            return
375        margins = value.get("MARGINS", None)
376
377        if not margins:
378            return
379
380        # convert inch values to pixels
381        margins = [
382            margins[0] * self.ppi[1],
383            margins[1] * self.ppi[0],
384            margins[2] * self.ppi[1],
385            margins[3] * self.ppi[0]
386        ]
387
388        global MARGINS
389        if MARGINS != margins:
390            MARGINS = margins
391   
392    def closePhase( self, trace, abstime ):
393        "Return name and type of closest phase or None."
394        phaselist = PhaseList()
395        if not self.pixel_duration:
396            return None
397        toltime = 2*self.pixel_duration
398        station = "%s.%s" % (trace.stats.network,trace.stats.station)
399        for phase in phaselist.getPhaseList(station):
400            tdiff = abs( phase.picktime-abstime )
401            if tdiff < toltime:
402                return (phase.name,phaselist.picktypeName(phase.picktype))
403        return None
404   
405    def _drawXor( self, mode, coo=(None,None,None,None) ):
406        """Draws line (len(coo)==2) or rectangle (len(coo) == 4) in XOR mode.
407        Deletes previous line/rectangle. To only clear old element, pass
408        None-tuples.
409        Should also draw into backing store bitmap (self._bitmap). Now,
410        a repaint doesn't restore the XOR drawing, resulting in a doubled
411        element.
412        """
413        if mode == 'clear':
414            self._xor_line = None
415            self._xor_rect = None
416            return
417        color = 'orange'
418        # Line or rectangle?
419        islinemode = (mode == 'line')
420        # Setup dc.
421        if islinemode:
422            size = 1
423        else:
424            size = 2
425        dc = wx.ClientDC(self)
426        dc.SetLogicalFunction( wx.XOR )
427        dc.SetPen( wx.Pen(color,size,wx.SOLID) )
428        dc.SetBrush( wx.Brush(color,wx.TRANSPARENT) )
429        #dc.SetBrush( wx.Brush(color,wx.SOLID) )
430        if islinemode:
431            if self._xor_line != None:
432                # Clear old rectangle.
433                dc.DrawLine( *self._xor_line )
434            if coo[0] == None:
435                self._xor_line = None
436            else:
437                # Draw new rectangle.
438                dc.DrawLine( *coo )
439                self._xor_line = coo
440        else:
441            if self._xor_rect != None:
442                # Clear old rectangle.
443                dc.DrawRectangle( *self._xor_rect )
444            if coo[0] == None:
445                self._xor_rect = None
446            else:
447                # Draw new rectangle.
448                dc.DrawRectangle( *coo )
449                self._xor_rect = coo
450
451    # event processing
452    def OnMouseWheel(self, evt):
453        maxwheelpos = 10
454        wheel = evt.GetWheelRotation()
455        if wheel > 0:
456            if self.wheelpos < maxwheelpos:
457                _sendShCommand( "zoom/rel all 2" )
458                self.wheelpos += 1
459        elif wheel < 0:
460            if self.wheelpos > -maxwheelpos:
461                _sendShCommand( "zoom/rel all 0.5" )           
462                self.wheelpos -= 1
463        evt.Skip()
464
465    def OnKeyDown(self, evt):
466        print "key"
467        kc = evt.GetKeyCode()
468        print kc
469        evt.Skip()
470#        if kc == ord("r"):
471
472    def OnMouseLeftDouble(self, evt):
473        trace, tracetime = self.ScreenToTraceAndTime(*(evt.GetPositionTuple()))
474        if trace:
475            self.mousedouble = True
476        #    print "dbg: closePhase", self.closePhase(trace,tracetime)
477        #    cphase = self.closePhase( trace, tracetime )
478        #    if cphase:
479        #        station = "%s.%s" % (trace.stats.network,trace.stats.station)
480        #        _sendShCommand(
481        #            "phase clear %s %s %s" % (cphase[0],station,cphase[1])
482        #        )
483        evt.Skip()
484
485    def OnMouseRightDouble(self, evt):
486        print "dbg: right double click"
487        self.zoomwdwwidth = 0.
488        evt.Skip()
489
490    def OnMouseLeftDown(self, evt):
491        self._captureMouse(evt)
492        trace, tracetime = self.ScreenToTraceAndTime(*(evt.GetPositionTuple()))
493        if trace:
494            #self.mousedouble = True
495            cphase = self.closePhase( trace, tracetime )
496            if cphase:
497                station = "%s.%s" % (trace.stats.network,trace.stats.station)
498                station = station.upper()
499                _sendShCommand(
500                    "@PHASE CLEAR %s %s %s" % (cphase[0],station,cphase[1]) \
501                    +"\n@PHASE DEFAULT_PHASE %s" % cphase[0]
502                )
503                self.last_defined_phase = cphase[0]
504                self._drawSinglePick( trace, tracetime,color='white' )
505                if plotter.magnify:
506                    plotter.magnify.canvas.refresh()
507            self._drawPicksAndZoom()
508        evt.Skip()
509
510    def OnMouseRightDown(self, evt):
511        self._captureMouse(evt)
512        if self.zoomwdwwidth:
513            pass
514        elif self.ZoomWindow:
515            trace, start, end = self.ZoomWindow
516            start = self.TraceAndTimeToScreen(trace, start)
517            end = self.TraceAndTimeToScreen(trace, end)
518            self.zoomwdwwidth = end[0] - start[0]
519        else:
520            self.zoomwdwwidth = 0
521
522    def OnMouseMotion(self, evt):
523        """
524        Mouse motion.
525        """
526
527        if not len(self.traces):
528            return
529
530        x, y = evt.GetPositionTuple()
531        trace, timestamp = self.ScreenToTraceAndTime(x, y)
532
533        try:
534            tid = trace.id
535        except AttributeError:
536            tid = "*"
537       
538        reltimestr = "%g" % self.ScreenToRelativeTime( x, y )
539
540        dragwidth = None
541        if evt.Dragging() and trace:
542            th2 = self.traceheight / 2
543            shx_graphics = trace.get_graphics()
544            if shx_graphics == None:
545                print "dbg: returned from callback, no shx_graphics"
546                return
547            mpth2 = shx_graphics.midpoint + th2
548            mmth2 = shx_graphics.midpoint - th2
549
550            # draw box
551            if evt.RightIsDown():
552                self.ZoomWindow = None
553                if plotter:
554                    plotter.setZoomWindow( None, None, None )
555                if self.zoomwdwwidth == 0.:
556                    self.__zoombox( self.dragStart, (x, y),
557                        shx_graphics.midpoint, size=2 )
558                    dragwidth = x - self.dragStart[0]
559                    # data for magnify trace
560                    if plotter and plotter.magnify:
561                        plotter.magnify.canvas.magnifyTrace( trace,
562                            boxend=timestamp,
563                            boxwidth=dragwidth*self.pixel_duration )
564                else:
565                    _start = [x-self.zoomwdwwidth/2,y]
566                    _end = [x+self.zoomwdwwidth/2,y]
567                    self.__zoombox( _start, _end,
568                        shx_graphics.midpoint, size=2 )
569                    dragwidth = _end[0] - _start[0]
570                    if plotter and plotter.magnify:
571                        plotter.magnify.canvas.magnifyTrace( trace,
572                            boxcenter=timestamp,
573                            boxwidth=dragwidth*self.pixel_duration )
574            # draw position line
575            elif evt.LeftIsDown():
576                if self.traceOrder in [0, 1]:
577                    self._drawXor( 'line', (
578                        x, mmth2 - self.Scrolled,
579                        x, mpth2 - self.Scrolled
580                    ) )
581                elif self.traceOrder in [2, 3]:
582                    self._drawXor( 'line', (
583                        mmth2 - self.Scrolled, y,
584                        mpth2 - self.Scrolled, y
585                    ) )
586
587        stext = "%s - %s - %s" % (fromUTCDateTime(timestamp), reltimestr, tid)
588        if self.ZoomWindow:
589            stext += " - Z:%3.1f" % (self.ZoomWindow[2]-self.ZoomWindow[1])
590        elif dragwidth and self.pixel_duration:
591            stext += " - Z:%3.1f" % (dragwidth*self.pixel_duration)
592        self.parent.SetStatusText( stext )
593
594        # let the event propagate
595        evt.Skip()
596
597    def OnMouseRightUp(self, evt):
598        """
599        Release of right mouse button.
600        """
601        if self.HasCapture():
602            self._releaseMouse(evt)
603            x, y = evt.GetPositionTuple()
604
605            if self.zoomwdwwidth == 0:
606                trace, end = self.ScreenToTraceAndTime(x, y)
607                if self.traceOrder in [0, 1]:
608                    _, start = self.ScreenToTraceAndTime(self.dragStart[0], y)
609                elif self.traceOrder in [2, 3]:
610                    _, start = self.ScreenToTraceAndTime(x, self.dragStart[1])
611            else:
612                if self.traceOrder in [0, 1]:
613                    trace, start = \
614                        self.ScreenToTraceAndTime(x-self.zoomwdwwidth/2, y)
615                    trace, end = \
616                        self.ScreenToTraceAndTime(x+self.zoomwdwwidth/2, y)
617                elif self.traceOrder in [2, 3]:
618                    trace, start = \
619                        self.ScreenToTraceAndTime(x, y-self.zoomwdwwidth/2)
620                    trace, end = \
621                        self.ScreenToTraceAndTime(x, y+self.zoomwdwwidth/2)
622
623            if start > end:
624                end, start = start, end
625
626            self.ZoomWindow = [trace, start, end]
627            self.dragStart = None
628            self._drawPicksAndZoom()
629            if plotter and trace:
630                # store relative times for possible menu call to stw.
631                srel = start - trace.stats.starttime \
632                    + trace.get_info('t-origin')
633                erel = end - trace.stats.starttime + trace.get_info('t-origin')
634                plotter.setZoomWindow( trace.index(True), srel, erel )
635                # content of magnify window
636                if plotter.magnify:
637                    plotter.magnify.canvas.magnifyTrace(trace.slice(start,end))
638            elif plotter.magnify:
639                plotter.magnify.canvas.magnifyTrace( None )  # clear
640
641    def OnMouseLeftUp(self, evt):
642        """
643        Release of left mouse button.
644        """
645        if self.HasCapture():
646            self._releaseMouse(evt)
647            self.dragStart = None
648           
649        #print "dbg: double buffered", self.IsDoubleBuffered()
650
651        #if self.mousedouble:
652        #    self.mousedouble = False
653
654        x, y = evt.GetPositionTuple()
655        trace, tracetime = self.ScreenToTraceAndTime(x, y)
656
657        # Send SH command to define/clear phase if not waiting for user input.
658        if self.mousedouble:
659            if self.last_defined_phase:
660                station = "%s.%s" % (trace.stats.network,trace.stats.station)
661                _sendShCommand( "@PHASE CLEAR %s %s manual" % (
662                    self.last_defined_phase,station.upper()) )
663        elif trace and self.parent._user_selection:
664            station = "%s.%s" % (trace.stats.network,trace.stats.station)
665            _sendShCommand( "@PHASE DEFINE %s %s ;;; %s" % (station.upper(),
666                fromUTCDateTime(tracetime),trace.stats.channel[-1].upper()),
667                name="mouse evt" )
668            self._drawSinglePick( trace, tracetime )
669            if plotter.magnify:
670                plotter.magnify.canvas.refresh()
671        self.mousedouble = False
672
673        if self.interactive == "trace_time":
674            self.parent._user_selection = (trace, tracetime)
675        elif self.interactive == "relative":
676            self.parent._user_selection = self.ScreenToRelativeTime(x, y)
677        else:
678            raise NameError("unknown selection mode")
679       
680        evt.Skip()
681
682    def OnScroll(self, evt):
683        """
684        Update scroll offset.
685        """
686        if self.traceOrder in [0, 1]:
687            self.Scrolled = self.GetScrollPos(wx.VERTICAL) * \
688                                               self.GetScrollPixelsPerUnit()[1]
689        elif self.traceOrder in [2, 3]:
690            self.Scrolled = self.GetScrollPos(wx.HORIZONTAL) * \
691                                               self.GetScrollPixelsPerUnit()[0]
692        self.OnSize(evt, renewOverlay=True)
693        evt.Skip()
694
695    def OnIdle(self, evt):
696        """
697        Idle processing. Redraw is done here.
698        """
699        if self.refresh:
700            self._timewindow = get_runtime("timewindow")
701            self._normtype = get_runtime("normtype")
702            wx.BeginBusyCursor( cursor=wx.StockCursor(_cursorNameToID("wait")) )
703            self._resetTimeRange()
704            self._drawTraces()
705            self._drawPicksAndZoom()
706            wx.EndBusyCursor()
707            self.refresh = False
708
709    def _set_modes(self, name, mode):
710        """
711        Method for altering the selection mode. Intended to be called from
712        other threads using "CallAfter".
713        """
714        setattr(self, name, mode)
715
716    def OnSize(self, evt, renewOverlay=False):
717        """
718        Called if redraw is requested.
719
720        Just set flag for redraw - execution triggered from IDLE event.
721        In case of resize renew overlay buffers.
722        """
723        # if window was resized, renew overlay buffers
724        if evt and (renewOverlay or evt.GetEventType() == wx.EVT_SIZE.typeId):
725            self._setup()
726            self.overlay_picks.Reset()
727            self.overlay_drag.Reset()
728
729        # painting is only done in idle state
730        self.refresh = True
731   
732    def OnPaint( self, evt ):
733        #ks dc2 = wx.AutoBufferedPaintDCFactory(self)
734        #ks dc2.Clear()
735        #ks x, y = self.CalcUnscrolledPosition(0, 0)
736        #ks dc2.Blit(0, 0, width, height, canvas, x, y)
737        if self._bitmap:
738            dc = wx.BufferedPaintDC(self, self._bitmap)
739
740    # Usually called via message system.
741    def OnNotifyTimeAxis(self, orientation):
742        """
743        Handle changes of time axis mode (absolute vs. relative time
744        axis).
745        """
746        recent = self.relativeAxis
747        self.relativeAxis = orientation == 1
748        if recent != self.relativeAxis:
749            # BAD!
750            self.OnSize(True, renewOverlay=True)
751
752    def OnNotifyTraceOrder(self, order):
753        """
754        Handle changes of trace plotting mode (horizontal vs. vertical
755        modes).
756        """
757        recent = self.traceOrder
758        self.traceOrder = order
759        if recent != self.traceOrder:
760            # reset display height
761#            msgs.sendMessage('notify.tracecount', count=len(self.traces),
762#                                                                    reset=True)
763#            msgs.sendMessage('ui.change.displayheight',
764#                                                       height=len(self.traces))
765            self._setup()
766            # BAD: setting evt=True
767            self.OnSize(True, renewOverlay=True)
768
769    def OnNotifyHeight(self, height):
770        """
771        Handle changes of display height.
772        """
773        if self.traceOrder in [0, 1]:
774            self.space = self.GetClientSize()[1]
775        elif self.traceOrder in [2, 3]:
776            self.space = self.GetClientSize()[0]
777
778        if height != len(self.traces):
779            self.space *= len(self.traces) / float(height)
780
781        self._setup()
782        self.OnSize(None)
783
784    def OnNotifyPhaseName(self, name):
785        self.phasename = name
786
787    # helper functions
788    #@timeit
789    def ScreenToRelativeTime(self, x, y):
790        """
791        Returns relative time inside screen.
792        """
793        x, y = self.CalcUnscrolledPosition((x,y))
794        # horizontal display
795        if self.traceOrder in [0, 1]:
796            timepos = x
797        # vertical display
798        elif self.traceOrder in [2, 3]:
799            timepos = y
800
801        # horizontal
802        if self.traceOrder in [0, 1]:
803            pixel_width = self.width - STATION_INFO - MARGINS[1] - MARGINS[3]
804        # vertical
805        elif self.traceOrder in [2, 3]:
806            pixel_width = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
807
808        # horizontal plotting
809        if self.traceOrder in [0, 1]:
810            timepos -= STATION_INFO + MARGINS[1]
811        # vertical plotting
812        elif self.traceOrder == 2:
813            if self.relativeAxis:
814                timepos -= length + MARGINS[0]
815            else:
816                timepos -= pixel_width + MARGINS[0]
817            timepos *= -1
818        elif self.traceOrder == 3:
819            timepos -= STATION_INFO
820
821        if self.timewindow[1] is None:
822            duration = self.relend - self.relstart
823            start = self.relstart
824        else:
825            duration = self.timewindow[1] - self.timewindow[0]
826            start = self.timewindow[0]
827
828        t = timepos * duration / pixel_width + start
829
830        return t
831
832    #@timeit
833    def ScreenToTraceAndTime(self, x, y):
834        """
835        Returns trace instance and time code derived from cursor
836        position inside graphical panel.
837        """
838        x, y = self.CalcUnscrolledPosition((x,y))
839        # horizontal display
840        if self.traceOrder in [0, 1]:
841            tracepos, timepos = y, x
842        # vertical display
843        elif self.traceOrder in [2, 3]:
844            tracepos, timepos = x, y
845
846        # get trace under cursor
847        trace = None
848        theight = self.traceheight // 2
849        for t in self.traces:
850            shx_graphics = t.get_graphics()
851            if shx_graphics == None:
852                continue
853            if 'midpoint' not in shx_graphics:
854                continue
855            if tracepos >= shx_graphics.midpoint - theight and \
856                                          tracepos <= shx_graphics.midpoint + theight:
857                trace = t
858                break
859
860        # get time code
861        if trace and self.relativeAxis:
862            start = trace.stats.starttime
863            end = trace.stats.endtime
864            shx_graphics = trace.get_graphics()
865            if shx_graphics != None:
866                pixel_width = shx_graphics.PlotPixels
867            # only needed for vertical plotting
868            length = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
869        else:
870            # global time axis
871            start = self.start
872            end = self.end
873            # horizontal
874            if self.traceOrder in [0, 1]:
875                pixel_width = self.width - STATION_INFO - MARGINS[1] - MARGINS[3]
876            # vertical
877            elif self.traceOrder in [2, 3]:
878                pixel_width = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
879
880        if self.timewindow[1] is None:
881            duration = end - start
882        else:
883            duration = self.timewindow[1] - self.timewindow[0]
884            start += self.timewindow[0]
885
886        # horizontal plotting
887        if self.traceOrder in [0, 1]:
888            timepos -= STATION_INFO + MARGINS[3]
889        # vertical plotting
890        elif self.traceOrder == 2:
891            if self.relativeAxis:
892                timepos -= length + MARGIN
893            else:
894                timepos -= pixel_width + MARGIN
895            timepos *= -1
896        elif self.traceOrder == 3:
897            timepos -= STATION_INFO
898
899        t = timepos * duration / pixel_width
900        timestamp = start + t
901
902        # do not return trace if timestamp is outside, region is enlarged
903        # by one pixel.
904        pixel_duration = duration/pixel_width
905        self.pixel_duration = pixel_duration
906        if trace and (trace.stats.starttime > timestamp + pixel_duration or \
907                             trace.stats.endtime + pixel_duration < timestamp):
908            trace = None
909
910        return trace, timestamp
911
912    #@timeit
913    def TraceAndTimeToScreen(self, trace, time):
914        """
915        Return x, y from given trace (midpoint) and time code.
916        """
917        if trace is None:
918            return
919        shx_graphics = trace.get_graphics()
920        if shx_graphics == None:
921            return
922
923        # horizontal
924        if self.traceOrder in [0, 1]:
925            pixel_width = self.width - STATION_INFO - MARGINS[1] - MARGINS[3]
926        # vertical
927        elif self.traceOrder in [2, 3]:
928            pixel_width = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
929
930        zerotime = trace.stats.starttime - trace.get_info("t-origin")
931        reltime = time - zerotime
932       
933        if self.timewindow[1] == None:
934            start = self.relstart
935            end = self.relend
936        else:
937            start = self.timewindow[0]
938            end = self.timewindow[1]
939       
940        # relative position inside trace window
941        relpos = (reltime - start) / (end - start) * pixel_width
942
943        # horizontal
944        if self.traceOrder in [0, 1]:
945            x, y = self.CalcScrolledPosition((
946                relpos + STATION_INFO + MARGINS[3],
947                shx_graphics.midpoint
948            ))
949        # vertical
950        elif self.traceOrder == 2:
951            x, y = self.CalcScrolledPosition(
952                     (shx_graphics.midpoint, self.height - relpos - STATION_INFO))
953        elif self.traceOrder == 3:
954            x, y = self.CalcScrolledPosition(
955                                   (shx_graphics.midpoint, relpos + STATION_INFO))
956        return x, y
957   
958
959    #@timeit
960    def _setup(self):
961        w, h = self.GetClientSize()
962        if self.traceOrder in [0, 1]:
963            if self.space is None:
964                self.space = h
965            self.SetVirtualSizeWH(w, self.space)
966            self.SetScrollRate(0, 20)
967        elif self.traceOrder in [2, 3]:
968            if self.space is None:
969                self.space = w
970            self.SetVirtualSizeWH(self.space, h)
971            self.SetScrollRate(20, 0)
972
973    #@timeit
974    def _captureMouse(self, evt):
975        self.CaptureMouse()
976
977        ## create overlay
978        #dc = wx.ClientDC(self)
979        #odc = wx.DCOverlay(self.overlay_drag, dc)
980        #odc.Clear()
981
982        self.dragStart = evt.GetPosition()
983        self._debug("mouse captured at", self.dragStart)
984
985    #@timeit
986    def _releaseMouse(self, evt):
987        self.ReleaseMouse()
988
989        ## restore view
990        #dc = wx.ClientDC(self)
991        #odc = wx.DCOverlay(self.overlay_drag, dc)
992        #odc.Clear()
993        #del odc
994        self.overlay_drag.Reset()
995
996        self._debug("mouse released")
997
998    #@timeit
999    def _drawPicksAndZoom(self):
1000        """
1001        Draw picks.
1002        """
1003        phaselist = PhaseList()
1004        _picks = {}
1005        for trc in self.traces:
1006            sname = "%s.%s" % (trc.stats.network,trc.stats.station)
1007            for phase in phaselist.getPhaseList(sname):
1008                if phase.comp and trc.stats.channel[-1] != phase.comp:
1009                    continue
1010                pcol = phaselist.picktypeColor( phase.picktype )
1011                if pcol not in _picks.keys():
1012                    _picks[pcol] = []
1013                _picks[pcol].append(
1014                    (phase.name,self.TraceAndTimeToScreen(trc,phase.picktime))
1015                )
1016
1017        if not _picks and not self.ZoomWindow:
1018            return
1019
1020        dc = wx.ClientDC(self)
1021        # odc disables redraw
1022        #odc = wx.DCOverlay(self.overlay_picks, dc)
1023        #odc.Clear()
1024        dc.SetBrush(wx.TRANSPARENT_BRUSH)
1025        dc.SetFont( wx.Font( 8, wx.DEFAULT, wx.NORMAL, wx.NORMAL ) )
1026
1027        th2 = self.traceheight / 2 - 2
1028        # draw picks
1029        for pcolor in _picks.keys():
1030            dc.SetPen(wx.Pen(pcolor, 1))
1031            dc.SetTextForeground(pcolor)
1032            for pick in _picks[pcolor]:
1033                if self.traceOrder in [0, 1]:
1034                    dc.DrawLine(
1035                        pick[1][0], pick[1][1] - th2,
1036                        pick[1][0], pick[1][1] + th2
1037                    )
1038                    if pick[0] != '_Mark_':
1039                        dc.DrawText(pick[0], pick[1][0] + 2, pick[1][1] - th2 - 10)
1040                elif self.traceOrder in [2, 3]:
1041                    dc.DrawLine(
1042                        pick[1][0] - th2, pick[1][1],
1043                        pick[1][0] + th2, pick[1][1],
1044                    )
1045                    if pick[0] != '_Mark_':
1046                        dc.DrawRotatedText(pick[0], pick[1][0] - th2 + 2,
1047                                                            pick[1][1] - 2, 90)
1048        #del odc
1049
1050        #draw zoom window
1051        if self.ZoomWindow:
1052            trace, start, end = self.ZoomWindow
1053            if  trace is not None:
1054                shx_graphics = trace.get_graphics()
1055                if shx_graphics == None:
1056                    return
1057                start = self.TraceAndTimeToScreen(trace, start)
1058                end = self.TraceAndTimeToScreen(trace, end)
1059                self._debug("draw zoom window")
1060                self.__zoombox( start, end, shx_graphics.midpoint,
1061                    color="Blue")
1062            else:
1063                self.zoomwdwwidth = 0
1064                self.ZoomWindow = None
1065                if plotter:
1066                    plotter.setZoomWindow( None, None, None )
1067                self.__zoombox( None, None, None )
1068
1069
1070    def _drawSinglePick( self, trace, tracetime, color=None ):
1071        "Just for quick respomse to the user."
1072        pname = self.last_defined_phase
1073        if not pname:
1074            pname = "NewPhase"
1075        if color == None:
1076            color = 'red'
1077        dc = wx.ClientDC(self)
1078        dc.SetPen(wx.Pen(color, 1))
1079        dc.SetTextForeground(color)
1080        dc.SetFont( wx.Font( 8, wx.DEFAULT, wx.NORMAL, wx.NORMAL ) )
1081        th2 = self.traceheight / 2 - 2
1082        x, y, = self.TraceAndTimeToScreen(trace,tracetime)
1083        dc.DrawLine( x, y-th2, x, y+th2 )
1084        if pname != '_Mark_':
1085            dc.DrawText( pname, x+2, y-th2-10 )
1086       
1087
1088    #@timeit
1089    def _drawTraces(self):
1090        """
1091        Do the real plotting of waveforms including station text and
1092        time scale.
1093        """
1094        # Get dimensions of the drawing area and number of traces to plot.
1095        # We cannot use the Screen module because of loop imports.
1096        if self._timewindow[1] is None:
1097            timewindow = get_runtime("extend")
1098        else:
1099            timewindow = self._timewindow
1100        self.width, self.height = self.GetVirtualSize()
1101        numTraces = len(self.traces) - Overlays.numberOfOverlays()
1102        globalnorm = self._normtype.startswith("A")
1103
1104        # Delete zoom window if number of traces has changed.
1105        # -> i.e. not drawn in this redraw.
1106        if self.last_number_of_traces != numTraces and self.ZoomWindow:
1107            self.ZoomWindow = None
1108            if plotter:
1109                plotter.setZoomWindow( None, None, None )
1110        self.last_number_of_traces = numTraces
1111        self._drawXor( 'clear' )
1112
1113        # How much space is left for each trace:
1114        #    theight: height of trace space in pixel units
1115        #    pltwidth: width of trace space in pixel units
1116        # Compute trace height (theight) as float, otherwise trace positioning
1117        # is weird when many traces on screen.
1118        if self.traceOrder in [0, 1]:
1119            if numTraces:
1120                theight = float(self.height - TIMESCALE - MARGINS[0] \
1121                    - MARGINS[2]) / numTraces
1122            else:
1123                theight = float(self.height - TIMESCALE)
1124            pltwidth = self.width - STATION_INFO - MARGINS[1] - MARGINS[3]
1125        elif self.traceOrder in [2, 3]:
1126            if numTraces:
1127                theight = (self.width - TIMESCALE - MARGINS[1] \
1128                    - MARGINS[3]) / numTraces
1129            theight = self.width - TIMESCALE
1130            pltwidth = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
1131        else:
1132            raise ValueError("unknown trace order %d" % self.traceorder)
1133       
1134        # Trace height used in scaling routines,
1135        #   make it visible to other methods.
1136        self.traceheight = theight
1137
1138        # Bitmap holding the final figure, canvas->DC drawing to this bitmap.
1139        self._bitmap = wx.EmptyBitmap(self.width, self.height)
1140        canvas = wx.MemoryDC(self._bitmap)
1141        canvas.SetBrush(wx.TRANSPARENT_BRUSH)
1142        canvas.Clear()
1143        canvas.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL))
1144
1145        # If nothing to display, clear eand exit.
1146        if numTraces == 0:
1147            # just clearing the screen
1148            dc = wx.AutoBufferedPaintDCFactory(self)
1149            dc.Clear()
1150            return
1151
1152        if self.traceOrder == 0:
1153            trcs = self.traces[::-1]
1154        elif self.traceOrder in [1, 2, 3]:
1155            trcs = self.traces
1156       
1157        # Should traces overlap or stay within their y-ranges?
1158        # Overlapping traces: create taller bitmaps than computed before
1159        # in theight (zoomed up by a zoom factor). When the trace-bitmaps
1160        # are copied to the screen buffer, they overlap each other.
1161        overlapping_traces = get_runtime( "overlapping_traces", True )
1162
1163        # Init overlays positions. Overlays change the trace counting, since
1164        # we need less trace positions.
1165        Overlays.initPlot( len(self.traces), self.traceOrder )
1166
1167        # Loop over traces.
1168        for i, t in enumerate(trcs):
1169            if getattr(t, "shx_graphics", None) is None:
1170                # Trace not yet established.
1171                continue
1172            if not self.relativeAxis:
1173                start = self.start
1174                end = self.end
1175            else:
1176                start = t.stats.starttime
1177                end = start + self.maxDuration
1178            try:
1179                tzoom = t.stats.sh.ZOOM
1180            except:
1181                tzoom = 1.
1182
1183            # zheight is the (integer) height of the drawing box, possibly
1184            # zoomed by a factor if overlapping_traces is selected.
1185            if overlapping_traces:
1186                # Increased zheight produces overlapping of traces on display.
1187                zheight = int( theight * tzoom )
1188                try:
1189                    prepzoom = 1./tzoom
1190                except:
1191                    prepzoom = 1.
1192            else:
1193                zheight = int( theight )
1194                prepzoom = 0.9  # why not 1.?
1195           
1196            # Limit zheight to screen height, otherwise we run into memory
1197            # problems.
1198            if zheight > self.height:
1199                zheight = self.height
1200
1201            # Amplitude scaling factor within bitmap.
1202            norm = globalnorm and self.maxAmplitude or self._normtype[1]
1203           
1204            # After eliminating t.prepare_image_data (too slow), this
1205            # shx_graphics-stuff is probably not needed any more.
1206            shx_graphics = t.get_graphics()
1207            if shx_graphics == None:
1208                continue
1209
1210            # Create an emtpy bitmap for this trace, but only if the pixel
1211            # height is larger than 4. Doesn't make sense to put time and
1212            # CPU into figures with only 2 pixels height. Draw simple line
1213            # instead.
1214            if zheight > 4:
1215                if self.traceOrder in [0, 1]:
1216                    bitmap = wx.EmptyBitmap(pltwidth, zheight)
1217                elif self.traceOrder in [2, 3]:
1218                    bitmap = wx.EmptyBitmap(zheight, pltwidth)
1219            else:
1220                bitmap = None
1221            if bitmap is not None:
1222                # buffer to draw in
1223                dbuffer = wx.MemoryDC(bitmap)
1224                #dbuffer = dbuffer
1225                dbuffer.SetBrush(wx.TRANSPARENT_BRUSH)
1226                dbuffer.Clear()
1227                dbuffer.SetPen(wx.Pen((45,45,45), 1))
1228            else:
1229                dbuffer = None
1230
1231            # Time position of trace.
1232            _shift = t.get_info("t-origin") - timewindow[0]
1233            if _shift < 0.:
1234                _shift = 0.
1235            # On positive shifts the bitmap is moved out of the screen
1236            # to the right. On negative shifts the bitmap is recomputed
1237            # and always starts at 0. Fails if the bitmap move completely
1238            # out to the left. Bitmap should be cleared in this case.
1239
1240            # calculate offset to global time scale
1241            plotStartSec = t.stats.starttime + _shift - start
1242            plotoffset = 0
1243            if plotStartSec or not self.relativeAxis:
1244                # re-calculate offset
1245                portion = plotStartSec / (timewindow[1] - timewindow[0])
1246                plotoffset = portion * pltwidth
1247
1248            # Get drawing attributes, color and other styles.
1249            attrib = t.get_info("attrib")
1250            try:
1251                style = get_style(attrib)
1252            except KeyError:
1253                style = get_default_attrib(t.get_info("METASTATUS")) #AttributeBlock()
1254            cmode = None
1255            try:
1256                color = map(int, style.color.split(','))
1257                cmode = 'i'
1258            except:
1259                try:
1260                    color = map(float, style.color.split(','))
1261                    cmode = 'f'
1262                except:
1263                    color = style.color
1264                    cmode = 'o'
1265            if cmode == 'i':
1266                if 0 < max(color) <= 1:
1267                    # e.g. 1,0,0 means red, multiply with 255
1268                    color = [255*x for x in color]
1269            elif cmode == 'f':
1270                color = map( int, [255*x for x in color] )
1271            linestyle = getattr(wx, style.linestyle.upper())
1272
1273            # Draw seismogram into trace bitmap.
1274            if dbuffer is not None:
1275                dbuffer.SetPen(wx.Pen(color, style.linewidth, linestyle))
1276                dbuffer.BeginDrawing()
1277                dbuffer.DrawLines(
1278                    self.prepImage(t,pltwidth,zheight,timewindow,prepzoom,norm)
1279                )
1280                dbuffer.EndDrawing()
1281
1282            # Copy trace bitmap to canvas
1283            if dbuffer is not None:
1284                if self.traceOrder in [0, 1]:
1285                    if overlapping_traces:
1286                        fac = i + 0.5 - 0.5*tzoom
1287                    else:
1288                        fac = i
1289                    plotpos = Overlays.plotPos( i+1, fac * theight + MARGINS[0])
1290                    canvas.Blit(
1291                        plotoffset + STATION_INFO + MARGINS[3], 
1292                        plotpos, 
1293                        pltwidth,
1294                        zheight,
1295                        dbuffer,
1296                        0,
1297                        0,
1298                        wx.AND
1299                    )
1300                elif self.traceOrder == 2:
1301                    canvas.Blit(i * theight, -plotoffset + MARGIN, zheight,
1302                                                pltwidth, dbuffer, 0, 0, wx.AND)
1303                elif self.traceOrder == 3:
1304                    canvas.Blit(i * theight, STATION_INFO + plotoffset, zheight,
1305                                                pltwidth, dbuffer, 0, 0, wx.AND)
1306
1307            # Put labels on traces.
1308            # Trace numbering
1309            if self.traceOrder in [1, 2, 3]:
1310                idx = i + 1
1311            elif self.traceOrder == 0:
1312                idx = -i + len(self.traces)
1313            # Currently no choice on type of label, just station and component.
1314            txt = "%d: %s %s" % (idx, t.stats.station, t.stats.channel[-1])
1315            canvas.SetPen(wx.Pen('Grey', 1, wx.LONG_DASH))
1316            # Draw labels and helper lines (zero lines)
1317            w, h, _, _ = canvas.GetFullTextExtent(txt)
1318            shx_graphics.midpoint = tmp = i * theight + theight//2 + MARGINS[0]
1319            if self.traceOrder in [0, 1]:
1320                canvas.DrawText(txt, 5 + MARGINS[3], tmp - h // 2)
1321                canvas.DrawLine(STATION_INFO + MARGINS[3], tmp,
1322                    self.width - MARGINS[1], tmp)
1323            elif self.traceOrder == 2:
1324                canvas.DrawRotatedText(txt, tmp - h // 2, self.height - MARGIN, 90)
1325                canvas.DrawLine(shx_graphics.midpoint, MARGIN,
1326                    tmp, self.height - STATION_INFO)
1327            elif self.traceOrder == 3:
1328                canvas.DrawRotatedText(txt, tmp - h // 2,
1329                                    STATION_INFO - (STATION_INFO - w) // 2, 90)
1330                canvas.DrawLine(tmp, STATION_INFO, tmp, self.height - MARGIN)
1331
1332            # End of trace loop
1333
1334        # Draw time axis.
1335        canvas.SetPen(wx.Pen('Black', 1))
1336        if self.relativeAxis:
1337            start, end = timewindow
1338
1339        if self.traceOrder in [0, 1]:
1340            PARTS = 5.  # axis split into X parts
1341            length = self.width - MARGINS[1] - MARGINS[3] - STATION_INFO  # pixel length of time axis
1342            fixpos = self.height - TIMESCALE - MARGINS[2]  # here: y coordinate
1343            varpos_start = STATION_INFO + MARGINS[3] # here: x start
1344        elif self.traceOrder in [2, 3]:
1345            PARTS = 4.
1346            length = self.height - MARGIN - STATION_INFO
1347            fixpos = self.width - TIMESCALE + 10  # here: x coordinate
1348            if self.traceOrder == 2:
1349                varpos_start = MARGIN  # here: y start
1350            elif self.traceOrder == 3:
1351                varpos_start = STATION_INFO
1352
1353        chunk_t, chunk, labeloffset, pixoffset, PARTS \
1354            = self.niceNumbers( start, end, length, PARTS )
1355        chunk2 = chunk / 5.
1356
1357        # basic time axis line
1358        if self.traceOrder in [0, 1]:
1359            canvas.DrawLine(varpos_start, fixpos,
1360                                                 length + varpos_start, fixpos)
1361        elif self.traceOrder in [2, 3]:
1362            canvas.DrawLine(fixpos, varpos_start,
1363                                                 fixpos, varpos_start + length)
1364
1365        # sections with ticks and time string
1366
1367        try:
1368            prec = abs(int(round(1 / math.log10(chunk_t))))
1369        except:
1370            prec = 1
1371        mask = "%%.%uf" % prec
1372        for i in range(int(PARTS)):
1373            if isinstance(start, UTCDateTime):
1374                timecode = labeloffset + start + i * chunk_t
1375                txt = timecode.strftime("%H:%M:%S.") + \
1376                                             str(timecode.microsecond//1000)
1377            else:
1378                txt = mask % (labeloffset + start + i * chunk_t)
1379
1380            tw = canvas.GetFullTextExtent(txt)[0]
1381
1382            if self.traceOrder in [0, 1]:
1383                varpos_current = pixoffset + varpos_start + i * chunk
1384                # time string
1385                canvas.DrawText(txt, varpos_current - tw / 2,
1386                                                fixpos + TIMESCALE_OFFSET_TEXT)
1387                # major tick
1388                canvas.DrawLine(varpos_current, fixpos,
1389                                varpos_current, fixpos + TIMESCALE_TICKS_MAJOR)
1390                # minor ticks
1391                for j in range(1, 5):
1392                    canvas.DrawLine(
1393                        varpos_current + j * chunk2,
1394                        fixpos,
1395                        varpos_current + j * chunk2,
1396                        fixpos + TIMESCALE_TICKS_MINOR
1397                    )
1398            elif self.traceOrder in [2, 3]:
1399                if self.traceOrder == 2:
1400                    varpos_current = pixoffset \
1401                        + varpos_start + (PARTS - i) * chunk
1402                elif self.traceOrder == 3:
1403                    varpos_current = pixoffset + varpos_start + i * chunk
1404                canvas.DrawRotatedText(txt, fixpos + TIMESCALE_OFFSET_TEXT,
1405                                                   varpos_current + tw / 2, 90)
1406                canvas.DrawLine(fixpos, varpos_current,
1407                                fixpos + TIMESCALE_TICKS_MAJOR, varpos_current)
1408                for j in range(1, 5):
1409                    if self.traceOrder == 2:
1410                        j -= 5
1411                    canvas.DrawLine(
1412                        fixpos,
1413                        varpos_current + j * chunk2,
1414                        fixpos + TIMESCALE_TICKS_MINOR,
1415                        varpos_current + j * chunk2
1416                     )
1417
1418        if self.do_screenshot:
1419            self.save_shot(canvas)
1420            self.do_screenshot = False
1421        # detach
1422        #ks canvas.SelectObject(wx.NullBitmap)
1423        del canvas
1424        self.Refresh()
1425        self.Update()
1426
1427    def prepImage( self, trc, width, height, timewindow, zoom, norm ):
1428        "Return polygon ready for plotting via wx."
1429
1430        windowstart, windowend = timewindow
1431        try:
1432            pt = trc.slice_relative(windowstart, windowend)
1433        except:
1434            return []
1435           
1436        # scaling in time direction
1437        duration_total = windowend - windowstart
1438        duration_trace = pt.stats.endtime - pt.stats.starttime
1439        pixel_width = duration_trace * width / duration_total
1440       
1441        # save pixel_width (but dont' really understand)
1442        shx_graphics = trc.get_graphics()
1443        if shx_graphics != None:
1444            shx_graphics.PlotPixels = pixel_width
1445
1446        # make sure, norm is a number
1447        if hasattr(norm, "lower"):
1448            norm = norm.upper()[0]
1449            if norm == "W":
1450                norm = abs(pt.max())
1451            elif norm == "T":
1452                norm = self.max()
1453            else:
1454                raise ValueError("Invalid input for normation!")
1455       
1456        # scaling in amplitude direction
1457        try:
1458            trczoom = trc.stats.sh.ZOOM
1459        except:
1460            trczoom = 1.0
1461        amplscale = 1. / norm * height / 2 * zoom * trczoom
1462        return zip(
1463            np.linspace( 0., pixel_width, len(pt.data) ),
1464            pt.data*amplscale + height/2
1465        )
1466
1467    #@timeit
1468    def save_shot(self, dc):
1469        """
1470        Save PNG screen shot (PS later)
1471        """
1472        size = dc.Size
1473
1474        shot = wx.EmptyBitmap(size.width, size.height)
1475        shot_dc = wx.MemoryDC()
1476        shot_dc.SelectObject(shot)
1477
1478        shot_dc.Blit(0, 0, size.width, size.height, dc, 0, 0)
1479        shot_dc.SelectObject(wx.NullBitmap)
1480        img = shot.ConvertToImage()
1481        img.SaveFile(self.do_screenshot, wx.BITMAP_TYPE_PNG)
1482        log_message("info", "screen dump saved in: %s" % self.do_screenshot)
1483   
1484    #@timeit
1485    def niceNumbers( self, start, end, pixwidth, parts ):
1486        "2150 surf min"
1487        num = (end - start) / parts
1488        pixscaling = pixwidth / (end-start)
1489        tmp = 10**int(math.log10(num))  # integer power of 10
1490        nn = tmp   # best nice number is either this number or *2, *5, *10
1491        dist = abs(tmp-num)
1492        for i in (2,5,10):
1493            ndist = abs(num - i*tmp)
1494            if ndist < dist:
1495                nn = i*tmp
1496                dist = ndist
1497        labelstart = nn*int(start/nn)
1498        if (labelstart-start) < -1.e-5:
1499            labelstart += nn
1500        repeat = int((end-labelstart)/nn) + 1
1501        pixspan = nn * pixscaling
1502        pixoffset = (labelstart-start) * pixscaling
1503        labeloffset = labelstart - start
1504        #print nn, labelstart, pixoffset, repeat, '--', num, '.', start, end
1505        return (nn, pixspan, labeloffset, pixoffset, repeat)
1506
1507    @staticmethod
1508    def _debug(*args):
1509        log_message('debug.wx', " ".join([str(i) for i in args]))
1510        #print "dbg:", " ".join([str(i) for i in args])
1511
1512    #@timeit
1513    def _resetTimeRange(self, **kwargs):
1514        """
1515        Gather information about traces. Called on redraw (at idle state).
1516        """
1517        mint = UTCDateTime() # now
1518        maxt = UTCDateTime(0) # 1970
1519        minrel = 1e20
1520        maxrel = 0
1521        maxDuration = 0
1522        amplitude = 0
1523
1524        for t in self.traces:
1525            s = t.get_info("relstart")
1526            e = t.get_info("relend")
1527            if s < minrel:
1528                minrel = s
1529            if e > maxrel:
1530                maxrel = e
1531            if t.stats.starttime < mint:
1532                mint = t.stats.starttime
1533            if t.stats.endtime > maxt:
1534                maxt = t.stats.endtime
1535            # needed for relative time axis
1536            l = e - s
1537            if l > maxDuration:
1538                maxDuration = l
1539            # normation
1540            # no time window set or normtype forces full traces
1541            #if self._timewindow[1] is None or self._normtype.startswith("A"):
1542            if self._timewindow[1] is None:
1543                data = t.data
1544            else:
1545                try:
1546                    data = t.get_datawindow(*self._timewindow)
1547                except:
1548                    continue
1549            t_ampl = max( abs(data.max()), abs(data.min()) )
1550            if t_ampl > amplitude:
1551                amplitude = t_ampl
1552
1553        self.start = mint
1554        self.relstart = minrel
1555        self.end = maxt
1556        self.relend = maxrel
1557        self.maxDuration = maxDuration
1558        self.maxAmplitude = amplitude
1559        recent = self.timewindow
1560        self.timewindow = get_runtime("timewindow")
1561        if recent != self.timewindow:
1562            # time window changed, delete zoom box
1563            self.ZoomWindow = None
1564            if plotter:
1565                plotter.setZoomWindow( None, None, None )
1566
1567   
1568    #@timeit
1569    def __zoombox(self, start, end, midpoint, color="DARKORANGE", size=1):
1570        if start == None:
1571            self._drawXor( 'rect', (None,None,None,None) )
1572            return
1573        marginfactor = 0.3
1574        mf2 = (1 - marginfactor) * 2
1575        th2 = self.traceheight / 2
1576        offset = midpoint - th2 - self.Scrolled
1577        if self.traceOrder in [0, 1]:
1578            self._drawXor( 'rect', (start[0], offset + th2 * marginfactor,
1579                end[0] - start[0], th2 * mf2) )
1580        elif self.traceOrder in [2, 3]:
1581            self._drawXor( 'rect', (offset + th2 * marginfactor, start[1],
1582                th2 * mf2, end[1] - start[1]) )
1583
1584
1585class magnifyWindow(wx.Frame):
1586
1587    def __init__( self, parent, title, size=(640,100), position=(0,0) ):
1588        wx.Frame.__init__(self, parent, title=title, size=size,
1589            style=wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE |
1590            wx.FRAME_FLOAT_ON_PARENT)
1591        self.SetPosition( position )
1592        self.canvas = magnifyCanvas( self )
1593        self.Disable()
1594        self.Show()
1595        self.Enable()
1596
1597
1598class plotterWindow(wx.Frame):
1599    """
1600    Basic program frame including menu.
1601   
1602    Shortcuts
1603           b(Beam) d(DelTimeWdw) g(PhasePg) n(PhasePn) p(PhaseP) s(SetTimeWdw)
1604           u(Undo Filter) 0(PrevEvent) 1(NextEvent) 2(PhaseSg) 8(Filter 1-8Hz)
1605    Ctrl-  f(FK) l(FilterSROLP) n(Norm) o(Overlapping) q(Quit) s(SaveT&P)
1606           w(FilterWWSSNSP) y(SortByDistance)
1607    """
1608    def __init__(self, parent, title, size=(640,280), position=(100,100)):
1609        # save instance for external access
1610        global plotter
1611        plotter = self
1612        wx.Frame.__init__(self, parent, title=title, size=size,
1613              style=wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE |
1614                    wx.FRAME_FLOAT_ON_PARENT)
1615
1616        self.SetPosition(position)
1617        self.addMenuEntries()
1618        self.canvas = traceCanvas(self, [])
1619        self.CreateStatusBar()
1620        self.SetStatusText("%s %s" % (NAME, VERSION))
1621        self.Disable()
1622        self.Show()
1623        self.Enable()
1624        self._user_selection = 999.
1625        self._zoomwdw = None
1626        self._seltrace = None
1627        self.flag_beam = False
1628        self.flag_norm = False
1629        self.flag_overlap = True
1630        self.pardialog = None
1631        self.magnify = None
1632   
1633    def openMagnifyWindow( self, e ):
1634        self.magnify = magnifyWindow( None, 'SHX magnify' )
1635   
1636    def showMessage( self, msg, error=False ):
1637        mode = wx.OK
1638        if error:
1639            mode |= wx.ICON_ERROR
1640        dlg = wx.MessageDialog(self, msg, "An error occured!", mode )
1641        dlg.ShowModal()
1642        dlg.Destroy()
1643       
1644    def addMenuEntries( self ):
1645        menubar = wx.MenuBar()
1646        # file menu
1647        fileMenu = wx.Menu()
1648        self.addEntry( fileMenu, 'Read Data File(s)',
1649            'Read data file(s) in one of various formats', self.OnReadDataFile )
1650        self.addEntry( fileMenu, 'Write Data File',
1651            'Write all traces into file. Specify format as extension.',
1652            self.OnWriteDataFile )
1653        fileMenu.AppendSeparator()
1654        self.addEntry( fileMenu, 'Read FDSN net/station list',
1655            'Read station list from FDSN webserver', self.OnReadFdsnStations )
1656        self.addEntry( fileMenu, 'Read FDSN circle area',
1657            'Read stations in circle area from FDSN webserver',
1658            self.OnReadFdsnCircle )
1659        fileMenu.AppendSeparator()
1660        self.addEntry( fileMenu, 'Complete Metadata (FDSN)',
1661            'Complete metadata of traces using FDSN webserver',
1662            self.OnCompleteMeta )
1663        fileMenu.AppendSeparator()
1664        self.addEntry( fileMenu, '&Quit\tCtrl+Q', 'Quit Graphics', self.OnQuit )
1665        # window menu
1666        windowMenu = wx.Menu()
1667        self.addEntry( windowMenu, '&Set Time Window\tS',
1668            'Set Zoom window as new time window', self.OnSetTimeWindow )
1669        self.addEntry( windowMenu, '&Del Time Window\tD',
1670            'Delete time window', self.OnDelTimeWindow )
1671        self.addEntry( windowMenu, '&Norm All/Single\tCtrl+N',
1672            'Normalize All Traces Together / Normalize Traces Separately',
1673            self.OnNorm )
1674        self.addEntry( windowMenu, '&Overlapping/Clipped Traces\tCtrl+O',
1675            'Traces overlap when zooming / Traces are clipped when zooming '\
1676            +'amplitudes', self.OnOverlappingTraces )
1677        # traces menu
1678        tracesMenu = wx.Menu()
1679        self.addEntry( tracesMenu, '&Demean\tCtrl+D',
1680            'Remove mean value from all traces', self.OnDemean )
1681        self.addEntry( tracesMenu, 'Sort by Distance\tCtrl+Y',
1682            'Sort traces by distance', self.OnSortByDistance )
1683        tracesMenu.AppendSeparator()
1684        self.addEntry( tracesMenu, 'Delete Selected Trace',
1685            'Delete trace with zoom window', self.OnDeleteTrace )
1686        self.addEntry( tracesMenu, 'Delete Selected Station',
1687            'Delete all traces of this station', self.OnDeleteStation )
1688        self.addEntry( tracesMenu, 'Delete Selected And Above',
1689            'Delete trace with zoom window and all above',
1690            self.OnDeleteTraceAndAbove )
1691        # array menu
1692        arrayMenu = wx.Menu()
1693        self.addEntry( arrayMenu, 'FK',
1694            'Compute FK for all traces on selected time window', self.OnFK )
1695        self.addEntry( arrayMenu, '&FK (Auto Freq)\tCtrl+F',
1696            'Compute FK with automatic freq window', self.OnFKAutoFreq )
1697        self.addEntry( arrayMenu, 'Beam / Del Beam\tB',
1698            'Compute/delete beam for all traces', self.OnBeam )
1699        # simulate menu
1700        simulateMenu = wx.Menu()
1701        self.addEntry( simulateMenu, 'Simulate &WWSSN-SP\tCtrl+W',
1702            'Simulate WWSSN-SP instrument on all traces', self.OnSimulateWWSSNSP )
1703        self.addEntry( simulateMenu, 'Simulate WWSSN-LP',
1704            'Simulate WWSSN-LP instrument on all traces', self.OnSimulateWWSSNLP )
1705        self.addEntry( simulateMenu, 'Simulate LRSM-SP',
1706            'Simulate WWSSN-SP instrument on all traces', self.OnSimulateLRSMSP )
1707        self.addEntry( simulateMenu, 'Simulate LRSM-LP',
1708            'Simulate WWSSN-LP instrument on all traces', self.OnSimulateLRSMLP )
1709        self.addEntry( simulateMenu, 'Simulate KIRNOS',
1710            'Simulate KIRNOS instrument on all traces', self.OnSimulateKIRNOS )
1711        self.addEntry( simulateMenu, 'Simulate SRO-&LP\tCtrl+L',
1712            'Simulate SRO-LP instrument on all traces', self.OnSimulateSROLP )
1713        simulateMenu.AppendSeparator()
1714        self.addEntry( simulateMenu, 'Filter BP 1-8Hz\t8',
1715            'Apply Butterworth bandpass 1-8Hz', self.OnFilterBP_1_8 )
1716        simulateMenu.AppendSeparator()
1717        self.addEntry( simulateMenu, 'Undo Filter\tu',
1718            'Return to original traces', self.OnSimulateUndo )
1719        # events menu
1720        eventsMenu = wx.Menu()
1721        self.addEntry( eventsMenu, 'Get Eventlist',
1722            'Retrieve event list from FDSN server', self.OnGetEventlist )
1723        self.addEntry( eventsMenu, 'Next Event\t1',
1724            'Prepare next event time from list', self.OnNextEvent )
1725        self.addEntry( eventsMenu, 'Previous Event\t0',
1726            'Prepare previous event time from list', self.OnPrevEvent )
1727        phaseMenu = wx.Menu()
1728        self.addEntry( phaseMenu, 'Pg\tg',
1729            'Set phase for picking to Pg', self.OnSetPhase_Pg )
1730        self.addEntry( phaseMenu, 'Pn\tn',
1731            'Set phase for picking to Pn', self.OnSetPhase_Pn )
1732        self.addEntry( phaseMenu, 'Sg\t2',
1733            'Set phase for picking to Sg', self.OnSetPhase_Sg )
1734        self.addEntry( phaseMenu, 'Sn',
1735            'Set phase for picking to Sn', self.OnSetPhase_Sn )
1736        self.addEntry( phaseMenu, 'P\tp',
1737            'Set phase for picking to P', self.OnSetPhase_P )
1738        self.addEntry( phaseMenu, 'pP',
1739            'Set phase for picking to pP', self.OnSetPhase_pP )
1740        self.addEntry( phaseMenu, 'sP',
1741            'Set phase for picking to sP', self.OnSetPhase_sP )
1742        self.addEntry( phaseMenu, 'PP',
1743            'Set phase for picking to PP', self.OnSetPhase_PP )
1744        self.addEntry( phaseMenu, 'S',
1745            'Set phase for picking to S', self.OnSetPhase_S )
1746        self.addEntry( phaseMenu, 'PKP',
1747            'Set phase for picking to PKP', self.OnSetPhase_PKP )
1748        self.addEntry( phaseMenu, 'PKPdf',
1749            'Set phase for picking to PKPdf', self.OnSetPhase_PKPdf )
1750        self.addEntry( phaseMenu, 'PKPbc',
1751            'Set phase for picking to PKPbc', self.OnSetPhase_PKPbc )
1752        self.addEntry( phaseMenu, 'PKPab',
1753            'Set phase for picking to PKPab', self.OnSetPhase_PKPab )
1754        self.addEntry( phaseMenu, 'pPKPdf',
1755            'Set phase for picking to pPKPdf', self.OnSetPhase_pPKPdf )
1756        self.addEntry( phaseMenu, 'pPKPbc',
1757            'Set phase for picking to pPKPbc', self.OnSetPhase_pPKPbc )
1758        self.addEntry( phaseMenu, 'pPKPab',
1759            'Set phase for picking to pPKPab', self.OnSetPhase_pPKPab )
1760        self.addEntry( phaseMenu, 'Use ev_phase',
1761            'Use phase name from parameter ev_phase',
1762            self.OnSetPhaseUseEvPhase )
1763        # control menu
1764        controlMenu = wx.Menu()
1765        self.addEntry( controlMenu, 'Save Traces/Params\tCtrl+S',
1766            'Save trace info and parameters into a recovery command file',
1767            self.OnSaveTracesAndParamsQuick )
1768        self.addEntry( controlMenu, 'Save Traces/Params as ...',
1769            'Save trace info and parameters into a recovery command file',
1770            self.OnSaveTracesAndParams )
1771        self.addEntry( controlMenu, 'Recover Traces/Params',
1772            'Recover traces and parameters from default recovery',
1773            self.OnRecoverTracesAndParamsQuick )
1774        self.addEntry( controlMenu, 'Recover Traces/Params from ...',
1775            'Recover traces and parameters from a recovery command file',
1776            self.OnRecoverTracesAndParams )
1777        tracesMenu.AppendSeparator()
1778        self.addEntry( controlMenu, 'Open Parameter Window',
1779            'Open parameter dialog', self.OnOpenParams )
1780        # test menu
1781        testMenu = wx.Menu()
1782        self.addEntry( testMenu, 'Read GR * BHZ of an event',
1783            'fdsnws gr * * bhz 7-aug-15_00:16:30 420', self.OnTest1 )
1784        self.addEntry( testMenu, 'Read SX,TH * BHZ of an event',
1785            'fdsnws sx,th * * bhz 7-aug-15_00:16:30 420', self.OnTest2 )
1786        # put menus and menu bar in place
1787        menubar.Append( fileMenu, 'File' )
1788        menubar.Append( windowMenu, 'Display' )
1789        menubar.Append( tracesMenu, 'Traces' )
1790        menubar.Append( arrayMenu, 'Array' )
1791        menubar.Append( simulateMenu, 'Filter' )
1792        menubar.Append( eventsMenu, 'Events' )
1793        menubar.Append( phaseMenu, 'Phases' )
1794        menubar.Append( controlMenu, 'Control' )
1795        menubar.Append( testMenu, 'Test' )
1796        self.SetMenuBar( menubar )
1797        self.Centre()
1798   
1799    def addEntry( self, menutitle, entrytext, entrydescr, callback ):
1800        #self.menu_entry_id += 1
1801        #qmi = wx.MenuItem( menutitle, self.menu_entry_id, entrytext,
1802        #    help=entrydescr )
1803        #menutitle.AppendItem( qmi )
1804        #self.Bind( wx.EVT_MENU, callback, id=self.menu_entry_id )       
1805        item = menutitle.Append( wx.ID_ANY, entrytext, entrydescr )
1806        self.Bind( wx.EVT_MENU, callback, item )
1807
1808    def OnQuit( self, e ):
1809        # Cannot quit SHX, _sendShCommand waits for command to be executed
1810        # i.e. no way to terminate wx smoothly. Quit SHX after Close also not
1811        # possible, runs into communication issues between threads.
1812        #_sendShCommand( "quit y" )
1813        self.Close()
1814   
1815    def OnNorm( self, e ):
1816        if self.flag_norm:
1817            _sendShCommand( 'norm aw' )
1818        else:
1819            _sendShCommand( 'norm sw' )
1820        self.flag_norm = not self.flag_norm
1821
1822    def OnOverlappingTraces( self, e ):
1823        if self.flag_overlap:
1824            _sendShCommand( 'fct overlapping_traces false' )
1825        else:
1826            _sendShCommand( 'fct overlapping_traces true' )
1827        self.flag_overlap = not self.flag_overlap
1828
1829    def OnDemean( self, e ):
1830        _sendShCommand( 'demean all' )
1831   
1832    def OnDeleteTrace( self, e ):
1833        if self._seltrace == None:
1834            self.showMessage( "no trace selected" )
1835        else:
1836            _sendShCommand( "del %d" % self._seltrace )
1837   
1838    def OnDeleteStation( self, e ):
1839        if self._seltrace == None:
1840            self.showMessage( "no trace selected" )
1841            return
1842        trclist = traces_from_list( "%d" % self._seltrace )
1843        if len(trclist) != 1:
1844            self.showMessage( "Program bug: should select only one trace" )
1845            return
1846        trc = trclist[0]
1847        _sendShCommand( "del _station(%s)" % trc.stats.station )
1848
1849    def OnDeleteTraceAndAbove( self, e ):
1850        if self._seltrace == None:
1851            self.showMessage( "no trace selected" )
1852        else:
1853            _sendShCommand( "del |%d|-|$dsptrcs|" % self._seltrace )
1854
1855    def OnTest1( self, e ):
1856        _sendShCommand( 'fdsnws gr * * bhz 7-aug-15_00:16:30 420' )
1857
1858    def OnTest2( self, e ):
1859        _sendShCommand( 'fdsnws sx,th * * bhz 7-aug-15_00:16:30 420' )
1860
1861    def OnSetTimeWindow( self, e ):
1862        _sendShCommand( 'stw %g %g' % self._zoomwdw )
1863
1864    def OnDelTimeWindow( self, e ):
1865        _sendShCommand( 'dtw' )
1866
1867    def OnReadDataFile( self, e ):
1868        "Read one or more data files."
1869        style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
1870        dlg = wx.FileDialog( self, "Select Data File", defaultDir=os.getcwd(),
1871            style=style )
1872        dlg.ShowModal()
1873        filelist = dlg.GetPaths()
1874        retcode = dlg.GetReturnCode()
1875        dlg.Destroy()
1876        if retcode != wx.ID_OK:
1877            return
1878        cmd = '\n'.join(["@READO %s ALL" % fname.replace('/','\\') \
1879            for fname in filelist])
1880        _sendShCommand( cmd )
1881   
1882    def OnWriteDataFile( self, e ):
1883        "Writes all traces to file."
1884        legal_formats = ('mseed','q','gse','gse2','sac')
1885        style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
1886        dlg = wx.FileDialog( self, "Select Output File", defaultDir=os.getcwd(),
1887            style=style )
1888        dlg.ShowModal()
1889        outfile = dlg.GetPath()
1890        retcode = dlg.GetReturnCode()
1891        dlg.Destroy()
1892        if retcode != wx.ID_OK:
1893            return
1894        format = os.path.splitext(outfile)[1].lstrip('.').lower()
1895        if format not in legal_formats:
1896            msg = "Format '%s' not supported. Nothing written." % format
1897            self.showMessage( msg, error=True )
1898            return
1899        cmd = "@WRITEO %s ALL %s" % (outfile.replace('/','\\'),format.upper())
1900        _sendShCommand( cmd )
1901   
1902    def OnReadFdsnStations( self, e ):
1903        ap = AnalysisPar()
1904        cmd = "fdsnws %s %s %s %s %s %s /addr=%s" % (
1905            ap.getValueAsString( 'readws_net' ),
1906            ap.getValueAsString( 'readws_station' ),
1907            ap.getValueAsString( 'readws_loc' ),
1908            ap.getValueAsString( 'readws_chan' ),
1909            ap.getValueAsString( 'read_time' ),
1910            ap.getValueAsString( 'read_length' ),
1911            ap.getValueAsString( 'readws_server' ),
1912        )
1913        cmd += "\nmeta all complete /addr=%s" \
1914            % ap.getValueAsString('readws_server')
1915        _sendShCommand( cmd )       
1916
1917    def OnReadFdsnCircle( self, e ):
1918        ap = AnalysisPar()
1919        cmd = "fdsnws %s,%s,%s * * %s %s %s /addr=%s" % (
1920            ap.getValueAsString( 'sta_latitude' ),
1921            ap.getValueAsString( 'sta_longitude' ),
1922            ap.getValueAsString( 'sta_radius' ),
1923            ap.getValueAsString( 'readws_chan' ),
1924            ap.getValueAsString( 'read_time' ),
1925            ap.getValueAsString( 'read_length' ),
1926            ap.getValueAsString( 'readws_server' ),
1927        )
1928        cmd += "\nmeta all complete /addr=%s" \
1929            % ap.getValueAsString('readws_server')
1930        _sendShCommand( cmd )
1931   
1932    def OnCompleteMeta( self, e ):
1933        ap = AnalysisPar()
1934        _sendShCommand(
1935            "meta all complete /addr=%s" % ap.getValueAsString('readws_server')
1936        )
1937
1938    def OnSortByDistance( self, e ):
1939        ap = AnalysisPar()
1940        lat = ap.getValueAsString( 'epi_latitude' )
1941        lon = ap.getValueAsString( 'epi_longitude' )
1942        if not lat or not lon:
1943            self.showMessage( "no epicenter defined" )
1944            return
1945        _sendShCommand( "sort_by_distance %s %s" % (lat,lon) )
1946   
1947    def OnFK( self, e ):
1948        if not self._zoomwdw:
1949            return
1950        timea, timeb = self._zoomwdw
1951        if timea == None or timeb == None:
1952            return
1953        ap = AnalysisPar()
1954        cmd = "shx_menu_fk %g %g %s %s %s" % (timea,timeb,
1955            ap.getValueAsString('fk_min_freq'),
1956            ap.getValueAsString('fk_max_freq'),
1957            ap.getValueAsString('fk_max_slowness'))
1958        _sendShCommand( cmd )
1959   
1960    def OnFKAutoFreq( self, e ):
1961        if not self._zoomwdw:
1962            return
1963        timea, timeb = self._zoomwdw
1964        if timea == None or timeb == None:
1965            return
1966        frq = 1./(timeb-timea)
1967        ap = AnalysisPar()
1968        cmd = "shx_menu_fk %g %g %s %s %s" % (timea,timeb,frq,(10.*frq),
1969            ap.getValueAsString('fk_max_slowness'))
1970        _sendShCommand( cmd )
1971    def OnBeam( self, e ):
1972        if self.flag_beam:
1973            _sendShCommand( "shx_menu_delbeam" )
1974        else:
1975            _sendShCommand( "shx_menu_beam" )
1976        self.flag_beam = not self.flag_beam
1977   
1978    def OnSimulateWWSSNSP( self, e ):
1979        _sendShCommand( 'shx_menu_simulate wwssn-sp' )
1980
1981    def OnSimulateWWSSNLP( self, e ):
1982        _sendShCommand( 'shx_menu_simulate wwssn-lp' )
1983
1984    def OnSimulateLRSMSP( self, e ):
1985        _sendShCommand( 'shx_menu_simulate lrsm-sp' )
1986
1987    def OnSimulateLRSMLP( self, e ):
1988        _sendShCommand( 'shx_menu_simulate lrsm-lp' )
1989
1990    def OnSimulateKIRNOS( self, e ):
1991        _sendShCommand( 'shx_menu_simulate kirnos' )
1992
1993    def OnSimulateSROLP( self, e ):
1994        _sendShCommand( 'shx_menu_simulate sro-lp' )
1995
1996    def OnFilterBP_1_8( self, e ):
1997        _sendShCommand( 'shx_menu_filter bp_1hz_8hz_4' )
1998
1999    def OnSimulateUndo( self, e ):
2000        _sendShCommand( 'shx_menu_simulate undo' )
2001   
2002    def OnOpenParams( self, e ):
2003        self.pardialog = ParamDialog( self )
2004        self.pardialog.Show()
2005
2006    def OnGetEventlist( self, e ):
2007        _sendShCommand( 'shx_menu_get_eventlist' )
2008
2009    def OnNextEvent( self, e ):
2010        _sendShCommand( 'shx_menu_next_event;;' )
2011
2012    def OnPrevEvent( self, e ):
2013        _sendShCommand( 'shx_menu_next_event -1' )
2014       
2015    def OnSaveTracesAndParamsQuick( self, e ):
2016        sloutfile = 'TRACES_PARAMS_RECOVER.SHC'
2017        cmd = "param savetraces %s\n" % sloutfile\
2018            +"param saveascmd %s /append" % sloutfile
2019        _sendShCommand( cmd )
2020   
2021    def OnSaveTracesAndParams( self, e ):
2022        style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
2023        dlg = wx.FileDialog( self, "Select Save File", defaultDir=os.getcwd(),
2024            style=style, wildcard='*.SHC' )
2025        dlg.ShowModal()
2026        outfile = dlg.GetFilename()
2027        retcode = dlg.GetReturnCode()
2028        dlg.Destroy()
2029        if retcode != wx.ID_OK:
2030            return
2031        sloutfile = outfile.replace("/","\\")
2032        cmd = "param savetraces %s\n" % sloutfile\
2033            +"param saveascmd %s /append" % sloutfile
2034        _sendShCommand( cmd )
2035   
2036    def OnRecoverTracesAndParamsQuick( self, e ):
2037        _sendShCommand( 'TRACES_PARAMS_RECOVER' )
2038   
2039    def OnRecoverTracesAndParams( self, e ):
2040        style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
2041        dlg = wx.FileDialog( self, "Select Recovery File",
2042            defaultDir=os.getcwd(), style=style, wildcard='*.SHC' )
2043        dlg.ShowModal()
2044        recfile = dlg.GetFilename()
2045        retcode = dlg.GetReturnCode()
2046        dlg.Destroy()
2047        if retcode != wx.ID_OK:
2048            return
2049        slrecfile = recfile.replace('/','\\')
2050        if slrecfile.endswith('.SHC'):
2051            slrecfile = os.path.splitext(slrecfile)[0]
2052        _sendShCommand( slrecfile )
2053
2054    def OnSetPhase_Pg( self, e ):
2055        _sendShCommand( "@PHASE DEFAULT_PHASE Pg" )
2056    def OnSetPhase_Pn( self, e ):
2057        _sendShCommand( "@PHASE DEFAULT_PHASE Pn" )
2058    def OnSetPhase_Sg( self, e ):
2059        _sendShCommand( "@PHASE DEFAULT_PHASE Sg" )
2060    def OnSetPhase_Sn( self, e ):
2061        _sendShCommand( "@PHASE DEFAULT_PHASE Sn" )
2062    def OnSetPhase_P( self, e ):
2063        _sendShCommand( "@PHASE DEFAULT_PHASE P" )
2064    def OnSetPhase_pP( self, e ):
2065        _sendShCommand( "@PHASE DEFAULT_PHASE pP" )
2066    def OnSetPhase_sP( self, e ):
2067        _sendShCommand( "@PHASE DEFAULT_PHASE sP" )
2068    def OnSetPhase_PP( self, e ):
2069        _sendShCommand( "@PHASE DEFAULT_PHASE PP" )
2070    def OnSetPhase_S( self, e ):
2071        _sendShCommand( "@PHASE DEFAULT_PHASE S" )
2072    def OnSetPhase_PKP( self, e ):
2073        _sendShCommand( "@PHASE DEFAULT_PHASE PKP" )
2074    def OnSetPhase_PKPdf( self, e ):
2075        _sendShCommand( "@PHASE DEFAULT_PHASE PKPdf" )
2076    def OnSetPhase_PKPbc( self, e ):
2077        _sendShCommand( "@PHASE DEFAULT_PHASE PKPbc" )
2078    def OnSetPhase_PKPab( self, e ):
2079        _sendShCommand( "@PHASE DEFAULT_PHASE PKPab" )
2080    def OnSetPhase_pPKPdf( self, e ):
2081        _sendShCommand( "@PHASE DEFAULT_PHASE pPKPdf" )
2082    def OnSetPhase_pPKPbc( self, e ):
2083        _sendShCommand( "@PHASE DEFAULT_PHASE pPKPbc" )
2084    def OnSetPhase_pPKPab( self, e ):
2085        _sendShCommand( "@PHASE DEFAULT_PHASE pPKPab" )
2086
2087    def OnSetPhaseUseEvPhase( self, e ):
2088        _sendShCommand( "@PHASE DEFAULT_PHASE %s"
2089            % AnalysisPar().getValueAsString('ev_phase'))
2090
2091    def redraw(self):
2092        self.canvas.OnSize(None)
2093   
2094    def setZoomWindow( self, trace, start, end ):
2095        self._seltrace = trace
2096        self._zoomwdw = (start,end)
2097
2098
2099class ParamDialog(wx.Dialog):
2100
2101    def __init__( self, parent ):
2102        wx.Dialog.__init__( self, parent, -1, "Parameters", size=(350,750) )
2103        #sizer =  self.CreateTextSizer('Param X')
2104       
2105        ypos = 10
2106        height = 26
2107        textwidth = 270
2108        btnwidth = 70
2109        space = 11
2110       
2111        self.SetBackgroundColour( 'white' )
2112        self.vtext = {}
2113        self.vbutton = {}
2114
2115        self.ap = AnalysisPar()
2116        for pname in self.ap.sortedParams():
2117            pvalue = self.ap.getValueAsString( pname )
2118            ptext = "%s : %s" % (pname,pvalue)
2119            self.vbutton[pname] = wx.Button( self, label='Change',
2120                pos=(space,ypos-4), size=(btnwidth,height) )
2121            self.vtext[pname] = wx.StaticText( self, label=ptext,
2122                pos=(btnwidth+2*space,ypos), size=(textwidth,height) )
2123            #self.vtext[pname].SetBackgroundColour( 'red' )
2124            self.Bind( wx.EVT_BUTTON, partial(self.OnButton,pname),
2125                self.vbutton[pname] )
2126            ypos += 28
2127   
2128    def OnButton( self, pname, e, *args ):
2129        oldvalue = self.ap.getValueAsString( pname )
2130        ptype = self.ap.getType( pname )
2131        dlg = QueryString( self, "Change %s" % pname,
2132            "New value for '%s' (type %s):" % (pname,ptype), oldvalue )
2133        btn = dlg.ShowModal()
2134        result = dlg.getValue()
2135        dlg.Destroy()
2136        if result == None:
2137            # Clicked on Cancel
2138            return
2139        if self.validate( result, ptype ):
2140            if result == "":
2141                cmd = "param set %s ;;" % pname
2142            else:
2143                # phase names need capcnv switched off
2144                cmd = "@PARAM SET %s %s" % (pname,result)
2145            _sendShCommand( cmd )
2146        else:
2147            msg = "Illegal input: got '%s' but need type %s" % (result,ptype)
2148            dlg = wx.MessageDialog(self, msg, "Illegal input",
2149                                                     wx.OK | wx.ICON_ERROR)
2150            dlg.ShowModal()
2151            dlg.Destroy()
2152        #self.updateVariables() # done via command line
2153   
2154    def updateVariables( self, *args ):
2155        for pname in self.ap.sortedParams():
2156            pvalue = self.ap.getValueAsString( pname )
2157            self.vtext[pname].SetLabel( "%s : %s" % (pname,pvalue) )
2158   
2159    def validate( self, result, ptype ):
2160        if ptype == 'int':
2161            try:
2162                i = int( result )
2163                return True
2164            except:
2165                return False
2166        elif ptype in ('float','floatN'):
2167            if ptype == 'floatN' and result == "":
2168                return True
2169            try:
2170                i = float( result )
2171                return True
2172            except:
2173                return False
2174        elif ptype == 'datetime':
2175            try:
2176                r = toUTCDateTime( result )
2177                return (r != None)
2178            except:
2179                return False
2180        elif ptype in ('string','file'):
2181            return (result != "")
2182        elif ptype == 'fileN':
2183            return True
2184        else:
2185            print "Program bug, unknown type '%s'" % ptype
2186            return False
2187
2188
2189class QueryString(wx.Dialog):
2190
2191    def __init__( self, parent, title, prompt, oldvalue ):
2192        wx.Dialog.__init__( self, parent, -1, title, size=(350,120) )
2193        self.SetBackgroundColour( 'white' )
2194        wx.StaticText( self, label=prompt, pos=(10,10) )
2195        self.text = wx.TextCtrl( self, value=oldvalue, pos=(10,30),
2196            size=(200,26) )
2197        tbtn = wx.Button( self, label='OK', pos=(50,70) )
2198        self.Bind( wx.EVT_BUTTON, self.OnTextOk, tbtn )
2199        cbtn = wx.Button( self, label='Cancel', pos=(150,70) )
2200        self.Bind( wx.EVT_BUTTON, self.OnTextCancel, cbtn )
2201        self.result = None
2202   
2203    def OnTextOk( self, e ):
2204        self.result = self.text.GetValue()
2205        self.Destroy()
2206   
2207    def OnTextCancel( self, e ):
2208        self.result = None
2209        self.Destroy()
2210   
2211    def getValue( self ):
2212        return self.result
2213       
2214
2215
2216
2217def _sendShCommand( cmdstring, name="default" ):
2218    "Send command to command line interpreter."
2219    wx.BeginBusyCursor()
2220    try:
2221        msgs.sendMessage("ui.command", cmd=cmdstring, name=name )
2222        exc = None
2223    except Exception as exc:
2224        pass
2225    wx.EndBusyCursor()
2226    if exc:
2227        raise exc
2228
2229
2230def ui_events(func):
2231    """
2232    """
2233    def wrapper(payload, msgid):
2234        result = func(payload)
2235        if msgid is not None:
2236            msgs.sendMessage(msgid, value=result, msgid=msgid)
2237    return wrapper
2238
2239
2240def _cursorNameToID( mode ):
2241    mode = mode.lower()
2242    if mode == "cross":
2243        _c = wx.CURSOR_CROSS
2244    elif mode == "normal":
2245        _c = wx.CURSOR_DEFAULT
2246    elif mode == "left":
2247        _c = wx.CURSOR_POINT_LEFT
2248    elif mode == "right":
2249        _c = wx.CURSOR_POINT_RIGHT
2250    elif mode == "busy":
2251        _c = wx.CURSOR_WAIT
2252    elif mode == "wait":
2253        _c = wx.CURSOR_ARROWWAIT
2254    return _c
2255
2256
2257@ui_events
2258def __set_cursor(mode):
2259    """
2260    Interface method for altering cursor style on canvas. Thread-safe.
2261    """
2262    _c = _cursorNameToID( mode )
2263    myCursor = wx.StockCursor(_c)
2264    global plotter
2265    wx.CallAfter(plotter.canvas.SetCursor, myCursor)
2266subscribe_ui_event(__set_cursor, "cursor")
2267
2268
2269@ui_events
2270def __select_gui(mode):
2271    """
2272    Interface method for selecting time and/or trace by user interaction. While
2273    UI remains responsive, everything waits for user input.
2274
2275    mode is one of the following commands:
2276    - relative (screen coordinates to relative offset) - for selecting windows
2277    - trace_time (trace under cursor and timestamp) - for picking
2278    """
2279    global plotter
2280    wx.CallAfter(plotter.canvas._set_modes, "interactive", mode)
2281    plotter._user_selection = None
2282    while plotter._user_selection is None:
2283        time.sleep(TIME_SLOT)
2284    return plotter._user_selection
2285subscribe_ui_event(__select_gui, "input")
2286
2287
2288@ui_events
2289def __screenshot(fname):
2290    """
2291    Trigger screen shot creation. Thread-safe. Image will be saved immediately.
2292    """
2293    global plotter
2294    plotter.canvas.do_screenshot = fname
2295    plotter.canvas.refresh = True
2296    wx.CallAfter(plotter.canvas.OnIdle, None)
2297subscribe_ui_event(__screenshot, "screenshot")
2298
2299
2300@ui_events
2301def __updateparams(unused):
2302    """
2303    Trigger redraw of parameter dialog.
2304    """
2305    global plotter
2306    if plotter and plotter.pardialog != None:
2307        wx.CallAfter(plotter.pardialog.updateVariables, None)
2308subscribe_ui_event(__updateparams, "updateparams")
2309
2310@ui_events
2311def __openparams(unused):
2312    """
2313    Open parameter dialog box.
2314    """
2315    global plotter
2316    if plotter and plotter.pardialog == None:
2317        wx.CallAfter(plotter.OnOpenParams, None)
2318subscribe_ui_event(__openparams, "openparams")
2319
2320@ui_events
2321def __openmagnify(unused):
2322    """
2323    Open magnify window.
2324    """
2325    global plotter
2326    if plotter and plotter.magnify == None:
2327        wx.CallAfter(plotter.openMagnifyWindow, None)
2328subscribe_ui_event(__openmagnify, "openmagnify")
2329
Note: See TracBrowser for help on using the repository browser.