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

Revision 1164, 121.4 KB checked in by klaus, 4 years ago (diff)

bugfixes in time window computations, zoomwindow

  • 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, set_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,\
20    EventTextParser
21from SeismicHandler.basics.analysispar import PhaseList, AnalysisPar
22from obspy.core import read, UTCDateTime
23from obspy.sh.core import fromUTCDateTime, toUTCDateTime
24
25# width of station info
26STATION_INFO = 85
27# margins (top, right, bottom, left)
28MARGINS = [10, 10, 10, 10]
29# space for time-scale
30TIMESCALE = 40
31# offset for time string
32TIMESCALE_OFFSET_TEXT = 8
33# length of minor/major ticks
34TIMESCALE_TICKS_MINOR = 3
35TIMESCALE_TICKS_MAJOR = 7
36
37# regular checking of UI data
38TIME_SLOT = .03
39
40shxcolor1 = '#ddeeff'
41shxcolor2 = '#bccdff'
42
43# holds window instance
44plotter = None
45
46# status information
47statusinfotext = ""
48
49displayselect = None
50
51class magnifyCanvas(wx.Panel):
52    """
53    A drawing canvas for the magnification trace. It has methods similar to
54    the traceCanvas class, but the methods are simpler due the fact that there
55    is only one trace displayed. On mouse operations there is no need for the
56    y (vertical) coordinate to be evaluated. Subclassing therefore is difficult
57    or the code must be reorganized. It is not advisable to run this in a
58    separate thread, the message communication could be too slow for immediate
59    reactions to mouse input.
60    """
61    def __init__( self, parent ):
62        "magnifyCanvas: init method."
63        wx.Panel.__init__( self, parent,
64            style=wx.BORDER_SIMPLE|wx.NO_FULL_REPAINT_ON_RESIZE|wx.WANTS_CHARS)
65        self.Bind(wx.EVT_PAINT, self.OnPaint)
66        self.Bind(wx.EVT_SIZE, self.OnSize)
67        self.Bind(wx.EVT_MOTION, self.OnMouseMotion)
68        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
69        self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
70        self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
71        self.Bind(wx.EVT_RIGHT_UP, self.OnMouseRightUp)
72        self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseLeftDouble)
73        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
74        #self.Bind(wx.EVT_KEY_UP, self.OnKeyPress )
75        self.Bind(wx.EVT_CHAR, self.OnKeyPress )
76        self.SetFocus()
77        self._magn_bitmap = None
78        self.trc = None
79        self.pixwidth = None
80        self.pixheight = None
81        self.pixel_duration = None
82        self._xor_line = None
83        self._last_defined_phase = None
84        self._mousedouble = None
85        self._dragstarttime = None
86        self._s3_showalltraces = False
87        self._s3_origtrcname = None
88        self._s3_othertraces = []
89        self.SetBackgroundColour( "white" )
90        self.clearWindow()
91   
92    def OnPaint( self, e ):
93        "magnifyCanvas: redraw event."
94        if self._magn_bitmap:
95            dc = wx.BufferedPaintDC(self, self._magn_bitmap)
96            #self._drawPicks()
97   
98    def OnSize( self, e ):
99        "magnifyCanvas: resize event."
100        self.pixwidth, self.pixheight = self.GetVirtualSize()
101        self.refresh()
102   
103    def OnMouseMotion(self, evt):
104        "magnifyCanvas: mouse motion event."
105        if not self.trc:
106            return
107        x, y = evt.GetPositionTuple()
108        abstime, reltime = self._fromScreenCoo( x,  mode='both' )
109        # draw XOR marker
110        if evt.LeftIsDown():
111            self._drawXorLine( x )
112        # Put text in status line of plotter window
113        stext = "%s    %g    %s    Z:%3.1f" % (fromUTCDateTime(abstime),reltime,
114            self.trc.id,(self.trc.stats.endtime-self.trc.stats.starttime))
115        plotter._setStatusText( stext )
116        if evt.RightIsDown() and self._dragstarttime:
117            difftime = self._dragstarttime - abstime
118            plotter.canvas.moveZoomWindow( 'right', span=difftime )
119            #self._dragstarttime -= 0.05*difftime
120        # let the event propagate
121        evt.Skip()
122
123    def OnMouseLeftDown(self, evt):
124        "magnifyCanvas: Left moune button pressed down."
125        x, y = evt.GetPositionTuple()
126        abstime = self._fromScreenCoo( x,  mode='abs' )
127        if self.trc:
128            cphase = self._closePhase( abstime )
129            if cphase:
130                station = "%s.%s" % (self.trc.stats.network,
131                    self.trc.stats.station)
132                station = station.upper()
133                _sendShCommand(
134                    "@PHASE CLEAR %s %s %s" % (cphase[0],station,cphase[1]) \
135                    +"\n@PHASE DEFAULT_PHASE %s" % cphase[0]
136                )
137                self._last_defined_phase = cphase[0]
138                #self._drawSinglePick( trace, tracetime,color='white' )
139                self.refresh()
140        evt.Skip()
141   
142    def OnMouseRightDown( self, evt ):
143        "magnifyCanvas: right mouse button pressed."
144        x, y = evt.GetPositionTuple()
145        self._dragstarttime = self._fromScreenCoo( x,  mode='abs' )
146        evt.Skip()
147
148    def OnMouseRightUp( self, evt ):
149        "magnifyCanvas: right mouse button released."
150        self._dragstarttime = None
151        evt.Skip()
152
153    def OnMouseLeftUp(self, evt):
154        "magnifyCanvas: left mouse button released."
155        if not self.trc:
156            return
157        self._drawXorLine( None )  # clear possibly existing XOR line.
158        x, y = evt.GetPositionTuple()
159        abstime, reltime = self._fromScreenCoo( x,  mode='both' )
160        station = "%s.%s" % (self.trc.stats.network,self.trc.stats.station)
161
162        # Send SH command to define/clear phase if not waiting for user input.
163        # Redraw of main window only if less than 50 traces.
164        if displayselect or len(Traces) < 50:
165            qredraw = '/REDRAW'
166        else:
167            qredraw = ''
168        if self._mousedouble:
169            if self._last_defined_phase:
170                _sendShCommand( "@PHASE CLEAR %s %s manual %s" % (
171                    self._last_defined_phase,station.upper(),qredraw) )
172        else:
173            _sendShCommand( "@PHASE DEFINE %s %s ;;; %s %s" % (station.upper(),
174                fromUTCDateTime(abstime),self.trc.stats.channel[-1].upper(),
175                qredraw), name="mouse evt" )
176        self.refresh()
177        self._mousedouble = False
178        evt.Skip()
179   
180    def OnMouseLeftDouble( self, evt ):
181        "magnifyCanvas: left double click."
182        self._mousedouble = True
183
184    def OnMouseWheel(self, evt):
185        "magnifyCanvas: mouse wheel event."
186        wheel = evt.GetWheelRotation()
187        if wheel > 0:
188            plotter.canvas.moveZoomWindow( 'up' )
189        elif wheel < 0:
190            plotter.canvas.moveZoomWindow( 'down' )
191        evt.Skip()
192   
193    def OnKeyPress( self, evt ):
194        "magnifyCanvas: key event: emulate all shortcuts of main window."
195        localtrans = {
196            314 : '<-',
197            315 : '-^',
198            316 : '->',
199            317 : '-v',
200        }
201        try:
202            char = chr( evt.GetUniChar() ).lower()
203        except:
204            try:
205                char = localtrans[evt.GetUniChar()]
206            except:
207                print "dbg: unichar not recognized", repr(evt.GetUniChar())
208                return
209        shortcut = {
210            'a': plotter.OnSimulateWoodAnderson,
211            'b': plotter.OnBeam,
212            'd': plotter.OnDelTimeWindow,
213            'g': plotter.OnSetPhase_Pg,
214            'l': plotter.OnLocateEvent,
215            'm': plotter.OnMagnMlAnalyticSingle,
216            'n': plotter.OnSetPhase_Pn,
217            'p': plotter.OnSetPhase_P,
218            's': plotter.OnSetTimeWindow,
219            'u': plotter.OnSimulateUndo,
220            'w': plotter.OnPlaneWave,
221            '>': plotter.OnMoveZoomRight,
222            '<': plotter.OnMoveZoomLeft,
223            '+': plotter.OnAmplZoomUp,
224            '-': plotter.OnAmplZoomDown,
225            '0': plotter.OnPrevEvent,
226            '1': plotter.OnNextEvent,
227            '2': plotter.OnSetPhase_Sg,
228            '3': plotter.OnShowAllInMagnify,
229            '8': plotter.OnFilterBP_1_8,
230            '-^': self.keyZoomUp,
231            '-v': self.keyZoomDown,
232            '->': self.keyZoomGrow,
233            '<-': self.keyZoomShrink,
234        }
235        shortcut_control = {
236            'd': plotter.OnDemean,
237            'f': plotter.OnFKAutoFreq,
238            'n': plotter.OnNorm,
239            'o': plotter.OnOverlappingTraces,
240            'q': plotter.OnQuit,
241            's': plotter.OnSaveTracesAndParamsQuick,
242            'w': plotter.OnSimulateWWSSNSP,
243            'l': plotter.OnSimulateSROLP,
244            'y': plotter.OnSortByDistance,
245        }
246        if evt.ControlDown():
247            if char in shortcut_control.keys():
248                shortcut_control[char](None)
249        else:
250            if char in shortcut.keys():
251                shortcut[char](None)
252   
253    def keyZoomGrow( self, e ):
254        plotter.canvas.moveZoomWindow( 'grow' )
255    def keyZoomShrink( self, e ):
256        plotter.canvas.moveZoomWindow( 'shrink' )
257    def keyZoomUp( self, e ):
258        plotter.canvas.moveZoomWindow( 'up' )
259    def keyZoomDown( self, e ):
260        plotter.canvas.moveZoomWindow( 'down' )
261
262    def refresh( self ):
263        "magnifyCanvas: redraw content of window."
264        self._drawMagnifyTrace()
265        self._drawPicks()
266
267    def _closePhase( self, abstime ):
268        "magnifyCanvas: return name and type of closest phase or None."
269        if not self.trc:
270            return None
271        phaselist = PhaseList()
272        if not self.pixel_duration:
273            return None
274        toltime = 3*self.pixel_duration
275        station = "%s.%s" % (self.trc.stats.network,self.trc.stats.station)
276        for phase in phaselist.getPhaseList(station):
277            tdiff = abs( phase.picktime-abstime )
278            if tdiff < toltime:
279                return (phase.name,phaselist.picktypeName(phase.picktype))
280        return None
281   
282    def _drawMagnifyTrace( self, clear=True, othertrace=None, yoffset=0.,
283        color=(0,0,0), norm=None ):
284        "magnifyCanvas: does the drawing work."
285        # Bitmap holding the final figure, canvas->DC drawing to this bitmap.
286        if self.trc == None:
287            self.clearWindow()
288            return
289        if norm == None:
290            try:
291                norm = max( abs(max(self.trc.data)), abs(min(self.trc.data)) )
292            except:
293                self.clearWindow()
294                return
295        if othertrace == None:
296            drawtrc = self.trc
297        else:
298            drawtrc = othertrace
299        amplscale = 1./norm * self.pixheight/2.
300        self._magn_bitmap = wx.EmptyBitmap(self.pixwidth, self.pixheight)
301        mdc = wx.MemoryDC(self._magn_bitmap)
302        mdc.SetBrush(wx.TRANSPARENT_BRUSH)
303        if clear:
304            mdc.Clear()
305        mdc.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL))
306        mdc.SetPen(wx.Pen(color, 1))
307        mdc.BeginDrawing()
308        mdc.DrawLines(
309            zip(
310                np.linspace( 0., self.pixwidth, len(drawtrc.data) ),
311                drawtrc.data*amplscale + self.pixheight/2 + yoffset
312            )
313        )
314        # station label and amplitudes
315        mindat = self.trc.data.min()
316        maxdat = self.trc.data.max()
317        if abs(mindat) > 10 or abs(maxdat) > 10:
318            valtext = "%3.1f,%3.1f" % (mindat,maxdat)
319        else:
320            valtext = "%g,%g" % (mindat,maxdat)
321        labtext = "%s (%s)" % (self.trc.id,valtext)
322        mdc.DrawText( labtext, 4, 4 ) # top left corner addressed
323        mdc.EndDrawing()
324        del mdc
325        self.Refresh()
326        self.Update()
327   
328    def _drawPicks( self ):
329        "magnifyCanvas: add picks to waveform."
330        if self.trc == None:
331            return
332        phaselist = PhaseList()
333        _picks = {}
334        sname = "%s.%s" % (self.trc.stats.network,self.trc.stats.station)
335        for phase in phaselist.getPhaseList(sname):
336            if phase.comp and self.trc.stats.channel[-1] != phase.comp:
337                continue
338            pcol = phaselist.picktypeColor( phase.picktype )
339            if pcol not in _picks.keys():
340                _picks[pcol] = []
341            _picks[pcol].append(
342                ( phase.name, self._toScreenCoo(phase.picktime) )
343            )
344        dc = wx.MemoryDC(self._magn_bitmap)  # mdc
345        #dc = wx.ClientDC(self)
346        dc.SetBrush(wx.TRANSPARENT_BRUSH)
347        dc.SetFont( wx.Font( 8, wx.DEFAULT, wx.NORMAL, wx.NORMAL ) )
348        pickborder = 10
349        yup = self.pixheight - pickborder
350        ydn = pickborder
351        # draw picks
352        for pcolor in _picks.keys():
353            dc.SetPen(wx.Pen(pcolor, 1))
354            dc.SetTextForeground(pcolor)
355            for pick in _picks[pcolor]:
356                pname, xcoo = pick
357                dc.DrawLine( xcoo, yup, xcoo, ydn )
358                if pname != '_Mark_':
359                    dc.DrawText(pname, xcoo + 2, ydn - 10)
360        del dc          # mdc
361        self.Refresh()  # mdc
362        self.Update()   # mdc
363   
364    def _drawXorLine( self, xcoo=None ):
365        """
366        magnifyCanvas:
367        Draws line in XOR mode. Deletes previous line. To only clear old
368        element, pass None.
369        Should also draw into backing store bitmap (self._bitmap). Now,
370        a repaint doesn't restore the XOR drawing, resulting in a doubled
371        element.
372        """
373        color = 'orange'
374        size = 1
375        pickborder = 10
376        yup = self.pixheight - pickborder
377        ydn = pickborder
378        if xcoo == None:
379            coo = (None,None,None,None)
380        else:
381            coo = (xcoo,ydn,xcoo,yup)
382        dc = wx.ClientDC(self)
383        dc.SetLogicalFunction( wx.XOR )
384        dc.SetPen( wx.Pen(color,size,wx.SOLID) )
385        dc.SetBrush( wx.Brush(color,wx.TRANSPARENT) )
386        if self._xor_line != None:
387            # Clear old line.
388            dc.DrawLine( *self._xor_line )
389        if coo[0] == None:
390            self._xor_line = None
391        else:
392            # Draw new rectangle.
393            dc.DrawLine( *coo )
394            self._xor_line = coo
395
396    def magnifyTrace( self, trc, boxwidth=None, boxcenter=None, boxend=None ):
397        "magnifyCanvas: top level redraw routine for magnify content."
398        if trc == None:
399            self.clearWindow()
400            return
401        if self._s3_showalltraces:
402            s3_recent = self._s3_origtrcname
403            self._s3_origtrcname = "%s.%s.%s.%s" % (trc.stats.network,
404                trc.stats.station,trc.stats.location,trc.stats.channel)
405            if s3_recent != self._s3_origtrcname:
406                self._s3_othertraces = self._s3_findOtherTraces()
407        self.pixwidth, self.pixheight = self.GetVirtualSize()
408        self.pixel_duration = self._fromScreenCoo( 1, mode='rel' )
409        if boxcenter != None:
410            if not boxwidth:
411                return
412            w2 = boxwidth/2
413            ta = boxcenter - w2
414            tb = boxcenter + w2
415            self.trc = trc.slice( ta, tb )
416        elif boxend != None:
417            if not boxwidth:
418                return
419            ta = boxend - boxwidth
420            if ta > boxend:
421                self.trc = trc.slice( boxend, ta )
422            else:
423                self.trc = trc.slice( ta, boxend )
424        else:
425            self.trc = trc
426        clearwdw = True
427        if self._s3_showalltraces and len(self._s3_othertraces) > 0:
428            try:
429                norm = max( abs(max(self.trc.data)), abs(min(self.trc.data)) )
430            except:
431                return
432            self._s3_showOtherTraces( norm )
433            clearwdw = False
434        self._drawMagnifyTrace( clearwdw )
435        self._drawPicks()
436   
437    def clearWindow( self ):
438        "magnifyCanvas: clear window content."
439        dc = wx.AutoBufferedPaintDCFactory(self)
440        dc.Clear()
441        return
442   
443    def _toScreenCoo( self, abstime ):
444        "magnifyCanvas: helper for coo transformation."
445        if not self.trc or not self.pixwidth:
446            return 0.
447        tdiff = self.trc.stats.endtime-self.trc.stats.starttime
448        if tdiff == 0.:
449            return 0.
450        return ((abstime-self.trc.stats.starttime) / tdiff * self.pixwidth)
451   
452    def _fromScreenCoo( self, xpix, mode='abs' ):
453        "magnifyCanvas: helper for coo transformation."
454        if not self.trc or not self.pixwidth:
455            return 0.
456        reltime = (float(xpix)/float(self.pixwidth)) \
457            * (self.trc.stats.endtime-self.trc.stats.starttime)
458        if mode == 'abs':
459            return self.trc.stats.starttime + reltime
460        elif mode == 'rel':
461            return reltime
462        else:
463            return (self.trc.stats.starttime + reltime,reltime)
464   
465    def _s3_findOtherTraces( self ):
466        "magnifyCanvas: find other components of main trace."
467        tlist = []
468        for trc in traces_from_list('all'):
469            sname = "%s.%s.%s.%s" % (trc.stats.network,
470                trc.stats.station,trc.stats.location,trc.stats.channel)
471            if sname == self._s3_origtrcname:
472                # not the same trace again
473                continue
474            # take away last char (component)
475            tname = self._s3_origtrcname[:-1]
476            if sname.startswith(tname):
477                tlist.append( trc )
478        return tlist
479   
480    def _s3_showOtherTraces( self, norm ):
481        """
482        magnifyCanvas:
483        show other components of main traces using plot method
484        _drawMagnifyTrace.
485        """
486        yoffset = -self.pixheight/5
487        ystep = 2*self.pixheight/5
488        clearwdw = True
489        for trc in self._s3_othertraces:
490            otrc = trc.slice(self.trc.stats.starttime,self.trc.stats.endtime)
491            if len(otrc) < 2:
492                print "dbg: s3: short other trace"
493                continue
494            if abs(otrc.stats.starttime-self.trc.stats.starttime) \
495                > self.trc.stats.delta:
496                print "dbg: s3: other trace stange start time"
497                continue
498            self._drawMagnifyTrace( clearwdw, otrc, yoffset,
499                color=(200,200,200), norm=norm )
500            yoffset += ystep
501            clearwdw = False
502   
503    def toggleShowAllTraces( self ):
504        "magnifyCanvas: toggle show-all-traces flag. Called from menu."
505        if self._s3_showalltraces:
506            self._s3_showalltraces = False
507            self._s3_origtrcname = None
508            self._s3_othertraces = []
509        else:
510            self._s3_showalltraces = True
511       
512
513class traceCanvas(SP.ScrolledPanel):
514    """
515    All drawing methods for plotting traces. Include all mouse-related
516    functions.
517    """
518    #@timeit
519    def __init__(self, parent, fnames=[]):
520        "Trace canvas init method."
521        SP.ScrolledPanel.__init__(self, parent,
522            style=wx.BORDER_SIMPLE|wx.NO_FULL_REPAINT_ON_RESIZE)
523
524        # class data
525        self._bitmap = None
526        self.parent = parent
527        self.traces = Traces
528        self.ZoomWindow = None
529        self.zoomwdwwidth = 0
530        self.Scrolled = 0
531        self.mousedouble = 0
532        self.refresh = False
533        self.AllowDoubleClick = True
534        self.do_screenshot = False
535        self.pixel_duration = None
536        self.last_defined_phase = None
537        self.wheelpos = 0
538        self.timewindow = (None,None)
539        self.last_number_of_traces = 0
540        self._xor_line = None
541        self._xor_rect = None
542
543        # temporary layer for mouse motion
544        self.overlay_drag = wx.Overlay()
545        # layer for picks and zoom box
546        self.overlay_picks = wx.Overlay()
547
548        # application defaults
549        self.relativeAxis = True  # False not supported
550        self.traceOrder = 0
551        self.phasename = "P"   # unused
552        self.space = None
553        self.interactive = "trace_time"
554
555        self.ppi = wx.ScreenDC().GetPPI()
556
557        self._setup()
558
559        def disable_event(*pargs,**kwargs):
560            "traceCanvas: from some website."
561            pass # the sauce, please
562
563        # event binding
564        self.Bind(wx.EVT_PAINT, self.OnPaint)
565        self.Bind(wx.EVT_SIZE, self.OnSize)
566        self.Bind(wx.EVT_IDLE, self.OnIdle)
567        self.Bind(wx.EVT_MOTION, self.OnMouseMotion)
568        self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
569        self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseLeftDouble)
570        self.Bind(wx.EVT_RIGHT_DCLICK, self.OnMouseRightDouble)
571        self.Bind(wx.EVT_RIGHT_UP, self.OnMouseRightUp)
572        self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
573        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
574        #self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
575        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
576        #self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyDown)
577        self.Bind(wx.EVT_ERASE_BACKGROUND, disable_event)
578
579        # messaging system
580#        msgs.subscribe(self.OnNotifyHeight, 'GUT.displayheight')
581#        msgs.subscribe(self.OnNotifyPhaseName, 'GUI.phasename')
582#        msgs.subscribe(self.OnNotifyTraceOrder, 'GUI.traceorder')
583#        msgs.subscribe(self.OnNotifyTimeAxis, 'GUT.timeaxis')
584        msgs.subscribe(self.__runtime_changer, "setruntime")
585
586        # init
587        self.OnSize(None)
588        self.SetFocus()
589
590    def __runtime_changer(self, name, value):
591        """
592        traceCanvas:
593        Helper method for acting on certain runtime events. Called via message
594        system.
595        """
596        if name != "RUNTIME:styles":
597            return
598        margins = value.get("MARGINS", None)
599
600        if not margins:
601            return
602
603        # convert inch values to pixels
604        margins = [
605            margins[0] * self.ppi[1],
606            margins[1] * self.ppi[0],
607            margins[2] * self.ppi[1],
608            margins[3] * self.ppi[0]
609        ]
610
611        global MARGINS
612        if MARGINS != margins:
613            MARGINS = margins
614   
615    def closePhase( self, trace, abstime ):
616        "traceCanvas: Return name and type of closest phase or None."
617        phaselist = PhaseList()
618        if not self.pixel_duration:
619            return None
620        toltime = 2*self.pixel_duration
621        station = "%s.%s" % (trace.stats.network,trace.stats.station)
622        for phase in phaselist.getPhaseList(station):
623            tdiff = abs( phase.picktime-abstime )
624            if tdiff < toltime:
625                return (phase.name,phaselist.picktypeName(phase.picktype))
626        return None
627   
628    def _drawXor( self, mode, coo=(None,None,None,None), dc=None ):
629        """
630        traceCanvas:
631        Draws line (len(coo)==2) or rectangle (len(coo) == 4) in XOR mode.
632        Deletes previous line/rectangle. To only clear old element, pass
633        None-tuples.
634        Should also draw into backing store bitmap (self._bitmap). Now,
635        a repaint doesn't restore the XOR drawing, resulting in a doubled
636        element.
637        """
638        if mode == 'clear':
639            self._xor_line = None
640            self._xor_rect = None
641            return
642        color = 'orange'
643        # Line or rectangle?
644        islinemode = (mode == 'line')
645        # Setup dc.
646        if islinemode:
647            size = 1
648        else:
649            size = 2
650        if dc == None:
651            dc = wx.ClientDC(self)
652        dc.SetLogicalFunction( wx.XOR )
653        dc.SetPen( wx.Pen(color,size,wx.SOLID) )
654        dc.SetBrush( wx.Brush(color,wx.TRANSPARENT) )
655        #dc.SetBrush( wx.Brush(color,wx.SOLID) )
656        if islinemode:
657            if self._xor_line != None:
658                # Clear old rectangle.
659                dc.DrawLine( *self._xor_line )
660            if coo[0] == None:
661                self._xor_line = None
662            else:
663                # Draw new rectangle.
664                dc.DrawLine( *coo )
665                self._xor_line = coo
666        else:
667            if self._xor_rect != None:
668                # Clear old rectangle.
669                dc.DrawRectangle( *self._xor_rect )
670            if coo[0] == None:
671                self._xor_rect = None
672            else:
673                # Draw new rectangle.
674                dc.DrawRectangle( *coo )
675                self._xor_rect = coo
676
677    # event processing
678    def OnMouseWheel(self, evt):
679        "traceCanvas: mouse wheel callback."
680        maxwheelpos = 10
681        wheel = evt.GetWheelRotation()
682        if displayselect:
683            if wheel > 0:
684                _scrollTraces( 'up' )
685            elif wheel < 0:
686                _scrollTraces( 'down' )
687        else:
688            if wheel > 0:
689                #self.moveZoomWindow( 'up' )
690                self.moveZoomWindow( 'grow' )
691            elif wheel < 0:
692                #self.moveZoomWindow( 'down' )
693                self.moveZoomWindow( 'shrink' )
694        #else:
695        #    if wheel > 0:
696        #        if self.wheelpos < maxwheelpos:
697        #            _sendShCommand( "zoom/rel all 2" )
698        #            self.wheelpos += 1
699        #    elif wheel < 0:
700        #        if self.wheelpos > -maxwheelpos:
701        #            _sendShCommand( "zoom/rel all 0.5" )           
702        #            self.wheelpos -= 1
703        evt.Skip()
704
705    def OnKeyDown(self, evt):
706        "traceCanvas: key events not used, just menu shortcuts."
707        print "key"
708        kc = evt.GetKeyCode()
709        print kc
710        evt.Skip()
711#        if kc == ord("r"):
712
713    def OnMouseLeftDouble(self, evt):
714        "traceCanvas: left double click."
715        trace, tracetime = self.ScreenToTraceAndTime(*(evt.GetPositionTuple()))
716        if trace:
717            self.mousedouble = True
718        #    print "dbg: closePhase", self.closePhase(trace,tracetime)
719        #    cphase = self.closePhase( trace, tracetime )
720        #    if cphase:
721        #        station = "%s.%s" % (trace.stats.network,trace.stats.station)
722        #        _sendShCommand(
723        #            "phase clear %s %s %s" % (cphase[0],station,cphase[1])
724        #        )
725        evt.Skip()
726
727    def OnMouseRightDouble(self, evt):
728        "traceCanvas: right double click."
729        self.ZoomWindow = None
730        if plotter:
731            plotter.setZoomWindow( None, None, None )
732        self.zoomwdwwidth = 0.
733        self.__zoombox( None, None, None )
734        evt.Skip()
735
736    def OnMouseLeftDown(self, evt):
737        "traceCanvas: press left mouse button."
738        self._captureMouse(evt)
739        trace, tracetime = self.ScreenToTraceAndTime(*(evt.GetPositionTuple()))
740        if trace:
741            #self.mousedouble = True
742            cphase = self.closePhase( trace, tracetime )
743            if cphase:
744                station = "%s.%s" % (trace.stats.network,trace.stats.station)
745                station = station.upper()
746                _sendShCommand(
747                    "@PHASE CLEAR %s %s %s" % (cphase[0],station,cphase[1]) \
748                    +"\n@PHASE DEFAULT_PHASE %s" % cphase[0]
749                )
750                self.last_defined_phase = cphase[0]
751                self._drawSinglePick( trace, tracetime,color='white' )
752                if plotter.magnify:
753                    plotter.magnify.canvas.refresh()
754            self._drawPicksM()
755        evt.Skip()
756
757    def OnMouseRightDown(self, evt):
758        "traceCanvas: press right mouse button callback."
759        self._captureMouse(evt)
760        if self.zoomwdwwidth:
761            pass
762        elif self.ZoomWindow:
763            trace, start, end = self.ZoomWindow
764            start = self.TraceAndTimeToScreen(trace, start)
765            end = self.TraceAndTimeToScreen(trace, end)
766            if start[0] == None or end[0] == None:
767                self.ZoomWindow = None
768                return
769            self.zoomwdwwidth = end[0] - start[0]
770        else:
771            self.zoomwdwwidth = 0
772        # draw zoom box
773        if self.zoomwdwwidth > 0:
774            x, y = evt.GetPositionTuple()
775            trace, start = \
776                self.ScreenToTraceAndTime(x-self.zoomwdwwidth/2, y)
777            trace, end = \
778                self.ScreenToTraceAndTime(x+self.zoomwdwwidth/2, y)
779            self.ZoomWindow = [trace, start, end]
780            self._drawZoomWindow()
781       
782
783    def OnMouseMotion(self, evt):
784        """
785        traceCanvas: Mouse motion.
786        """
787
788        if not len(self.traces):
789            return
790
791        x, y = evt.GetPositionTuple()
792        trace, timestamp = self.ScreenToTraceAndTime(x, y)
793        if timestamp == None:
794            return
795
796        try:
797            tid = trace.id
798        except AttributeError:
799            tid = "*"
800       
801        reltimestr = "%g" % self.ScreenToRelativeTime( x, y )
802
803        dragwidth = None
804        if evt.Dragging() and trace:
805            th2 = self.traceheight / 2
806            shx_graphics = trace.get_graphics()
807            if shx_graphics == None:
808                print "dbg: returned from callback, no shx_graphics"
809                return
810            mpth2 = shx_graphics.midpoint + th2
811            mmth2 = shx_graphics.midpoint - th2
812
813            # draw box
814            if evt.RightIsDown():
815                self.ZoomWindow = None
816                if plotter:
817                    plotter.setZoomWindow( None, None, None )
818                if self.zoomwdwwidth == 0.:
819                    self.__zoombox( self.dragStart, (x, y),
820                        shx_graphics.midpoint, size=2 )
821                    dragwidth = x - self.dragStart[0]
822                    # data for magnify trace
823                    if plotter and plotter.magnify:
824                        plotter.magnify.canvas.magnifyTrace( trace,
825                            boxend=timestamp,
826                            boxwidth=dragwidth*self.pixel_duration )
827                else:
828                    _start = [x-self.zoomwdwwidth/2,y]
829                    _end = [x+self.zoomwdwwidth/2,y]
830                    self.__zoombox( _start, _end,
831                        shx_graphics.midpoint, size=2 )
832                    dragwidth = _end[0] - _start[0]
833                    if plotter and plotter.magnify:
834                        plotter.magnify.canvas.magnifyTrace( trace,
835                            boxcenter=timestamp,
836                            boxwidth=dragwidth*self.pixel_duration )
837            # draw position line
838            elif evt.LeftIsDown():
839                if self.traceOrder in [0, 1]:
840                    self._drawXor( 'line', (
841                        x, mmth2 - self.Scrolled,
842                        x, mpth2 - self.Scrolled
843                    ) )
844                elif self.traceOrder in [2, 3]:
845                    self._drawXor( 'line', (
846                        mmth2 - self.Scrolled, y,
847                        mpth2 - self.Scrolled, y
848                    ) )
849
850        stext = "%s    %s    %s" % (fromUTCDateTime(timestamp), reltimestr, tid)
851        if self.ZoomWindow:
852            stext += "    Z:%3.1f" % (self.ZoomWindow[2]-self.ZoomWindow[1])
853        elif dragwidth and self.pixel_duration:
854            stext += "    Z:%3.1f" % (dragwidth*self.pixel_duration)
855        self.parent._setStatusText( stext )
856
857        # let the event propagate
858        evt.Skip()
859
860    def OnMouseRightUp(self, evt):
861        """
862        traceCanvas: Release of right mouse button.
863        """
864        if self.HasCapture():
865            self._releaseMouse(evt)
866            x, y = evt.GetPositionTuple()
867
868            if self.zoomwdwwidth == 0:
869                trace, end = self.ScreenToTraceAndTime(x, y)
870                if end == None:
871                    return
872                if self.traceOrder in [0, 1]:
873                    _, start = self.ScreenToTraceAndTime(self.dragStart[0], y)
874                elif self.traceOrder in [2, 3]:
875                    _, start = self.ScreenToTraceAndTime(x, self.dragStart[1])
876            else:
877                if self.traceOrder in [0, 1]:
878                    trace, start = \
879                        self.ScreenToTraceAndTime(x-self.zoomwdwwidth/2, y)
880                    trace, end = \
881                        self.ScreenToTraceAndTime(x+self.zoomwdwwidth/2, y)
882                elif self.traceOrder in [2, 3]:
883                    trace, start = \
884                        self.ScreenToTraceAndTime(x, y-self.zoomwdwwidth/2)
885                    trace, end = \
886                        self.ScreenToTraceAndTime(x, y+self.zoomwdwwidth/2)
887
888            if start > end:
889                end, start = start, end
890
891            self.ZoomWindow = [trace, start, end]
892            self.dragStart = None
893            self._drawZoomWindow()
894            if plotter and trace:
895                # store relative times for possible menu call to stw.
896                srel = start - trace.stats.starttime \
897                    + trace.get_info('t-origin')
898                erel = end - trace.stats.starttime + trace.get_info('t-origin')
899                plotter.setZoomWindow( trace.index(True), srel, erel )
900                # content of magnify window
901                if plotter.magnify:
902                    plotter.magnify.canvas.magnifyTrace(trace.slice(start,end))
903            elif plotter.magnify:
904                plotter.magnify.canvas.magnifyTrace( None )  # clear
905
906    def OnMouseLeftUp(self, evt):
907        """
908        traceCanvas: Release of left mouse button.
909        """
910        if self.HasCapture():
911            self._releaseMouse(evt)
912            self.dragStart = None
913           
914        #print "dbg: double buffered", self.IsDoubleBuffered()
915
916        #if self.mousedouble:
917        #    self.mousedouble = False
918
919        x, y = evt.GetPositionTuple()
920        trace, tracetime = self.ScreenToTraceAndTime(x, y)
921        if tracetime == None:
922            return
923
924        # Send SH command to define/clear phase if not waiting for user input.
925        # Redraw of main window only if less than 50 traces on display.
926        if displayselect or len(Traces) < 50:
927            qredraw = "/REDRAW"
928        else:
929            qredraw = ""
930        if self.mousedouble:
931            if self.last_defined_phase:
932                station = "%s.%s" % (trace.stats.network,trace.stats.station)
933                _sendShCommand( "@PHASE CLEAR %s %s manual %s" % (
934                    self.last_defined_phase,station.upper(),qredraw) )
935        elif trace and self.parent._user_selection:
936            station = "%s.%s" % (trace.stats.network,trace.stats.station)
937            _sendShCommand( "@PHASE DEFINE %s %s ;;; %s %s" % (station.upper(),
938                fromUTCDateTime(tracetime),trace.stats.channel[-1].upper(),
939                qredraw), name="mouse evt" )
940            self._drawSinglePick( trace, tracetime )
941            if plotter.magnify:
942                plotter.magnify.canvas.refresh()
943        self.mousedouble = False
944
945        if self.interactive == "trace_time":
946            self.parent._user_selection = (trace, tracetime)
947        elif self.interactive == "relative":
948            self.parent._user_selection = self.ScreenToRelativeTime(x, y)
949        else:
950            raise NameError("unknown selection mode")
951       
952        evt.Skip()
953
954    def OnScroll(self, evt):
955        """
956        traceCanvas: Update scroll offset. Event disabled.
957        """
958        print "dbg: on scroll"
959        if self.traceOrder in [0, 1]:
960            self.Scrolled = self.GetScrollPos(wx.VERTICAL) * \
961                                               self.GetScrollPixelsPerUnit()[1]
962        elif self.traceOrder in [2, 3]:
963            self.Scrolled = self.GetScrollPos(wx.HORIZONTAL) * \
964                                               self.GetScrollPixelsPerUnit()[0]
965        self.OnSize(evt, renewOverlay=True)
966        evt.Skip()
967
968    def OnIdle(self, evt):
969        """
970        traceCanvas: idle processing. Redraw is done here.
971        """
972        if self.refresh:
973            self._timewindow = get_runtime("timewindow")
974            self._normtype = get_runtime("normtype")
975            wx.BeginBusyCursor( cursor=wx.StockCursor(_cursorNameToID("cross")) )
976            self._resetTimeRange()
977            self._drawTraces()
978            self._drawPicksM()
979            self._drawZoomWindow()
980            if plotter.magnify:
981                plotter.magnify.canvas.refresh()
982            wx.EndBusyCursor()
983            self.refresh = False
984
985    def _set_modes(self, name, mode):
986        """
987        traceCanvas:
988        Method for altering the selection mode. Intended to be called from
989        other threads using "CallAfter".
990        """
991        setattr(self, name, mode)
992
993    def OnSize(self, evt, renewOverlay=False):
994        """
995        traceCanvas: Called if redraw is requested.
996
997        Just set flag for redraw - execution triggered from IDLE event.
998        In case of resize renew overlay buffers.
999        """
1000        # if window was resized, renew overlay buffers
1001        if evt and (renewOverlay or evt.GetEventType() == wx.EVT_SIZE.typeId):
1002            self._setup()
1003            self.overlay_picks.Reset()
1004            self.overlay_drag.Reset()
1005
1006        # painting is only done in idle state
1007        self.refresh = True
1008   
1009    def OnPaint( self, evt ):
1010        "traceCnvas: redraw event."
1011        if self._bitmap:
1012            dc = wx.BufferedPaintDC(self, self._bitmap)
1013
1014    # Usually called via message system.
1015    def OnNotifyTimeAxis(self, orientation):
1016        """
1017        traceCanvas:
1018        Handle changes of time axis mode (absolute vs. relative time
1019        axis).
1020        """
1021        recent = self.relativeAxis
1022        self.relativeAxis = orientation == 1
1023        if recent != self.relativeAxis:
1024            # BAD!
1025            self.OnSize(True, renewOverlay=True)
1026
1027    def OnNotifyTraceOrder(self, order):
1028        """
1029        traceCanvas:
1030        Handle changes of trace plotting mode (horizontal vs. vertical
1031        modes).
1032        """
1033        recent = self.traceOrder
1034        self.traceOrder = order
1035        if recent != self.traceOrder:
1036            # reset display height
1037#            msgs.sendMessage('notify.tracecount', count=len(self.traces),
1038#                                                                    reset=True)
1039#            msgs.sendMessage('ui.change.displayheight',
1040#                                                       height=len(self.traces))
1041            self._setup()
1042            # BAD: setting evt=True
1043            self.OnSize(True, renewOverlay=True)
1044
1045    def OnNotifyHeight(self, height):
1046        """
1047        traceCanvas: handle changes of display height.
1048        """
1049        if self.traceOrder in [0, 1]:
1050            self.space = self.GetClientSize()[1]
1051        elif self.traceOrder in [2, 3]:
1052            self.space = self.GetClientSize()[0]
1053
1054        if height != len(self.traces):
1055            self.space *= len(self.traces) / float(height)
1056
1057        self._setup()
1058        self.OnSize(None)
1059
1060    def OnNotifyPhaseName(self, name):
1061        "traceCanvas: ?"
1062        print "dbg: *** is this used ? ***"
1063        self.phasename = name
1064
1065    # helper functions
1066    #@timeit
1067    def ScreenToRelativeTime(self, x, y):
1068        """
1069        traceCanvas: Returns relative time inside screen.
1070        """
1071        x, y = self.CalcUnscrolledPosition((x,y))
1072        # horizontal display
1073        if self.traceOrder in [0, 1]:
1074            timepos = x
1075        # vertical display
1076        elif self.traceOrder in [2, 3]:
1077            timepos = y
1078
1079        # horizontal
1080        if self.traceOrder in [0, 1]:
1081            pixel_width = self.width - STATION_INFO - MARGINS[1] - MARGINS[3]
1082        # vertical
1083        elif self.traceOrder in [2, 3]:
1084            pixel_width = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
1085
1086        # horizontal plotting
1087        if self.traceOrder in [0, 1]:
1088            timepos -= STATION_INFO + MARGINS[1]
1089        # vertical plotting
1090        elif self.traceOrder == 2:
1091            if self.relativeAxis:
1092                timepos -= length + MARGINS[0]
1093            else:
1094                timepos -= pixel_width + MARGINS[0]
1095            timepos *= -1
1096        elif self.traceOrder == 3:
1097            timepos -= STATION_INFO
1098
1099        if self.timewindow[1] is None:
1100            duration = self.relend - self.relstart
1101            start = self.relstart
1102        else:
1103            duration = self.timewindow[1] - self.timewindow[0]
1104            start = self.timewindow[0]
1105
1106        t = timepos * duration / pixel_width + start
1107
1108        return t
1109
1110    #@timeit
1111    def ScreenToTraceAndTime(self, x, y):
1112        """
1113        traceCanvas:
1114        Returns trace instance and time code derived from cursor
1115        position inside graphical panel.
1116        """
1117        x, y = self.CalcUnscrolledPosition((x,y))
1118        # horizontal display
1119        if self.traceOrder in [0, 1]:
1120            tracepos, timepos = y, x
1121        # vertical display
1122        elif self.traceOrder in [2, 3]:
1123            tracepos, timepos = x, y
1124
1125        # get trace under cursor
1126        trace = None
1127        theight = self.traceheight // 2
1128        for t in self.traces:
1129            shx_graphics = t.get_graphics()
1130            if shx_graphics == None:
1131                continue
1132            if 'midpoint' not in shx_graphics:
1133                continue
1134            if shx_graphics.midpoint == None:
1135                continue
1136            if tracepos >= shx_graphics.midpoint - theight and \
1137                tracepos <= shx_graphics.midpoint + theight:
1138                trace = t
1139                break
1140
1141        # get time code
1142        if trace and self.relativeAxis:
1143            start = trace.stats.starttime
1144            end = trace.stats.endtime
1145            shx_graphics = trace.get_graphics()
1146            if shx_graphics != None:
1147                try:
1148                    pixel_width = shx_graphics.PlotPixels
1149                except:
1150                    # during redraw PlotPixels may not be set yet
1151                    return (None,None)
1152            # only needed for vertical plotting
1153            length = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
1154        else:
1155            # global time axis
1156            start = self.start
1157            end = self.end
1158            # horizontal
1159            if self.traceOrder in [0, 1]:
1160                pixel_width = self.width - STATION_INFO - MARGINS[1] - MARGINS[3]
1161            # vertical
1162            elif self.traceOrder in [2, 3]:
1163                pixel_width = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
1164
1165        if self.timewindow[1] is None:
1166            duration = end - start
1167        else:
1168            duration = self.timewindow[1] - self.timewindow[0]
1169            start += self.timewindow[0]
1170
1171        # horizontal plotting
1172        if self.traceOrder in [0, 1]:
1173            timepos -= STATION_INFO + MARGINS[3]
1174        # vertical plotting
1175        elif self.traceOrder == 2:
1176            if self.relativeAxis:
1177                timepos -= length + MARGIN
1178            else:
1179                timepos -= pixel_width + MARGIN
1180            timepos *= -1
1181        elif self.traceOrder == 3:
1182            timepos -= STATION_INFO
1183
1184        t = timepos * duration / pixel_width
1185        timestamp = start + t
1186       
1187        # if trace is shifted, take t-origin into account
1188        if trace:
1189            timestamp -= trace.get_info('t-origin')
1190            if self.timewindow[1] == None:
1191                timestamp += self.relstart
1192
1193        # do not return trace if timestamp is outside, region is enlarged
1194        # by one pixel.
1195        pixel_duration = duration/pixel_width
1196        self.pixel_duration = pixel_duration
1197        if trace and (trace.stats.starttime > timestamp + pixel_duration or \
1198                             trace.stats.endtime + pixel_duration < timestamp):
1199            trace = None
1200
1201        return trace, timestamp
1202
1203    #@timeit
1204    def TraceAndTimeToScreen(self, trace, time):
1205        """
1206        traceCanvas:
1207        Return x, y from given trace (midpoint) and time code.
1208        """
1209        if trace is None:
1210            return (None,None)
1211        shx_graphics = trace.get_graphics()
1212        if shx_graphics == None:
1213            return (None,None)
1214        if shx_graphics.midpoint == None:
1215            return (None,None)
1216
1217        # horizontal
1218        if self.traceOrder in [0, 1]:
1219            pixel_width = self.width - STATION_INFO - MARGINS[1] - MARGINS[3]
1220        # vertical
1221        elif self.traceOrder in [2, 3]:
1222            pixel_width = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
1223
1224        zerotime = trace.stats.starttime - trace.get_info("t-origin")
1225        reltime = time - zerotime
1226       
1227        if self.timewindow[1] == None:
1228            start = self.relstart
1229            end = self.relend
1230        else:
1231            start = self.timewindow[0]
1232            end = self.timewindow[1]
1233       
1234        # relative position inside trace window
1235        relpos = (reltime - start) / (end - start) * pixel_width
1236
1237        # horizontal
1238        if self.traceOrder in [0, 1]:
1239            x, y = self.CalcScrolledPosition((
1240                relpos + STATION_INFO + MARGINS[3],
1241                shx_graphics.midpoint
1242            ))
1243        # vertical
1244        elif self.traceOrder == 2:
1245            x, y = self.CalcScrolledPosition(
1246                (shx_graphics.midpoint, self.height - relpos - STATION_INFO))
1247        elif self.traceOrder == 3:
1248            x, y = self.CalcScrolledPosition(
1249                (shx_graphics.midpoint, relpos + STATION_INFO))
1250        return x, y
1251   
1252
1253    #@timeit
1254    def _setup(self):
1255        "traceCanvas: helper routine."
1256        w, h = self.GetClientSize()
1257        if self.traceOrder in [0, 1]:
1258            if self.space is None:
1259                self.space = h
1260            self.SetVirtualSizeWH(w, self.space)
1261            self.SetScrollRate(0, 20)
1262        elif self.traceOrder in [2, 3]:
1263            if self.space is None:
1264                self.space = w
1265            self.SetVirtualSizeWH(self.space, h)
1266            self.SetScrollRate(20, 0)
1267
1268    #@timeit
1269    def _captureMouse(self, evt):
1270        "traceCanvas: helper."
1271        self.CaptureMouse()
1272
1273        ## create overlay
1274        #dc = wx.ClientDC(self)
1275        #odc = wx.DCOverlay(self.overlay_drag, dc)
1276        #odc.Clear()
1277
1278        self.dragStart = evt.GetPosition()
1279        self._debug("mouse captured at", self.dragStart)
1280
1281    #@timeit
1282    def _releaseMouse(self, evt):
1283        "traceCanvas: helper."
1284        self.ReleaseMouse()
1285
1286        ## restore view
1287        #dc = wx.ClientDC(self)
1288        #odc = wx.DCOverlay(self.overlay_drag, dc)
1289        #odc.Clear()
1290        #del odc
1291        self.overlay_drag.Reset()
1292
1293        self._debug("mouse released")
1294
1295    #@timeit
1296    def _drawPicksM(self):
1297        """
1298        traceCanvas: draw picks.
1299        """
1300        phaselist = PhaseList()
1301        _picks = {}
1302        for trc in self.traces:
1303            sname = "%s.%s" % (trc.stats.network,trc.stats.station)
1304            for phase in phaselist.getPhaseList(sname):
1305                if phase.comp and trc.stats.channel[-1] != phase.comp:
1306                    continue
1307                pcol = phaselist.picktypeColor( phase.picktype )
1308                if pcol not in _picks.keys():
1309                    _picks[pcol] = []
1310                ppos = self.TraceAndTimeToScreen(trc,phase.picktime)
1311                if ppos[0] != None:
1312                    _picks[pcol].append( (phase.name,ppos) )
1313
1314        if not _picks and not self.ZoomWindow:
1315            return
1316
1317        dc = wx.MemoryDC(self._bitmap)   # mdc
1318        #dc = wx.ClientDC(self)
1319        dc.SetBrush(wx.TRANSPARENT_BRUSH)
1320        dc.SetFont( wx.Font( 8, wx.DEFAULT, wx.NORMAL, wx.NORMAL ) )
1321
1322        th2 = self.traceheight / 2 - 2
1323        # draw picks
1324        for pcolor in _picks.keys():
1325            dc.SetPen(wx.Pen(pcolor, 1))
1326            dc.SetTextForeground(pcolor)
1327            for pick in _picks[pcolor]:
1328                if self.traceOrder in [0, 1]:
1329                    dc.DrawLine(
1330                        pick[1][0], pick[1][1] - th2,
1331                        pick[1][0], pick[1][1] + th2
1332                    )
1333                    if pick[0] != '_Mark_':
1334                        dc.DrawText(pick[0], pick[1][0] + 2, pick[1][1] - th2 - 10)
1335                elif self.traceOrder in [2, 3]:
1336                    dc.DrawLine(
1337                        pick[1][0] - th2, pick[1][1],
1338                        pick[1][0] + th2, pick[1][1],
1339                    )
1340                    if pick[0] != '_Mark_':
1341                        dc.DrawRotatedText(pick[0], pick[1][0] - th2 + 2,
1342                                                            pick[1][1] - 2, 90)
1343        del dc          # mdc
1344        self.Refresh()  # mdc
1345        self.Update()   # mdc
1346
1347    def _drawZoomWindow( self ):
1348        #draw zoom window
1349        if self.ZoomWindow:
1350            trace, start, end = self.ZoomWindow
1351            if  trace is not None:
1352                shx_graphics = trace.get_graphics()
1353                if shx_graphics == None:
1354                    return
1355                start = self.TraceAndTimeToScreen(trace, start)
1356                end = self.TraceAndTimeToScreen(trace, end)
1357                self._debug("draw zoom window")
1358                self.__zoombox( start, end, shx_graphics.midpoint,
1359                    color="Blue")
1360            else:
1361                self.zoomwdwwidth = 0
1362                self.ZoomWindow = None
1363                if plotter:
1364                    plotter.setZoomWindow( None, None, None )
1365                self.__zoombox( None, None, None )
1366
1367
1368    def _drawSinglePick( self, trace, tracetime, color=None ):
1369        "traceCanvas: Just for quick response to the user."
1370        pname = self.last_defined_phase
1371        if not pname:
1372            pname = "NewPhase"
1373        if color == None:
1374            color = 'red'
1375        dc = wx.ClientDC(self)
1376        dc.SetPen(wx.Pen(color, 1))
1377        dc.SetTextForeground(color)
1378        dc.SetFont( wx.Font( 8, wx.DEFAULT, wx.NORMAL, wx.NORMAL ) )
1379        th2 = self.traceheight / 2 - 2
1380        x, y, = self.TraceAndTimeToScreen(trace,tracetime)
1381        dc.DrawLine( x, y-th2, x, y+th2 )
1382        if pname != '_Mark_':
1383            dc.DrawText( pname, x+2, y-th2-10 )
1384       
1385
1386    #@timeit
1387    def _drawTraces(self):
1388        """
1389        traceCanvas:
1390        Do the real plotting of waveforms including station text and
1391        time scale.
1392        """
1393        # Get dimensions of the drawing area and number of traces to plot.
1394        # We cannot use the Screen module because of loop imports.
1395        if self._timewindow[1] is None:
1396            timewindow = get_runtime("extend")
1397        else:
1398            timewindow = self._timewindow
1399        self.width, self.height = self.GetVirtualSize()
1400        numTraces = len(self.traces) - Overlays.numberOfOverlays()
1401        if displayselect:
1402            dsptmp = displayselect[1]-displayselect[0]+1
1403            if dsptmp < numTraces:
1404                numTraces = dsptmp
1405        globalnorm = self._normtype.startswith("A")
1406
1407        # Delete zoom window if number of traces has changed.
1408        # -> i.e. not drawn in this redraw.
1409        if self.last_number_of_traces != numTraces and self.ZoomWindow:
1410            self.ZoomWindow = None
1411            if plotter:
1412                plotter.setZoomWindow( None, None, None )
1413        self.last_number_of_traces = numTraces
1414        self._drawXor( 'clear' )
1415
1416        # How much space is left for each trace:
1417        #    theight: height of trace space in pixel units
1418        #    pltwidth: width of trace space in pixel units
1419        # Compute trace height (theight) as float, otherwise trace positioning
1420        # is weird when many traces on screen.
1421        if self.traceOrder in [0, 1]:
1422            if numTraces:
1423                theight = float(self.height - TIMESCALE - MARGINS[0] \
1424                    - MARGINS[2]) / numTraces
1425            else:
1426                theight = float(self.height - TIMESCALE)
1427            pltwidth = self.width - STATION_INFO - MARGINS[1] - MARGINS[3]
1428        elif self.traceOrder in [2, 3]:
1429            if numTraces:
1430                theight = (self.width - TIMESCALE - MARGINS[1] \
1431                    - MARGINS[3]) / numTraces
1432            theight = self.width - TIMESCALE
1433            pltwidth = self.height - STATION_INFO - MARGINS[0] - MARGINS[2]
1434        else:
1435            raise ValueError("unknown trace order %d" % self.traceorder)
1436       
1437        # Trace height used in scaling routines,
1438        #   make it visible to other methods.
1439        self.traceheight = theight
1440
1441        # Bitmap holding the final figure, canvas->DC drawing to this bitmap.
1442        self._bitmap = wx.EmptyBitmap(self.width, self.height)
1443        canvas = wx.MemoryDC(self._bitmap)
1444        canvas.SetBrush(wx.TRANSPARENT_BRUSH)
1445        canvas.Clear()
1446        canvas.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL))
1447
1448        # If nothing to display, clear eand exit.
1449        if numTraces == 0:
1450            # just clearing the screen
1451            dc = wx.AutoBufferedPaintDCFactory(self)
1452            dc.Clear()
1453            return
1454
1455        if self.traceOrder == 0:
1456            trcs = self.traces[::-1]
1457        elif self.traceOrder in [1, 2, 3]:
1458            trcs = self.traces
1459       
1460        # Should traces overlap or stay within their y-ranges?
1461        # Overlapping traces: create taller bitmaps than computed before
1462        # in theight (zoomed up by a zoom factor). When the trace-bitmaps
1463        # are copied to the screen buffer, they overlap each other.
1464        overlapping_traces = get_runtime( "overlapping_traces", True )
1465
1466        # Init overlays positions. Overlays change the trace counting, since
1467        # we need less trace positions.
1468        Overlays.initPlot( len(self.traces), self.traceOrder )
1469
1470        # Loop over traces.
1471        plotidx = 0
1472        for trccnt,t in enumerate(trcs):
1473            if getattr(t, "shx_graphics", None) is None:
1474                # Trace not yet established.
1475                continue
1476            if displayselect:
1477                trcidx = t.index(True)
1478                if trcidx < displayselect[0] or trcidx > displayselect[1]:
1479                    shx_graphics = t.get_graphics()
1480                    if shx_graphics != None:
1481                        shx_graphics.midpoint = None
1482                    continue
1483            if not self.relativeAxis:
1484                start = self.start
1485                end = self.end
1486            else:
1487                start = t.stats.starttime
1488                end = start + self.maxDuration
1489            try:
1490                tzoom = t.stats.sh.ZOOM
1491            except:
1492                tzoom = 1.
1493
1494            # zheight is the (integer) height of the drawing box, possibly
1495            # zoomed by a factor if overlapping_traces is selected.
1496            if overlapping_traces:
1497                # Increased zheight produces overlapping of traces on display.
1498                zheight = int( theight * tzoom )
1499                try:
1500                    prepzoom = 1./tzoom
1501                except:
1502                    prepzoom = 1.
1503            else:
1504                zheight = int( theight )
1505                prepzoom = 0.9  # why not 1.?
1506           
1507            # Limit zheight to screen height, otherwise we run into memory
1508            # problems.
1509            if zheight > self.height:
1510                zheight = self.height
1511
1512            # Amplitude scaling factor within bitmap.
1513            norm = globalnorm and self.maxAmplitude or self._normtype[1]
1514           
1515            # After eliminating t.prepare_image_data (too slow), this
1516            # shx_graphics-stuff is probably not needed any more.
1517            shx_graphics = t.get_graphics()
1518            if shx_graphics == None:
1519                continue
1520
1521            # Create an emtpy bitmap for this trace, but only if the pixel
1522            # height is larger than 4. Doesn't make sense to put time and
1523            # CPU into figures with only 2 pixels height. Draw simple line
1524            # instead.
1525            if zheight > 4:
1526                if self.traceOrder in [0, 1]:
1527                    bitmap = wx.EmptyBitmap(pltwidth, zheight)
1528                elif self.traceOrder in [2, 3]:
1529                    bitmap = wx.EmptyBitmap(zheight, pltwidth)
1530            else:
1531                bitmap = None
1532            if bitmap is not None:
1533                # buffer to draw in
1534                dbuffer = wx.MemoryDC(bitmap)
1535                #dbuffer = dbuffer
1536                dbuffer.SetBrush(wx.TRANSPARENT_BRUSH)
1537                dbuffer.Clear()
1538                dbuffer.SetPen(wx.Pen((45,45,45), 1))
1539            else:
1540                dbuffer = None
1541
1542            # Time position of trace.
1543            _shift = t.get_info("t-origin") - timewindow[0]
1544            if _shift < 0.:
1545                _shift = 0.
1546            # On positive shifts the bitmap is moved out of the screen
1547            # to the right. On negative shifts the bitmap is recomputed
1548            # and always starts at 0. Fails if the bitmap moves completely
1549            # out to the left. Bitmap should be cleared in this case.
1550
1551            # calculate offset to global time scale
1552            plotStartSec = t.stats.starttime + _shift - start
1553            plotoffset = 0
1554            if plotStartSec or not self.relativeAxis:
1555                # re-calculate offset
1556                portion = plotStartSec / (timewindow[1] - timewindow[0])
1557                plotoffset = portion * pltwidth
1558
1559            # Get drawing attributes, color and other styles.
1560            attrib = t.get_info("attrib")
1561            try:
1562                style = get_style(attrib)
1563            except KeyError:
1564                style = get_default_attrib(t.get_info("METASTATUS"))
1565            cmode = None
1566            try:
1567                color = map(int, style.color.split(','))
1568                cmode = 'i'
1569            except:
1570                try:
1571                    color = map(float, style.color.split(','))
1572                    cmode = 'f'
1573                except:
1574                    color = style.color
1575                    cmode = 'o'
1576            if cmode == 'i':
1577                if 0 < max(color) <= 1:
1578                    # e.g. 1,0,0 means red, multiply with 255
1579                    color = [255*x for x in color]
1580            elif cmode == 'f':
1581                color = map( int, [255*x for x in color] )
1582            linestyle = getattr(wx, style.linestyle.upper())
1583
1584            # Draw seismogram into trace bitmap.
1585            if dbuffer is not None:
1586                dbuffer.SetPen(wx.Pen(color, style.linewidth, linestyle))
1587                dbuffer.BeginDrawing()
1588                dbuffer.DrawLines(
1589                    self.prepImage(t,pltwidth,zheight,timewindow,prepzoom,norm)
1590                )
1591                dbuffer.EndDrawing()
1592
1593            # Copy trace bitmap to canvas
1594            if dbuffer is not None:
1595                if self.traceOrder in [0, 1]:
1596                    if overlapping_traces:
1597                        fac = plotidx + 0.5 - 0.5*tzoom
1598                    else:
1599                        fac = plotidx
1600                    plotpos = Overlays.plotPos( plotidx+1, fac * theight + MARGINS[0])
1601                    canvas.Blit(
1602                        plotoffset + STATION_INFO + MARGINS[3], 
1603                        plotpos, 
1604                        pltwidth,
1605                        zheight,
1606                        dbuffer,
1607                        0,
1608                        0,
1609                        wx.AND
1610                    )
1611                elif self.traceOrder == 2:
1612                    canvas.Blit(plotidx * theight, -plotoffset + MARGIN, zheight,
1613                                                pltwidth, dbuffer, 0, 0, wx.AND)
1614                elif self.traceOrder == 3:
1615                    canvas.Blit(plotidx * theight, STATION_INFO + plotoffset, zheight,
1616                                                pltwidth, dbuffer, 0, 0, wx.AND)
1617
1618            # Put labels on traces.
1619            # Trace numbering
1620            if self.traceOrder in [1, 2, 3]:
1621                idx = trccnt + 1
1622            elif self.traceOrder == 0:
1623                idx = -trccnt + len(self.traces)
1624            # Currently no choice on type of label, just station and component.
1625            txt = "%d: %s %s" % (idx, t.stats.station, t.stats.channel[-1])
1626            canvas.SetPen(wx.Pen('Grey', 1, wx.LONG_DASH))
1627            # Draw labels and helper lines (zero lines)
1628            w, h, _, _ = canvas.GetFullTextExtent(txt)
1629            shx_graphics.midpoint = tmp = plotidx * theight + theight//2 + MARGINS[0]
1630            if self.traceOrder in [0, 1]:
1631                canvas.DrawText(txt, 5 + MARGINS[3], tmp - h // 2)
1632                canvas.DrawLine(STATION_INFO + MARGINS[3], tmp,
1633                    self.width - MARGINS[1], tmp)
1634            elif self.traceOrder == 2:
1635                canvas.DrawRotatedText(txt, tmp - h // 2, self.height - MARGIN, 90)
1636                canvas.DrawLine(shx_graphics.midpoint, MARGIN,
1637                    tmp, self.height - STATION_INFO)
1638            elif self.traceOrder == 3:
1639                canvas.DrawRotatedText(txt, tmp - h // 2,
1640                                    STATION_INFO - (STATION_INFO - w) // 2, 90)
1641                canvas.DrawLine(tmp, STATION_INFO, tmp, self.height - MARGIN)
1642
1643            plotidx += 1
1644            # End of trace loop
1645
1646        # Draw time axis.
1647        canvas.SetPen(wx.Pen('Black', 1))
1648        if self.relativeAxis:
1649            start, end = timewindow
1650
1651        if self.traceOrder in [0, 1]:
1652            PARTS = 5.  # axis split into X parts
1653            length = self.width - MARGINS[1] - MARGINS[3] - STATION_INFO  # pixel length of time axis
1654            fixpos = self.height - TIMESCALE - MARGINS[2]  # here: y coordinate
1655            varpos_start = STATION_INFO + MARGINS[3] # here: x start
1656        elif self.traceOrder in [2, 3]:
1657            PARTS = 4.
1658            length = self.height - MARGIN - STATION_INFO
1659            fixpos = self.width - TIMESCALE + 10  # here: x coordinate
1660            if self.traceOrder == 2:
1661                varpos_start = MARGIN  # here: y start
1662            elif self.traceOrder == 3:
1663                varpos_start = STATION_INFO
1664
1665        chunk_t, chunk, labeloffset, pixoffset, PARTS \
1666            = self.niceNumbers( start, end, length, PARTS )
1667        chunk2 = chunk / 5.
1668
1669        # basic time axis line
1670        if self.traceOrder in [0, 1]:
1671            canvas.DrawLine(varpos_start, fixpos,
1672                                                 length + varpos_start, fixpos)
1673        elif self.traceOrder in [2, 3]:
1674            canvas.DrawLine(fixpos, varpos_start,
1675                                                 fixpos, varpos_start + length)
1676
1677        # sections with ticks and time string
1678
1679        try:
1680            prec = abs(int(round(1 / math.log10(chunk_t))))
1681        except:
1682            prec = 1
1683        mask = "%%.%uf" % prec
1684        for i in range(int(PARTS)):
1685            if isinstance(start, UTCDateTime):
1686                timecode = labeloffset + start + i * chunk_t
1687                txt = timecode.strftime("%H:%M:%S.") + \
1688                                             str(timecode.microsecond//1000)
1689            else:
1690                txt = mask % (labeloffset + start + i * chunk_t)
1691
1692            tw = canvas.GetFullTextExtent(txt)[0]
1693
1694            if self.traceOrder in [0, 1]:
1695                varpos_current = pixoffset + varpos_start + i * chunk
1696                # time string
1697                canvas.DrawText(txt, varpos_current - tw / 2,
1698                                                fixpos + TIMESCALE_OFFSET_TEXT)
1699                # major tick
1700                canvas.DrawLine(varpos_current, fixpos,
1701                                varpos_current, fixpos + TIMESCALE_TICKS_MAJOR)
1702                # minor ticks
1703                for j in range(1, 5):
1704                    canvas.DrawLine(
1705                        varpos_current + j * chunk2,
1706                        fixpos,
1707                        varpos_current + j * chunk2,
1708                        fixpos + TIMESCALE_TICKS_MINOR
1709                    )
1710            elif self.traceOrder in [2, 3]:
1711                if self.traceOrder == 2:
1712                    varpos_current = pixoffset \
1713                        + varpos_start + (PARTS - i) * chunk
1714                elif self.traceOrder == 3:
1715                    varpos_current = pixoffset + varpos_start + i * chunk
1716                canvas.DrawRotatedText(txt, fixpos + TIMESCALE_OFFSET_TEXT,
1717                                                   varpos_current + tw / 2, 90)
1718                canvas.DrawLine(fixpos, varpos_current,
1719                                fixpos + TIMESCALE_TICKS_MAJOR, varpos_current)
1720                for j in range(1, 5):
1721                    if self.traceOrder == 2:
1722                        j -= 5
1723                    canvas.DrawLine(
1724                        fixpos,
1725                        varpos_current + j * chunk2,
1726                        fixpos + TIMESCALE_TICKS_MINOR,
1727                        varpos_current + j * chunk2
1728                     )
1729
1730        if self.do_screenshot:
1731            self.save_shot(canvas)
1732            self.do_screenshot = False
1733        # detach
1734        #ks canvas.SelectObject(wx.NullBitmap)
1735        del canvas
1736        self.Refresh()
1737        self.Update()
1738
1739    def prepImage( self, trc, width, height, timewindow, zoom, norm ):
1740        "traceCanvas: return polygon ready for plotting via wx."
1741
1742        windowstart, windowend = timewindow
1743        try:
1744            pt = trc.slice_relative(windowstart, windowend)
1745        except:
1746            return []
1747           
1748        # scaling in time direction
1749        duration_total = windowend - windowstart
1750        duration_trace = pt.stats.endtime - pt.stats.starttime
1751        pixel_width = duration_trace * width / duration_total
1752       
1753        # save pixel_width (but dont' really understand)
1754        shx_graphics = trc.get_graphics()
1755        if shx_graphics != None:
1756            shx_graphics.PlotPixels = pixel_width
1757
1758        # make sure, norm is a number
1759        if hasattr(norm, "lower"):
1760            norm = norm.upper()[0]
1761            if norm == "W":
1762                norm = abs(pt.max())
1763            elif norm == "T":
1764                norm = self.max()
1765            else:
1766                raise ValueError("Invalid input for normation!")
1767       
1768        # scaling in amplitude direction
1769        try:
1770            trczoom = trc.stats.sh.ZOOM
1771        except:
1772            trczoom = 1.0
1773        amplscale = 1. / norm * height / 2 * zoom * trczoom
1774        return zip(
1775            np.linspace( 0., pixel_width, len(pt.data) ),
1776            pt.data*amplscale + height/2
1777        )
1778
1779    #@timeit
1780    def save_shot(self, dc):
1781        """
1782        traceCanvas: Save PNG screen shot (PS later)
1783        """
1784        size = dc.Size
1785
1786        shot = wx.EmptyBitmap(size.width, size.height)
1787        shot_dc = wx.MemoryDC()
1788        shot_dc.SelectObject(shot)
1789
1790        shot_dc.Blit(0, 0, size.width, size.height, dc, 0, 0)
1791        shot_dc.SelectObject(wx.NullBitmap)
1792        img = shot.ConvertToImage()
1793        img.SaveFile(self.do_screenshot, wx.BITMAP_TYPE_PNG)
1794        log_message("info", "screen dump saved in: %s" % self.do_screenshot)
1795   
1796    #@timeit
1797    def niceNumbers( self, start, end, pixwidth, parts ):
1798        "traceCanvas: nice numbers for time axis."
1799        num = (end - start) / parts
1800        pixscaling = pixwidth / (end-start)
1801        tmp = 10**int(math.log10(num))  # integer power of 10
1802        nn = tmp   # best nice number is either this number or *2, *5, *10
1803        dist = abs(tmp-num)
1804        for i in (2,5,10):
1805            ndist = abs(num - i*tmp)
1806            if ndist < dist:
1807                nn = i*tmp
1808                dist = ndist
1809        labelstart = nn*int(start/nn)
1810        if (labelstart-start) < -1.e-5:
1811            labelstart += nn
1812        repeat = int((end-labelstart)/nn) + 1
1813        pixspan = nn * pixscaling
1814        pixoffset = (labelstart-start) * pixscaling
1815        labeloffset = labelstart - start
1816        #print nn, labelstart, pixoffset, repeat, '--', num, '.', start, end
1817        return (nn, pixspan, labeloffset, pixoffset, repeat)
1818
1819    @staticmethod
1820    def _debug(*args):
1821        "traceCanvas: helper."
1822        log_message('debug.wx', " ".join([str(i) for i in args]))
1823        #print "dbg:", " ".join([str(i) for i in args])
1824
1825    #@timeit
1826    def _resetTimeRange(self, **kwargs):
1827        """
1828        traceCanvas:
1829        Gather information about traces. Called on redraw (at idle state).
1830        """
1831        mint = UTCDateTime() # now
1832        maxt = UTCDateTime(0) # 1970
1833        minrel = 1e20
1834        maxrel = 0
1835        maxDuration = 0
1836        amplitude = 0
1837
1838        for t in self.traces:
1839            s = t.get_info("relstart")
1840            e = t.get_info("relend")
1841            if s < minrel:
1842                minrel = s
1843            if e > maxrel:
1844                maxrel = e
1845            if t.stats.starttime < mint:
1846                mint = t.stats.starttime
1847            if t.stats.endtime > maxt:
1848                maxt = t.stats.endtime
1849            # needed for relative time axis
1850            l = e - s
1851            if l > maxDuration:
1852                maxDuration = l
1853            # normation
1854            # no time window set or normtype forces full traces
1855            #if self._timewindow[1] is None or self._normtype.startswith("A"):
1856            if self._timewindow[1] is None:
1857                data = t.data
1858            else:
1859                try:
1860                    data = t.get_datawindow(*self._timewindow)
1861                except:
1862                    continue
1863            t_ampl = max( abs(data.max()), abs(data.min()) )
1864            if t_ampl > amplitude:
1865                amplitude = t_ampl
1866
1867        self.start = mint
1868        self.relstart = minrel
1869        self.end = maxt
1870        self.relend = maxrel
1871        self.maxDuration = maxDuration
1872        self.maxAmplitude = amplitude
1873        recent = self.timewindow
1874        self.timewindow = get_runtime("timewindow")
1875        if recent != self.timewindow:
1876            # time window changed, delete zoom box
1877            self.ZoomWindow = None
1878            if plotter:
1879                plotter.setZoomWindow( None, None, None )
1880
1881   
1882    #@timeit
1883    def __zoombox(self, start, end, midpoint, color="DARKORANGE", size=1,
1884        dc=None):
1885        "traceCanvas: draw zoom box in XOR mode."
1886        if start == None:
1887            self._drawXor( 'rect', (None,None,None,None), dc )
1888            return
1889        if midpoint == None:
1890            return
1891        marginfactor = 0.3
1892        mf2 = (1 - marginfactor) * 2
1893        th2 = self.traceheight / 2 + 1
1894        offset = midpoint - th2 - self.Scrolled
1895        if self.traceOrder in [0, 1]:
1896            self._drawXor( 'rect', (start[0], offset + th2 * marginfactor,
1897                end[0] - start[0], th2 * mf2 + 1), dc )
1898        elif self.traceOrder in [2, 3]:
1899            self._drawXor( 'rect', (offset + th2 * marginfactor, start[1],
1900                th2 * mf2, end[1] - start[1]), dc )
1901   
1902    def moveZoomWindow( self, direction, span=None ):
1903        "traceCanvas: move zoom window."
1904        if not self.ZoomWindow:
1905            return
1906        trace, start, end = self.ZoomWindow
1907        boxwidth = end - start
1908        if direction == 'right':
1909            if span == None:
1910                tmove = boxwidth/2.
1911            else:
1912                tmove = span
1913            start += tmove
1914            end += tmove
1915        elif direction == 'left':
1916            tmove = boxwidth/2.
1917            start -= tmove
1918            end -= tmove
1919        elif direction == 'shrink':
1920            if self.timewindow[1] is None:
1921                duration = self.relend - self.relstart
1922            else:
1923                duration = self.timewindow[1] - self.timewindow[0]
1924            limit = duration/100.
1925            if boxwidth > limit:
1926                boxwidth *= 0.9
1927                end = start + boxwidth
1928                self.zoomwdwwidth = boxwidth/self.pixel_duration
1929        elif direction == 'grow':
1930            if self.timewindow[1] is None:
1931                duration = self.relend - self.relstart
1932            else:
1933                duration = self.timewindow[1] - self.timewindow[0]
1934            limit = duration
1935            if boxwidth < limit:
1936                boxwidth *= 1.1
1937                end = start + boxwidth
1938                self.zoomwdwwidth = boxwidth/self.pixel_duration
1939        elif direction == 'up':
1940            oldoffset = trace.get_info("t-origin")
1941            oldstart = trace.stats.starttime
1942            newtrc = traces_from_list( "%d" % (trace.index(True)+1) )
1943            if newtrc and len(newtrc) == 1:
1944                trace = newtrc[0]
1945            else:
1946                return
1947            # correct for time offset otherwise zoom box jumps to left and right
1948            newoffset = trace.get_info("t-origin")
1949            newstart = trace.stats.starttime
1950            off = newoffset - oldoffset + (oldstart-newstart)
1951            start -= off
1952            end -= off
1953        elif direction == 'down':
1954            oldoffset = trace.get_info("t-origin")
1955            oldstart = trace.stats.starttime
1956            newidx = trace.index(True)-1
1957            if newidx == 0:
1958                return
1959            newtrc = traces_from_list( "%d" % newidx )
1960            if newtrc and len(newtrc) == 1:
1961                trace = newtrc[0]
1962            else:
1963                return
1964            # correct for time offset otherwise zoom box jumps ro left and right
1965            newoffset = trace.get_info("t-origin")
1966            newstart = trace.stats.starttime
1967            off = newoffset - oldoffset + (oldstart-newstart)
1968            start -= off
1969            end -= off
1970        else:
1971            return
1972        self.ZoomWindow = trace, start, end
1973        plotter.setZoomWindow( trace.index(True), _absToRelTime(trace,start),
1974            _absToRelTime(trace,end) )
1975        self._drawZoomWindow()
1976        plotter.magnify.canvas.magnifyTrace( trace, boxwidth=boxwidth,
1977            boxend=end )
1978
1979
1980class magnifyWindow(wx.Frame):
1981
1982    def __init__( self, parent, title, size=None, position=None ):
1983        "magnifyWindow: init method."
1984        if size == None or position == None:
1985            px, py = plotter.GetPosition()
1986            pw, ph = plotter.GetSize()
1987            if size == None:
1988                size = (pw,ph/5)
1989            if position == None:
1990                position = (px,0)
1991        wx.Frame.__init__(self, parent, title=title, size=size,
1992            style=wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE |
1993            wx.FRAME_FLOAT_ON_PARENT)
1994        #self.SetPosition( position )
1995        self.Move( get_runtime("wdwpos_magnify") )
1996        self.canvas = magnifyCanvas( self )
1997        self.Disable()
1998        self.Show()
1999        self.Enable()
2000
2001
2002class plotterWindow(wx.Frame):
2003    """
2004    Basic program frame including menu.
2005   
2006    Shortcuts
2007           a(FilterWA) b(Beam) d(DelTimeWdw) g(PhasePg) l(locate) m(mlSingle)
2008           n(PhasePn) p(PhaseP) s(SetTimeWdw) u(Undo Filter) w(planewave)
2009           0(PrevEvent) 1(NextEvent) 2(PhaseSg) 8(Filter 1-8Hz) >(zoom->right)
2010           <(zoom->left)
2011    Ctrl-  f(FK) l(FilterSROLP) n(Norm) o(Overlapping) q(Quit) s(SaveT&P)
2012           w(FilterWWSSNSP) y(SortByDistance)
2013    """
2014    def __init__(self, parent, title, size=(640,280), position=(50,100)):
2015        "plotterWindow: init method."
2016        # save instance for external access
2017        global plotter
2018        plotter = self
2019        wx.Frame.__init__(self, parent, title=title, size=size,
2020              style=wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE |
2021                    wx.FRAME_FLOAT_ON_PARENT)
2022
2023        #self.SetPosition(position) # positioning doesn't work
2024        self._menuitems = {}
2025        self.addMenuEntries()
2026        self.canvas = traceCanvas(self, [])
2027        statusbar = self.CreateStatusBar()
2028        statusbar.SetBackgroundColour( shxcolor1 )
2029        self.SetStatusText("%s %s" % (NAME, VERSION))
2030        self.Disable()
2031        self.Show()
2032        self.Enable()
2033        self._user_selection = 999.
2034        self._zoomwdw = None
2035        self._seltrace = None
2036        self.flag_beam = False
2037        self.flag_norm = False
2038        self.flag_overlap = True
2039        self.pardialog = None
2040        self.magnify = None
2041        set_runtime( "wdwpos_main", position )
2042        set_runtime( "wdwsize_main", size )
2043        self.Move( position )   # this works rather than SetPosition
2044        # set defaults for magnify and parameter window
2045        mh = 151
2046        set_runtime( "wdwpos_magnify", (position[0],position[1]-mh-30) )
2047        set_runtime( "wdwsize_magnify", (size[0],mh) )
2048        set_runtime( "wdwpos_params", (position[0]+size[0]+2,position[1]-mh-30) )
2049   
2050    def _setStatusText( self, localtext ):
2051        "plotterWindow: set local status text + global statusinfotext."
2052        self.SetStatusText( localtext + 10*'\t' + statusinfotext )
2053   
2054    def openMagnifyWindow( self, e ):
2055        "plotterWindow: open magnify window."
2056        self.magnify = magnifyWindow( self, 'SHX magnify' )
2057   
2058    def showMessage( self, msg, error=False ):
2059        "plotterWindow: show message in a dialog."
2060        mode = wx.OK
2061        if error:
2062            mode |= wx.ICON_ERROR
2063        dlg = wx.MessageDialog(self, msg, "An error occured!", mode )
2064        dlg.ShowModal()
2065        dlg.Destroy()
2066       
2067    def redraw(self):
2068        "plotterWindow: redraw procedure."
2069        self.canvas.OnSize(None)
2070   
2071    def setZoomWindow( self, trace, start, end ):
2072        "plotterWindow: get new zoom window (called from other class instances."
2073        self._seltrace = trace
2074        self._zoomwdw = (start,end)
2075
2076    def addMenuEntries( self ):
2077        "plotterWindow: definition of menu entries."
2078        menubar = wx.MenuBar()
2079        menubar.SetBackgroundColour( shxcolor2 )
2080        # file menu
2081        fileMenu = wx.Menu()
2082        self.addEntry( fileMenu, 'Read Data File(s)',
2083            'Read data file(s) in one of various formats', self.OnReadDataFile )
2084        self.addEntry( fileMenu, 'Write Data File',
2085            'Write all traces into file. Specify format as extension.',
2086            self.OnWriteDataFile )
2087        fileMenu.AppendSeparator()
2088        self.addEntry( fileMenu, 'Read FDSN Webservice',
2089            'Read network and stations network/station list '\
2090            +'+ circle area from FDSN webserver',
2091            self.OnReadFdsnCombi )
2092        self.addEntry( fileMenu, 'Add stations around epicenter (FDSN-ws)',
2093            'Read stations in circle area around epicener from FDSN webserver',
2094            self.OnReadFdsnCircle )
2095        fileMenu.AppendSeparator()
2096        self.addEntry( fileMenu, 'Complete Metadata (FDSN)',
2097            'Complete metadata of traces using FDSN webserver',
2098            self.OnCompleteMeta )
2099        fileMenu.AppendSeparator()
2100        self.addEntry( fileMenu, '&Quit\tCtrl+Q', 'Quit Graphics', self.OnQuit )
2101        # display menu
2102        windowMenu = wx.Menu()
2103        self.addEntry( windowMenu, '&Set Time Window\tS',
2104            'Set Zoom window as new time window', self.OnSetTimeWindow )
2105        self.addEntry( windowMenu, '&Del Time Window\tD',
2106            'Delete time window.', self.OnDelTimeWindow )
2107        windowMenu.AppendSeparator()
2108        self.addEntry( windowMenu, '&Move Zoom Right\t>',
2109            'Move zoom window to the right (50%).', self.OnMoveZoomRight )
2110        self.addEntry( windowMenu, 'Move Zoom Left\t<',
2111            'Move zoom window to the left (50%).', self.OnMoveZoomLeft )
2112        self.addEntry( windowMenu, 'Move Zoom Up',
2113            'Move zoom window upward.', self.OnMoveZoomUp, accel=wx.WXK_UP )
2114        self.addEntry( windowMenu, 'Move Zoom Down',
2115            'Move zoom window downward.', self.OnMoveZoomDown )
2116        self.addEntry( windowMenu, 'Zoom Amplitudes Up\t+',
2117            'Zoom amplitudes up (factor 2).', self.OnAmplZoomUp )
2118        self.addEntry( windowMenu, 'Zoom Amplitudes Down\t-',
2119            'Zoom amplitudes down (factor 0.5).', self.OnAmplZoomDown )
2120        windowMenu.AppendSeparator()
2121        m = self.addEntry( windowMenu, 'Trace Scroll On',
2122            'Display at max 21 traces and enable scrolling.',
2123            self.OnTraceScrollOn )
2124        self._menuitems['TraceScrollOn'] = m
2125        m = self.addEntry( windowMenu, 'Trace Scroll Off',
2126            'Display all traces, switches off scrolling.',
2127            self.OnTraceScrollOff )
2128        self._menuitems['TraceScrollOff'] = m
2129        m = self.addEntry( windowMenu, 'Trace Scroll Up',
2130            'Scroll traces up. Use mouse wheel.', self.OnTraceScrollUp )
2131        self._menuitems['TraceScrollUp'] = m
2132        m = self.addEntry( windowMenu, 'Trace Scroll Down',
2133            'Scroll traces down. Use mouse wheel', self.OnTraceScrollDown )
2134        self._menuitems['TraceScrollDown'] = m
2135        self.addEntry( windowMenu, 'Show All Components in Magnify\t3',
2136            'Show other components in magnify window.',
2137            self.OnShowAllInMagnify )
2138        windowMenu.AppendSeparator()
2139        self.addEntry( windowMenu, '&Norm All/Single\tCtrl+N',
2140            'Normalize All Traces Together / Normalize Traces Separately',
2141            self.OnNorm )
2142        self.addEntry( windowMenu, '&Overlapping/Clipped Traces\tCtrl+O',
2143            'Traces overlap when zooming / Traces are clipped when zooming '\
2144            +'amplitudes', self.OnOverlappingTraces )
2145        # traces menu
2146        tracesMenu = wx.Menu()
2147        self.addEntry( tracesMenu, '&Demean\tCtrl+D',
2148            'Remove mean value from all traces', self.OnDemean )
2149        self.addEntry( tracesMenu, 'Sort by Distance\tCtrl+Y',
2150            'Sort traces by distance', self.OnSortByDistance )
2151        tracesMenu.AppendSeparator()
2152        self.addEntry( tracesMenu, 'Delete Selected Trace',
2153            'Delete trace with zoom window', self.OnDeleteTrace )
2154        self.addEntry( tracesMenu, 'Delete Selected Station',
2155            'Delete all traces of this station', self.OnDeleteStation )
2156        self.addEntry( tracesMenu, 'Delete Selected And Above',
2157            'Delete trace with zoom window and all above',
2158            self.OnDeleteTraceAndAbove )
2159        tracesMenu.AppendSeparator()
2160        self.addEntry( tracesMenu, 'Spectrogram',
2161            'Compute spectrogram', self.OnSpectrogram )
2162        # array menu
2163        arrayMenu = wx.Menu()
2164        self.addEntry( arrayMenu, 'FK',
2165            'Compute FK for all traces on selected time window', self.OnFK )
2166        self.addEntry( arrayMenu, '&FK (Auto Freq)\tCtrl+F',
2167            'Compute FK with automatic freq window', self.OnFKAutoFreq )
2168        self.addEntry( arrayMenu, 'Beam / Del Beam\tB',
2169            'Compute/delete beam for all traces', self.OnBeam )
2170        self.addEntry( arrayMenu, 'Plane Wave\tW',
2171            'Compute slowness and azimuth using picks', self.OnPlaneWave )
2172        self.addEntry( arrayMenu, 'Correlation Picker',
2173            'Crosscorrelate all traces with selected wavelet and pick',
2174            self.OnCorrPick )
2175        # simulate menu
2176        simulateMenu = wx.Menu()
2177        self.addEntry( simulateMenu, 'Simulate &Wood-Anderson\tA',
2178            'Simulate Wood-Anderson instrument on all traces',
2179            self.OnSimulateWoodAnderson )
2180        self.addEntry( simulateMenu, 'Simulate Wood-Anderson-1Hz',
2181            'Simulate Wood-Anderson instrument + 1Hz-HP on all traces',
2182            self.OnSimulateWoodAnderson1Hz )
2183        self.addEntry( simulateMenu, 'Simulate WWSSN-SP\tCtrl+W',
2184            'Simulate WWSSN-SP instrument on all traces', self.OnSimulateWWSSNSP )
2185        self.addEntry( simulateMenu, 'Simulate WWSSN-LP',
2186            'Simulate WWSSN-LP instrument on all traces', self.OnSimulateWWSSNLP )
2187        self.addEntry( simulateMenu, 'Simulate LRSM-SP',
2188            'Simulate WWSSN-SP instrument on all traces', self.OnSimulateLRSMSP )
2189        self.addEntry( simulateMenu, 'Simulate LRSM-LP',
2190            'Simulate WWSSN-LP instrument on all traces', self.OnSimulateLRSMLP )
2191        self.addEntry( simulateMenu, 'Simulate KIRNOS',
2192            'Simulate KIRNOS instrument on all traces', self.OnSimulateKIRNOS )
2193        self.addEntry( simulateMenu, 'Simulate SRO-&LP\tCtrl+L',
2194            'Simulate SRO-LP instrument on all traces', self.OnSimulateSROLP )
2195        simulateMenu.AppendSeparator()
2196        self.addEntry( simulateMenu, 'Filter BP 1-8Hz\t8',
2197            'Apply Butterworth bandpass 1-8Hz', self.OnFilterBP_1_8 )
2198        simulateMenu.AppendSeparator()
2199        self.addEntry( simulateMenu, 'Undo Filter\tu',
2200            'Return to original traces', self.OnSimulateUndo )
2201        # events menu
2202        eventsMenu = wx.Menu()
2203        self.addEntry( eventsMenu, 'Get Eventlist',
2204            'Retrieve event list from FDSN server', self.OnGetEventlist )
2205        self.addEntry( eventsMenu, 'Next Event\t1',
2206            'Prepare next event time from list', self.OnNextEvent )
2207        self.addEntry( eventsMenu, 'Previous Event\t0',
2208            'Prepare previous event time from list', self.OnPrevEvent )
2209        eventsMenu.AppendSeparator()
2210        self.addEntry( eventsMenu, 'Event Info Parser ...',
2211            'Read event from text line', self.OnEventInfoParser )
2212        eventsMenu.AppendSeparator()
2213        self.addEntry( eventsMenu, 'Locate Local Event\tl',
2214            'Use phase list for locating event', self.OnLocateEvent )
2215        self.addEntry( eventsMenu, 'Locate Tele Event with P',
2216            'Use P arrivals for teleseismic location', self.OnLocateTeleEvent )
2217        self.addEntry( eventsMenu, 'Plot Location',
2218            'Simple map with stations and epicenter', self.OnPlotLocation )
2219        # phase menu
2220        phaseMenu = wx.Menu()
2221        self.addEntry( phaseMenu, 'Pg\tg',
2222            'Set phase for picking to Pg', self.OnSetPhase_Pg )
2223        self.addEntry( phaseMenu, 'Pn\tn',
2224            'Set phase for picking to Pn', self.OnSetPhase_Pn )
2225        self.addEntry( phaseMenu, 'Sg\t2',
2226            'Set phase for picking to Sg', self.OnSetPhase_Sg )
2227        self.addEntry( phaseMenu, 'Sn',
2228            'Set phase for picking to Sn', self.OnSetPhase_Sn )
2229        self.addEntry( phaseMenu, 'P\tp',
2230            'Set phase for picking to P', self.OnSetPhase_P )
2231        self.addEntry( phaseMenu, 'pP',
2232            'Set phase for picking to pP', self.OnSetPhase_pP )
2233        self.addEntry( phaseMenu, 'sP',
2234            'Set phase for picking to sP', self.OnSetPhase_sP )
2235        self.addEntry( phaseMenu, 'PP',
2236            'Set phase for picking to PP', self.OnSetPhase_PP )
2237        self.addEntry( phaseMenu, 'S',
2238            'Set phase for picking to S', self.OnSetPhase_S )
2239        self.addEntry( phaseMenu, 'PKP',
2240            'Set phase for picking to PKP', self.OnSetPhase_PKP )
2241        self.addEntry( phaseMenu, 'PKPdf',
2242            'Set phase for picking to PKPdf', self.OnSetPhase_PKPdf )
2243        self.addEntry( phaseMenu, 'PKPbc',
2244            'Set phase for picking to PKPbc', self.OnSetPhase_PKPbc )
2245        self.addEntry( phaseMenu, 'PKPab',
2246            'Set phase for picking to PKPab', self.OnSetPhase_PKPab )
2247        self.addEntry( phaseMenu, 'pPKPdf',
2248            'Set phase for picking to pPKPdf', self.OnSetPhase_pPKPdf )
2249        self.addEntry( phaseMenu, 'pPKPbc',
2250            'Set phase for picking to pPKPbc', self.OnSetPhase_pPKPbc )
2251        self.addEntry( phaseMenu, 'pPKPab',
2252            'Set phase for picking to pPKPab', self.OnSetPhase_pPKPab )
2253        self.addEntry( phaseMenu, 'Use ev_phase',
2254            'Use phase name from parameter ev_phase',
2255            self.OnSetPhaseUseEvPhase )
2256        phaseMenu.AppendSeparator()
2257        self.addEntry( phaseMenu, 'Theo Phases (local)',
2258            'Compute theoretical phases for a local event',
2259            self.OnTheoPhaseLocal )
2260        self.addEntry( phaseMenu, 'Theo Phases (tele)',
2261            'Compute theoretical phases for a teleseismic event',
2262            self.OnTheoPhaseTele )
2263        self.addEntry( phaseMenu, 'Delete all Theo Phases',
2264            'Delete all theoretical phases', self.OnDeleteTheoPhase )
2265        phaseMenu.AppendSeparator()
2266        self.addEntry( phaseMenu, 'Plot Phases',
2267            'Create distance vs travel time plot', self.OnPhasePlot )
2268        # magnitude menu
2269        magnMenu = wx.Menu()
2270        self.addEntry( magnMenu, 'ml on all Trc. (analytic)',
2271            'Computes magnitude ml around picked phases at all traces up to selected',
2272            self.OnMagnMlAnalytic )
2273        self.addEntry( magnMenu, '&ml on single Trc. (analytic)\tM',
2274            'Computes magnitude ml within selection window',
2275            self.OnMagnMlAnalyticSingle )
2276        self.addEntry( magnMenu, 'ml Delete all',
2277            'Deletes all ml magnitudes', self.OnMagnMlDeleteAll )
2278        self.addEntry( magnMenu, 'ml Delete selected',
2279            'Deletes magnitude ml at selected trace',
2280            self.OnMagnMlDeleteSingle )
2281        self.addEntry( magnMenu, 'ml Plot magnitudes',
2282            'Create and show a magnitude vs distance plot.', self.OnMagnMlPlot )
2283        # control menu
2284        controlMenu = wx.Menu()
2285        self.addEntry( controlMenu, 'Save Traces/Params\tCtrl+S',
2286            'Save trace info and parameters into a recovery command file',
2287            self.OnSaveTracesAndParamsQuick )
2288        self.addEntry( controlMenu, 'Save Traces/Params as ...',
2289            'Save trace info and parameters into a recovery command file',
2290            self.OnSaveTracesAndParams )
2291        self.addEntry( controlMenu, 'Recover Traces/Params',
2292            'Recover traces and parameters from default recovery',
2293            self.OnRecoverTracesAndParamsQuick )
2294        self.addEntry( controlMenu, 'Recover Traces/Params from ...',
2295            'Recover traces and parameters from a recovery command file',
2296            self.OnRecoverTracesAndParams )
2297        controlMenu.AppendSeparator()
2298        self.addEntry( controlMenu, 'Clear All Parameters and Traces',
2299            'Deletes traces, phase, magnitudes and resets parameters.',
2300            self.OnClearAllParameters )
2301        controlMenu.AppendSeparator()
2302        self.addEntry( controlMenu, 'Open Parameter Window',
2303            'Open parameter dialog', self.OnOpenParams )
2304        self.addEntry( controlMenu, 'Open Magnify Window',
2305            'Open magnification window for traces', self.OnOpenMagnify )
2306        self.addEntry( controlMenu, 'Refresh',
2307            'Refresh display', self.OnUserRefresh )
2308        # test menu
2309        testMenu = wx.Menu()
2310        self.addEntry( testMenu, 'Read GR * BHZ of an event',
2311            'fdsnws gr * * bhz 7-aug-15_00:16:30 420', self.OnTest1 )
2312        self.addEntry( testMenu, 'Read SX,TH * BHZ of an event',
2313            'fdsnws sx,th * * bhz 7-aug-15_00:16:30 420', self.OnTest2 )
2314        # put menus and menu bar in place
2315        menubar.Append( fileMenu, 'File' )
2316        menubar.Append( windowMenu, 'Display' )
2317        menubar.Append( tracesMenu, 'Traces' )
2318        menubar.Append( arrayMenu, 'Array' )
2319        menubar.Append( simulateMenu, 'Filter' )
2320        menubar.Append( eventsMenu, 'Events' )
2321        menubar.Append( phaseMenu, 'Phases' )
2322        menubar.Append( magnMenu, 'Magnitude' )
2323        menubar.Append( controlMenu, 'Control' )
2324        menubar.Append( testMenu, 'Test' )
2325        self._menuitems['TraceScrollOff'].Enable(False)
2326        self._menuitems['TraceScrollUp'].Enable(False)
2327        self._menuitems['TraceScrollDown'].Enable(False)
2328        self.SetMenuBar( menubar )
2329        self.Centre()
2330   
2331    def addEntry( self, menutitle, entrytext, entrydescr, callback,
2332        accel=None ):
2333        "plotterWindow: method to add a menu entry."
2334        #self.menu_entry_id += 1
2335        #qmi = wx.MenuItem( menutitle, self.menu_entry_id, entrytext,
2336        #    help=entrydescr )
2337        #menutitle.AppendItem( qmi )
2338        #self.Bind( wx.EVT_MENU, callback, id=self.menu_entry_id )       
2339        item = menutitle.Append( wx.ID_ANY, entrytext, entrydescr )
2340        item.SetBackgroundColour( shxcolor1 )
2341        self.Bind( wx.EVT_MENU, callback, item )
2342        # doesn't work:
2343        if accel:
2344            item.SetAccel( wx.AcceleratorEntry(keyCode=accel) )
2345        return item
2346
2347    def OnQuit( self, e ):
2348        "plotterWindow: file menu entry."
2349        # Cannot quit SHX, _sendShCommand waits for command to be executed
2350        # i.e. no way to terminate wx smoothly. Quit SHX after Close also not
2351        # possible, runs into communication issues between threads.
2352        #_sendShCommand( "quit y" )
2353        self.Close()
2354   
2355    def OnNorm( self, e ):
2356        "plotterWindow: display menu entry."
2357        if self.flag_norm:
2358            _sendShCommand( 'norm aw' )
2359        else:
2360            _sendShCommand( 'norm sw' )
2361        self.flag_norm = not self.flag_norm
2362
2363    def OnOverlappingTraces( self, e ):
2364        "plotterWindow: traces menu entry."
2365        if self.flag_overlap:
2366            _sendShCommand( 'fct overlapping_traces false' )
2367        else:
2368            _sendShCommand( 'fct overlapping_traces true' )
2369        self.flag_overlap = not self.flag_overlap
2370
2371    def OnDemean( self, e ):
2372        "plotterWindow: traces menu entry."
2373        _sendShCommand( 'demean all' )
2374   
2375    def OnDeleteTrace( self, e ):
2376        "plotterWindow: traces menu entry."
2377        if self._seltrace == None:
2378            self.showMessage( "no trace selected" )
2379        else:
2380            _sendShCommand( "del %d" % self._seltrace )
2381   
2382    def OnDeleteStation( self, e ):
2383        "plotterWindow: traces menu entry."
2384        if self._seltrace == None:
2385            self.showMessage( "no trace selected" )
2386            return
2387        trclist = traces_from_list( "%d" % self._seltrace )
2388        if len(trclist) != 1:
2389            self.showMessage( "Program bug: should select only one trace" )
2390            return
2391        trc = trclist[0]
2392        _sendShCommand( "del _station(%s)" % trc.stats.station )
2393
2394    def OnDeleteTraceAndAbove( self, e ):
2395        "plotterWindow: traces menu entry."
2396        if self._seltrace == None:
2397            self.showMessage( "no trace selected" )
2398        else:
2399            _sendShCommand( "del |%d|-|$dsptrcs|" % self._seltrace )
2400   
2401    def OnSpectrogram( self, e ):
2402        "plotterWindow: traces menu entry."
2403        if self._seltrace == None:
2404            self.showMessage( "no trace selected" )
2405            return
2406        try:
2407            timea, timeb = self._zoomwdw
2408        except:
2409            timea = timeb = None
2410        if timea and timeb:
2411            cmd = "spectrogram %d %g %g" % (self._seltrace,timea,timeb)
2412        else:
2413            cmd = "spectrogram %d" % self._seltrace
2414        _sendShCommand( cmd )
2415       
2416
2417    def OnTest1( self, e ):
2418        "plotterWindow: test menu entry."
2419        _sendShCommand( 'fdsnws gr * * bhz 7-aug-15_00:16:30 420' )
2420
2421    def OnTest2( self, e ):
2422        "plotterWindow: test menu entry."
2423        _sendShCommand( 'fdsnws sx,th * * bhz 7-aug-15_00:16:30 420' )
2424
2425    def OnSetTimeWindow( self, e ):
2426        "plotterWindow: display menu entry."
2427        _sendShCommand( 'stw %g %g' % self._zoomwdw )
2428
2429    def OnDelTimeWindow( self, e ):
2430        "plotterWindow: display menu entry."
2431        _sendShCommand( 'dtw' )
2432
2433    def OnMoveZoomRight( self, e ):
2434        "plotterWindow: display menu entry."
2435        plotter.canvas.moveZoomWindow( 'right' )
2436
2437    def OnMoveZoomLeft( self, e ):
2438        "plotterWindow: display menu entry."
2439        plotter.canvas.moveZoomWindow( 'left' )
2440
2441    def OnMoveZoomUp( self, e ):
2442        "plotterWindow: display menu entry."
2443        plotter.canvas.moveZoomWindow( 'up' )
2444
2445    def OnMoveZoomDown( self, e ):
2446        "plotterWindow: display menu entry."
2447        plotter.canvas.moveZoomWindow( 'down' )
2448
2449    def OnAmplZoomUp( self, e ):
2450        "plotterWindow: display menu entry."
2451        _sendShCommand( "zoom/rel all 2" )
2452
2453    def OnAmplZoomDown( self, e ):
2454        "plotterWindow: display menu entry."
2455        _sendShCommand( "zoom/rel all 0.5" )
2456
2457    def OnTraceScrollOn( self, e ):
2458        "plotterWindow: display menu entry."
2459        global displayselect
2460        displayselect = (1,21)
2461        plotter.canvas.refresh = True
2462        self._menuitems['TraceScrollOn'].Enable(False)
2463        self._menuitems['TraceScrollOff'].Enable()
2464        self._menuitems['TraceScrollUp'].Enable()
2465        self._menuitems['TraceScrollDown'].Enable()
2466
2467    def OnTraceScrollOff( self, e ):
2468        "plotterWindow: display menu entry."
2469        global displayselect
2470        displayselect = None
2471        plotter.canvas.refresh = True
2472        self._menuitems['TraceScrollOn'].Enable()
2473        self._menuitems['TraceScrollOff'].Enable(False)
2474        self._menuitems['TraceScrollUp'].Enable(False)
2475        self._menuitems['TraceScrollDown'].Enable(False)
2476       
2477    def OnTraceScrollUp( self, e ):
2478        "plotterWindow: display menu entry."
2479        _scrollTraces( 'up' )
2480
2481    def OnTraceScrollDown( self, e ):
2482        "plotterWindow: display menu entry."
2483        _scrollTraces( 'down' )
2484
2485    def OnShowAllInMagnify( self, e ):
2486        "plotterWindow: display menu entry."
2487        plotter.magnify.canvas.toggleShowAllTraces()
2488
2489    def OnReadDataFile( self, e ):
2490        "plotterWindow: file menu entry,  read one or more data files."
2491        style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
2492        dlg = wx.FileDialog( self, "Select Data File", defaultDir=os.getcwd(),
2493            style=style )
2494        dlg.ShowModal()
2495        filelist = dlg.GetPaths()
2496        retcode = dlg.GetReturnCode()
2497        dlg.Destroy()
2498        if retcode != wx.ID_OK:
2499            return
2500        cmd = '\n'.join(["@READO %s ALL" % fname.replace('/','\\') \
2501            for fname in filelist])
2502        _sendShCommand( cmd )
2503   
2504    def OnWriteDataFile( self, e ):
2505        "plotterWindow: file menue entry, writes all traces to file."
2506        legal_formats = ('mseed','q','gse','gse2','sac')
2507        style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
2508        dlg = wx.FileDialog( self, "Select Output File", defaultDir=os.getcwd(),
2509            style=style )
2510        dlg.ShowModal()
2511        outfile = dlg.GetPath()
2512        retcode = dlg.GetReturnCode()
2513        dlg.Destroy()
2514        if retcode != wx.ID_OK:
2515            return
2516        format = os.path.splitext(outfile)[1].lstrip('.').lower()
2517        if format not in legal_formats:
2518            msg = "Format '%s' not supported. Nothing written." % format
2519            self.showMessage( msg, error=True )
2520            return
2521        cmd = "@WRITEO %s ALL %s" % (outfile.replace('/','\\'),format.upper())
2522        _sendShCommand( cmd )
2523   
2524    def OnReadFdsnCombi( self, e ):
2525        "plotterWindow: file menu entry."
2526        ap = AnalysisPar()
2527        cmds = ['switch uniquechannel on']
2528        if ap.getValueAsString('readws_net'):
2529            cmd = "fdsnws %s %s %s %s %s %s /addr=%s" % (
2530                ap.getValueAsString( 'readws_net' ),
2531                ap.getValueAsString( 'readws_station' ),
2532                ap.getValueAsString( 'readws_loc' ),
2533                ap.getValueAsString( 'readws_chan' ),
2534                ap.getValueAsString( 'read_time' ),
2535                ap.getValueAsString( 'read_length' ),
2536                ap.getValueAsString( 'readws_server' ),
2537            )
2538            cmds.append( cmd )
2539        circle = ap.getValue('sta_radius')
2540        if circle != None and circle > 0.:
2541            cmd = "fdsnws %s,%s,%s * * %s %s %s /addr=%s" % (
2542                ap.getValueAsString( 'sta_latitude' ),
2543                ap.getValueAsString( 'sta_longitude' ),
2544                ap.getValueAsString( 'sta_radius' ),
2545                ap.getValueAsString( 'readws_chan' ),
2546                ap.getValueAsString( 'read_time' ),
2547                ap.getValueAsString( 'read_length' ),
2548                ap.getValueAsString( 'readws_server' ),
2549            )
2550            cmds.append( cmd )
2551        cmds.append( "meta all complete /addr=%s" \
2552            % ap.getValueAsString('readws_server') )
2553        _sendShCommand( '\n'.join(cmds) )
2554   
2555    def OnReadFdsnCircle( self, e ):
2556        "plotterWindow: file menu entry."
2557        ap = AnalysisPar()
2558        cmds = ['switch uniquechannel on']
2559        cmd = "fdsnws %s,%s,%s * * %s %s %s /addr=%s" % (
2560            ap.getValueAsString( 'epi_latitude' ),
2561            ap.getValueAsString( 'epi_longitude' ),
2562            ap.getValueAsString( 'sta_radius' ),
2563            ap.getValueAsString( 'readws_chan' ),
2564            ap.getValueAsString( 'read_time' ),
2565            ap.getValueAsString( 'read_length' ),
2566            ap.getValueAsString( 'readws_server' ),
2567        )
2568        cmds.append( cmd )
2569        cmds.append( "meta all complete /addr=%s" \
2570            % ap.getValueAsString('readws_server') )
2571        _sendShCommand( '\n'.join(cmds) )
2572   
2573    def OnCompleteMeta( self, e ):
2574        "plotterWindow: file menu entry."
2575        ap = AnalysisPar()
2576        _sendShCommand(
2577            "meta all complete /addr=%s" % ap.getValueAsString('readws_server')
2578        )
2579
2580    def OnSortByDistance( self, e ):
2581        "plotterWindow: traces menu entry."
2582        ap = AnalysisPar()
2583        lat = ap.getValueAsString( 'epi_latitude' )
2584        lon = ap.getValueAsString( 'epi_longitude' )
2585        if not lat or not lon:
2586            self.showMessage( "no epicenter defined" )
2587            return
2588        _sendShCommand( "sort_by_distance %s %s" % (lat,lon) )
2589   
2590    def OnFK( self, e ):
2591        "plotterWindow: array menu entry."
2592        if not self._zoomwdw:
2593            return
2594        timea, timeb = self._zoomwdw
2595        if timea == None or timeb == None:
2596            return
2597        ap = AnalysisPar()
2598        cmd = "shx_menu_fk %g %g %s %s %s" % (timea,timeb,
2599            ap.getValueAsString('fk_min_freq'),
2600            ap.getValueAsString('fk_max_freq'),
2601            ap.getValueAsString('fk_max_slowness'))
2602        _sendShCommand( cmd )
2603   
2604    def OnFKAutoFreq( self, e ):
2605        "plotterWindow: array menu entry."
2606        if not self._zoomwdw:
2607            return
2608        timea, timeb = self._zoomwdw
2609        if timea == None or timeb == None:
2610            return
2611        frq = 1./(timeb-timea)
2612        ap = AnalysisPar()
2613        cmd = "shx_menu_fk %g %g %s %s %s" % (timea,timeb,frq,(10.*frq),
2614            ap.getValueAsString('fk_max_slowness'))
2615        _sendShCommand( cmd )
2616
2617    def OnBeam( self, e ):
2618        "plotterWindow: menu entry."
2619        if self.flag_beam:
2620            _sendShCommand( "shx_menu_delbeam" )
2621        else:
2622            _sendShCommand( "shx_menu_beam" )
2623        self.flag_beam = not self.flag_beam
2624   
2625    def OnPlaneWave( self, e ):
2626        _sendShCommand( "shx_menu_plane_wave" )
2627   
2628    def OnCorrPick( self, e ):
2629        if not self._zoomwdw:
2630            self.showMessage( "No selection window found." )
2631            return
2632        timea, timeb = self._zoomwdw
2633        if timea == None or timeb == None:
2634            return
2635        _sendShCommand( "corrpick %d %g %g %g" % (self._seltrace,
2636            timea,timeb,((timeb-timea)*4.)) )
2637   
2638    def OnSimulateWoodAnderson( self, e ):
2639        "plotterWindow: filter menu entry."
2640        _sendShCommand( 'shx_menu_simulate wood-anderson ;; 5.' )
2641
2642    def OnSimulateWoodAnderson1Hz( self, e ):
2643        "plotterWindow: filter menu entry."
2644        _sendShCommand( 'shx_menu_simulate wood-anderson 1.0 5.' )
2645
2646    def OnSimulateWWSSNSP( self, e ):
2647        "plotterWindow: filter menu entry."
2648        _sendShCommand( 'shx_menu_simulate wwssn-sp ;; 5.' )
2649
2650    def OnSimulateWWSSNLP( self, e ):
2651        "plotterWindow: filter menu entry."
2652        _sendShCommand( 'shx_menu_simulate wwssn-lp ;; 20.' )
2653
2654    def OnSimulateLRSMSP( self, e ):
2655        "plotterWindow: filter menu entry."
2656        _sendShCommand( 'shx_menu_simulate lrsm-sp ;; 5.' )
2657
2658    def OnSimulateLRSMLP( self, e ):
2659        "plotterWindow: filter menu entry."
2660        _sendShCommand( 'shx_menu_simulate lrsm-lp ;; 20.' )
2661
2662    def OnSimulateKIRNOS( self, e ):
2663        "plotterWindow: filter menu entry."
2664        _sendShCommand( 'shx_menu_simulate kirnos ;; 20' )
2665
2666    def OnSimulateSROLP( self, e ):
2667        "plotterWindow: filter menu entry."
2668        _sendShCommand( 'shx_menu_simulate sro-lp ;; 30.' )
2669
2670    def OnFilterBP_1_8( self, e ):
2671        "plotterWindow: filter menu entry."
2672        _sendShCommand( 'shx_menu_filter bp_1hz_8hz_4 5.' )
2673
2674    def OnSimulateUndo( self, e ):
2675        "plotterWindow: filter menu entry."
2676        _sendShCommand( 'shx_menu_simulate undo' )
2677   
2678    def OnOpenParams( self, e ):
2679        "plotterWindow: control menu entry."
2680        self.pardialog = ParamDialog( self )
2681        self.pardialog.Show()
2682        self.pardialog.Move( get_runtime("wdwpos_params") )
2683   
2684    def OnOpenMagnify( self, e ):
2685        "plotterWindow: control menu entry."
2686        plotter.openMagnifyWindow(None)
2687   
2688    def OnUserRefresh( self, e ):
2689        self.redraw()
2690       
2691    def OnGetEventlist( self, e ):
2692        "plotterWindow: events menu entry."
2693        _sendShCommand( 'shx_menu_get_eventlist' )
2694
2695    def OnNextEvent( self, e ):
2696        "plotterWindow: events menu entry."
2697        _sendShCommand( 'shx_menu_next_event;;' )
2698
2699    def OnPrevEvent( self, e ):
2700        "plotterWindow: events menu entry."
2701        _sendShCommand( 'shx_menu_next_event -1' )
2702   
2703    def OnEventInfoParser( self, e ):
2704        "plotterWindow: events menu entry."
2705        dlg = EventParseDialog( self )
2706        dlg.ShowModal()
2707        dlg.Destroy()
2708
2709    def OnLocateEvent( self, e ):
2710        "plotterWindow: events menu entry."
2711        _sendShCommand( 'locate shxvar /showresult' )
2712   
2713    def OnLocateTeleEvent( self, e ):
2714        "plotterWindow: events menu entry."
2715        _sendShCommand( "shx_menu_locate_tele" )
2716       
2717    def OnPlotLocation( self, e ):
2718        "plotterWindow: events menu entry."
2719        ap = AnalysisPar()
2720        lat = ap.getValue( 'epi_latitude' )
2721        lon = ap.getValue( 'epi_longitude' )
2722        dep = ap.getValue( 'source_depth' )
2723        orig = ap.getValueAsString( 'origin_time' )
2724        title = "loc:_%s_%g,%g_dep:_%g_(%s)" % (orig,lat,lon,dep,
2725            ap.getValue('depth_type'))
2726        _sendShCommand( '@PLOT_STATIONS WITHPHASES LOCAL /ADDLOC=%g,%g /TITLE=%s' \
2727            % (lat,lon,title) )
2728       
2729    def OnSaveTracesAndParamsQuick( self, e ):
2730        "plotterWindow: control menu entry."
2731        sloutfile = 'TRACES_PARAMS_RECOVER.SHC'
2732        cmd = "param savetraces %s\n" % sloutfile\
2733            +"param saveascmd %s /append" % sloutfile
2734        _sendShCommand( cmd )
2735   
2736    def OnSaveTracesAndParams( self, e ):
2737        "plotterWindow: control menu entry."
2738        style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
2739        dlg = wx.FileDialog( self, "Select Save File", defaultDir=os.getcwd(),
2740            style=style, wildcard='*.SHC' )
2741        dlg.ShowModal()
2742        outfile = dlg.GetFilename()
2743        retcode = dlg.GetReturnCode()
2744        dlg.Destroy()
2745        if retcode != wx.ID_OK:
2746            return
2747        sloutfile = outfile.replace("/","\\")
2748        cmd = "param savetraces %s\n" % sloutfile\
2749            +"param saveascmd %s /append" % sloutfile
2750        _sendShCommand( cmd )
2751   
2752    def OnRecoverTracesAndParamsQuick( self, e ):
2753        "plotterWindow: control menu entry."
2754        _sendShCommand( 'TRACES_PARAMS_RECOVER' )
2755       
2756    def OnClearAllParameters( self, e ):
2757        _sendShCommand( 'shx_menu_reset_all' )
2758       
2759    def OnRecoverTracesAndParams( self, e ):
2760        "plotterWindow: control menu entry."
2761        style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
2762        dlg = wx.FileDialog( self, "Select Recovery File",
2763            defaultDir=os.getcwd(), style=style, wildcard='*.SHC' )
2764        dlg.ShowModal()
2765        recfile = dlg.GetFilename()
2766        retcode = dlg.GetReturnCode()
2767        dlg.Destroy()
2768        if retcode != wx.ID_OK:
2769            return
2770        slrecfile = recfile.replace('/','\\')
2771        if slrecfile.endswith('.SHC'):
2772            slrecfile = os.path.splitext(slrecfile)[0]
2773        _sendShCommand( slrecfile )
2774
2775    def OnSetPhase_Pg( self, e ):
2776        "plotterWindow: phase menu entry."
2777        _sendShCommand( "@PHASE DEFAULT_PHASE Pg" )
2778    def OnSetPhase_Pn( self, e ):
2779        "plotterWindow: phase menu entry."
2780        _sendShCommand( "@PHASE DEFAULT_PHASE Pn" )
2781    def OnSetPhase_Sg( self, e ):
2782        "plotterWindow: phase menu entry."
2783        _sendShCommand( "@PHASE DEFAULT_PHASE Sg" )
2784    def OnSetPhase_Sn( self, e ):
2785        "plotterWindow: phase menu entry."
2786        _sendShCommand( "@PHASE DEFAULT_PHASE Sn" )
2787    def OnSetPhase_P( self, e ):
2788        "plotterWindow: phase menu entry."
2789        _sendShCommand( "@PHASE DEFAULT_PHASE P" )
2790    def OnSetPhase_pP( self, e ):
2791        "plotterWindow: phase menu entry."
2792        _sendShCommand( "@PHASE DEFAULT_PHASE pP" )
2793    def OnSetPhase_sP( self, e ):
2794        "plotterWindow: phase menu entry."
2795        _sendShCommand( "@PHASE DEFAULT_PHASE sP" )
2796    def OnSetPhase_PP( self, e ):
2797        "plotterWindow: phase menu entry."
2798        _sendShCommand( "@PHASE DEFAULT_PHASE PP" )
2799    def OnSetPhase_S( self, e ):
2800        "plotterWindow: phase menu entry."
2801        _sendShCommand( "@PHASE DEFAULT_PHASE S" )
2802    def OnSetPhase_PKP( self, e ):
2803        "plotterWindow: phase menu entry."
2804        _sendShCommand( "@PHASE DEFAULT_PHASE PKP" )
2805    def OnSetPhase_PKPdf( self, e ):
2806        "plotterWindow: phase menu entry."
2807        _sendShCommand( "@PHASE DEFAULT_PHASE PKPdf" )
2808    def OnSetPhase_PKPbc( self, e ):
2809        "plotterWindow: phase menu entry."
2810        _sendShCommand( "@PHASE DEFAULT_PHASE PKPbc" )
2811    def OnSetPhase_PKPab( self, e ):
2812        "plotterWindow: phase menu entry."
2813        _sendShCommand( "@PHASE DEFAULT_PHASE PKPab" )
2814    def OnSetPhase_pPKPdf( self, e ):
2815        "plotterWindow: phase menu entry."
2816        _sendShCommand( "@PHASE DEFAULT_PHASE pPKPdf" )
2817    def OnSetPhase_pPKPbc( self, e ):
2818        "plotterWindow: phase menu entry."
2819        _sendShCommand( "@PHASE DEFAULT_PHASE pPKPbc" )
2820    def OnSetPhase_pPKPab( self, e ):
2821        "plotterWindow: phase menu entry."
2822        _sendShCommand( "@PHASE DEFAULT_PHASE pPKPab" )
2823
2824    def OnSetPhaseUseEvPhase( self, e ):
2825        "plotterWindow: phase menu entry."
2826        _sendShCommand( "@PHASE DEFAULT_PHASE %s"
2827            % AnalysisPar().getValueAsString('ev_phase'))
2828
2829    def OnTheoPhaseLocal( self, e ):
2830        "plotterWindow: phase menu entry."
2831        ap = AnalysisPar()
2832        for vname in ('epi_latitude','epi_longitude','source_depth',
2833            'origin_time'):
2834            val = ap.getValue(vname)
2835            if val == None or val == '':
2836                self.showMessage( "No location determined yet." )
2837                return
2838        plist = ap.getValue('phaselist')
2839        if plist == None or plist == "":
2840            plist = "Pg,Sg,Pn,Sn"
2841        _sendShCommand( "phase clear ;;; theo\n@THEOPHASE local all %s" % plist )
2842
2843    def OnTheoPhaseTele( self, e ):
2844        "plotterWindow: phase menu entry."
2845        _sendShCommand( "phase clear ;;; theo\ntheophase iasp91 all" )
2846   
2847    def OnPhasePlot( self, e ):
2848        _sendShCommand( "phase plot" )
2849
2850    def OnDeleteTheoPhase( self, e ):
2851        "plotterWindow: phase menu entry."
2852        _sendShCommand( "phase clear ;;; theo /redraw" )
2853
2854    def OnMagnMlAnalytic( self, e ):
2855        "plotterWindow: magnitude menu entry."
2856        if self._seltrace:
2857            cmd = "@MAGNITUDE determine ml 1-%d Pg,Sg,Pn,Sn 8" % self._seltrace
2858        else:
2859            cmd = "@MAGNITUDE determine ml all Pg,Sg,Pn,Sn 8"
2860        _sendShCommand( cmd )
2861   
2862    def OnMagnMlAnalyticSingle( self, e ):
2863        "plotterWindow: magnitude menu entry."
2864        try:
2865            timea, timeb = self._zoomwdw
2866        except:
2867            timea = timeb = None
2868        if timea == None or not self._seltrace:
2869            self.showMessage( "No trace selected" )
2870            return
2871        cmd = "magnitude determine ml %d %g %g" % (self._seltrace,timea,timeb)
2872        _sendShCommand( cmd )
2873   
2874    def OnMagnMlDeleteAll( self, e ):
2875        "plotterWindow: magnitude menu entry."
2876        _sendShCommand( "magnitude clear ml" )
2877
2878    def OnMagnMlDeleteSingle( self, e ):
2879        "plotterWindow: magnitude menu entry."
2880        if not self._seltrace:
2881            self.showMessage( "No trace selected" )
2882            return
2883        cmd = "magnitude clear ml %d" % self._seltrace
2884        _sendShCommand( cmd )
2885
2886    def OnMagnMlPlot( self, e ):
2887        "plotterWindow: magnitude menu entry."
2888        _infodisplayStatus( 'ml Plot magnitudes' )
2889        _sendShCommand( "magnitude plot ml" )
2890
2891
2892class ParamDialog(wx.Dialog):
2893
2894    def __init__( self, parent ):
2895        "ParamDialog: init method."
2896        wx.Dialog.__init__( self, parent, -1, title="Parameters",
2897            style=wx.SUNKEN_BORDER )       
2898        self.SetBackgroundColour( shxcolor1 )
2899        height = 26
2900        btnwidth = 70
2901        vbox = wx.BoxSizer(wx.VERTICAL)
2902        vbox.Add( (5,5), 0, wx.ALL, 1 )
2903        self.vtext = {}
2904        self.vbutton = {}
2905
2906        self.ap = AnalysisPar()
2907        lastgroup = self.ap.getGroup(self.ap.sortedParams()[0])
2908        for pname in self.ap.sortedParams():
2909            pvalue = self.ap.getValueAsString( pname )
2910            if self.ap.getGroup(pname) != lastgroup:
2911                # add spacer
2912                sline = wx.StaticLine(self, name='group', size=(300,1))
2913                #sline.SetForegroundColour( shxcolor2 )
2914                vbox.Add( sline, 0, wx.ALL, 5 )
2915                lastgroup = self.ap.getGroup(pname)
2916            ptext = "%s : %s" % (pname,pvalue)
2917            hbox = wx.BoxSizer(wx.HORIZONTAL)
2918            self.vbutton[pname] = wx.Button( self, label='Change',
2919                size=(btnwidth,height) )
2920            self.vbutton[pname].SetBackgroundColour( 'white' )
2921            self.vtext[pname] = wx.StaticText( self, label=ptext )
2922            hbox.Add( (3,3), 0, wx.ALL, 1 )
2923            hbox.Add( self.vbutton[pname], 0, wx.RIGHT, 1 )
2924            hbox.Add( self.vtext[pname], 1, wx.ALIGN_CENTER_VERTICAL, 1 )
2925            self.Bind( wx.EVT_BUTTON, partial(self.OnButton,pname),
2926                self.vbutton[pname] )
2927            vbox.Add( hbox, 0, wx.LEFT, 1 )
2928        vbox.Add( (5,5), 0, wx.ALL, 1 )
2929        vbox.SetSizeHints(self)
2930        self.SetAutoLayout(True)
2931        self.SetSizer( vbox )
2932        self.Layout()
2933   
2934    def OnButton( self, pname, e, *args ):
2935        "ParamDialog: button event."
2936        oldvalue = self.ap.getValueAsString( pname )
2937        ptype = self.ap.getType( pname )
2938        dlg = QueryString( self, "Change %s" % pname,
2939            "New value for '%s' (type %s):" % (pname,ptype), oldvalue )
2940        btn = dlg.ShowModal()
2941        result = dlg.getValue()
2942        dlg.Destroy()
2943        if result == None:
2944            # Clicked on Cancel
2945            return
2946        if self.validate( result, ptype ):
2947            if result == "":
2948                cmd = "param set %s ;;" % pname
2949            else:
2950                # phase names need capcnv switched off
2951                if result.startswith("$"):
2952                    cmd = "@PARAM SET %s |$DOLLAR|%s|" % (pname,result[1:])
2953                else:
2954                    cmd = "@PARAM SET %s %s" % (pname,result)
2955            _sendShCommand( cmd )
2956        else:
2957            msg = "Illegal input: got '%s' but need type %s" % (result,ptype)
2958            dlg = wx.MessageDialog(self, msg, "Illegal input",
2959                                                     wx.OK | wx.ICON_ERROR)
2960            dlg.ShowModal()
2961            dlg.Destroy()
2962        #self.updateVariables() # done via command line
2963   
2964    def updateVariables( self, *args ):
2965        "ParamDialog: update all values (strings) in dialog box."
2966        for pname in self.ap.sortedParams():
2967            pvalue = self.ap.getValueAsString( pname )
2968            self.vtext[pname].SetLabel( "%s : %s" % (pname,pvalue) )
2969   
2970    def validate( self, result, ptype ):
2971        "ParamDialog: validate user input according to data type."
2972        if ptype == 'int':
2973            try:
2974                i = int( result )
2975                return True
2976            except:
2977                return False
2978        elif ptype in ('float','floatN'):
2979            if ptype == 'floatN' and result == "":
2980                return True
2981            try:
2982                i = float( result )
2983                return True
2984            except:
2985                return False
2986        elif ptype == 'datetime':
2987            try:
2988                r = toUTCDateTime( result )
2989                return (r != None)
2990            except:
2991                return False
2992        elif ptype in ('string','file'):
2993            return (result != "")
2994        elif ptype in ('stringN','fileN'):
2995            return True
2996        else:
2997            print "Program bug, unknown type '%s'" % ptype
2998            return False
2999
3000
3001class QueryString(wx.Dialog):
3002
3003    def __init__( self, parent, title, prompt, oldvalue ):
3004        "QueryString init method."
3005        wx.Dialog.__init__( self, parent, -1, title )
3006        self.SetBackgroundColour( shxcolor1 )
3007        vbox = wx.BoxSizer( wx.VERTICAL )
3008        prompt = wx.StaticText( self, label=prompt )
3009        vbox.Add( prompt, 0, wx.ALIGN_CENTER_HORIZONTAL, 10 )
3010        vbox.Add( (10,5), 0 )
3011        self.text = wx.TextCtrl( self, value=oldvalue, size=(200,26) )
3012        vbox.Add( self.text, 0, wx.ALIGN_CENTER_HORIZONTAL, 10 )
3013        hbox = wx.BoxSizer( wx.HORIZONTAL )
3014        tbtn = wx.Button( self, label='OK', pos=(50,70) )
3015        self.Bind( wx.EVT_BUTTON, self.OnTextOk, tbtn )
3016        hbox.Add( tbtn, 0, wx.ALL, 10 )
3017        cbtn = wx.Button( self, label='Cancel', pos=(150,70) )
3018        self.Bind( wx.EVT_BUTTON, self.OnTextCancel, cbtn )
3019        hbox.Add( cbtn, 0, wx.ALL, 10 )
3020        tbox = wx.BoxSizer( wx.VERTICAL )
3021        tbox.Add( vbox, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 10 )
3022        tbox.Add( hbox, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 )
3023        tbox.SetSizeHints(self)
3024        self.SetAutoLayout(True)
3025        self.SetSizer( tbox )
3026        self.Layout()       
3027        self.result = None
3028   
3029    def OnTextOk( self, e ):
3030        "QueryString: OK button."
3031        self.result = self.text.GetValue()
3032        self.Destroy()
3033   
3034    def OnTextCancel( self, e ):
3035        "QueryString: cancel button."
3036        self.result = None
3037        self.Destroy()
3038   
3039    def getValue( self ):
3040        "QueryString: access to result."
3041        return self.result
3042       
3043
3044class EventParseDialog(wx.Dialog):
3045
3046    def __init__( self, parent ):
3047        "EventParseDialog: init method."
3048        wx.Dialog.__init__( self, parent, -1, title="Event Info Parser",
3049            style=wx.SUNKEN_BORDER )       
3050        self.SetBackgroundColour( shxcolor1 )
3051        height = 26
3052        btnwidth = 70
3053        vspace = [(15,15), 0, wx.ALL, 1]
3054
3055        hbox = wx.BoxSizer( wx.HORIZONTAL )
3056        okbtn = wx.Button( self, label='OK', pos=(50,70) )
3057        self.Bind( wx.EVT_BUTTON, self.OnTextOk, okbtn )
3058        hbox.Add( okbtn, 0, wx.ALL, 10 )
3059        cbtn = wx.Button( self, label='Cancel', pos=(150,70) )
3060        self.Bind( wx.EVT_BUTTON, self.OnTextCancel, cbtn )
3061        hbox.Add( cbtn, 0, wx.ALL, 10 )
3062
3063        vbox = wx.BoxSizer(wx.VERTICAL)
3064        vbox.Add( *vspace )
3065        vbox.Add( wx.StaticText(self,label="Reference Station:"), 0,
3066            wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 3 )
3067        self.text_refstat = wx.TextCtrl( self, value='gr.gra1', size=(60,height) )
3068        vbox.Add( self.text_refstat, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 10 )
3069        vbox.Add( *vspace )
3070        vbox.Add( wx.StaticText(self,label="Event Info Text:"), 0,
3071            wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 3 )
3072        self.text_evinfo = wx.TextCtrl( self, value='', size=(400,height) )
3073        vbox.Add( self.text_evinfo, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 10 )
3074        vbox.Add( *vspace )
3075        vbox.Add( hbox, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 3 )
3076        vbox.Add( *vspace )
3077        vbox.SetSizeHints(self)
3078        self.SetAutoLayout(True)
3079        self.SetSizer( vbox )
3080        self.Layout()
3081   
3082    def OnTextOk( self, e ):
3083        refstat = self.text_refstat.GetValue()
3084        evinfo = EventTextParser(self.text_evinfo.GetValue()).parse()
3085        if evinfo == None:
3086            plotter.showMessage( "Could not parse event line")
3087            self.Destroy()
3088            return
3089        origin, lat, lon = evinfo
3090        self.Destroy()
3091        cmd = "shx_util_eventinfo %s %s %g %g" % (refstat,
3092            fromUTCDateTime(origin),lat,lon)
3093        _sendShCommand( cmd )
3094       
3095    def OnTextCancel( self, e ):
3096        self.Destroy()
3097   
3098
3099def _absToRelTime( trc, abstime ):
3100    "Convert absolute (trace) time to relative (time axis) time."
3101    return (abstime - trc.stats.starttime + trc.get_info('t-origin'))
3102
3103def _relToAbsTime( trc, reltime ):
3104    "Convert relative (time axis) time into absolute (trace) time."
3105    return (trc.stats.starttime + reltime - trc.get_info('t-origin'))
3106
3107
3108def _sendShCommand( cmdstring, name="default" ):
3109    "Send command to command line interpreter."
3110    wx.BeginBusyCursor()
3111    try:
3112        msgs.sendMessage("ui.command", cmd=cmdstring, name=name )
3113        exc = None
3114    except Exception as exc:
3115        pass
3116    wx.EndBusyCursor()
3117    if exc:
3118        raise exc
3119
3120def _infodisplayStatus( text ):
3121    "Local routine for info display status text."
3122    global statusinfotext
3123    statusinfotext = text
3124    if plotter:
3125        plotter._setStatusText( 'Message' )
3126
3127
3128def ui_events(func):
3129    """
3130    """
3131    def wrapper(payload, msgid):
3132        result = func(payload)
3133        if msgid is not None:
3134            msgs.sendMessage(msgid, value=result, msgid=msgid)
3135    return wrapper
3136
3137
3138def _cursorNameToID( mode ):
3139    mode = mode.lower()
3140    if mode == "cross":
3141        _c = wx.CURSOR_CROSS
3142    elif mode == "normal":
3143        _c = wx.CURSOR_DEFAULT
3144    elif mode == "left":
3145        _c = wx.CURSOR_POINT_LEFT
3146    elif mode == "right":
3147        _c = wx.CURSOR_POINT_RIGHT
3148    elif mode == "busy":
3149        _c = wx.CURSOR_WAIT
3150    elif mode == "wait":
3151        _c = wx.CURSOR_ARROWWAIT
3152    return _c
3153
3154def _scrollTraces( direction ):
3155    global displayselect
3156    increment = 6
3157    if displayselect == None:
3158        return
3159    t1, t2 = displayselect
3160    trcno = len(Traces)
3161    if direction == 'up':
3162        t1 += increment
3163        t2 += increment
3164        while t2 > trcno:
3165            t2 -= 1
3166            t1 -= 1
3167    else:
3168        t1 -= increment
3169        t2 -= increment
3170        while t1 < 1:
3171            t1 += 1
3172            t2 += 1
3173    displayselect = (t1,t2)
3174    plotter.canvas.refresh = True
3175
3176@ui_events
3177def __set_cursor(mode):
3178    """
3179    Interface method for altering cursor style on canvas. Thread-safe.
3180    """
3181    _c = _cursorNameToID( mode )
3182    myCursor = wx.StockCursor(_c)
3183    global plotter
3184    wx.CallAfter(plotter.canvas.SetCursor, myCursor)
3185subscribe_ui_event(__set_cursor, "cursor")
3186
3187
3188@ui_events
3189def __select_gui(mode):
3190    """
3191    Interface method for selecting time and/or trace by user interaction. While
3192    UI remains responsive, everything waits for user input.
3193
3194    mode is one of the following commands:
3195    - relative (screen coordinates to relative offset) - for selecting windows
3196    - trace_time (trace under cursor and timestamp) - for picking
3197    """
3198    global plotter
3199    wx.CallAfter(plotter.canvas._set_modes, "interactive", mode)
3200    plotter._user_selection = None
3201    while plotter._user_selection is None:
3202        time.sleep(TIME_SLOT)
3203    return plotter._user_selection
3204subscribe_ui_event(__select_gui, "input")
3205
3206
3207@ui_events
3208def __screenshot(fname):
3209    """
3210    Trigger screen shot creation. Thread-safe. Image will be saved immediately.
3211    """
3212    global plotter
3213    plotter.canvas.do_screenshot = fname
3214    plotter.canvas.refresh = True
3215    wx.CallAfter(plotter.canvas.OnIdle, None)
3216subscribe_ui_event(__screenshot, "screenshot")
3217
3218
3219@ui_events
3220def __updateparams(unused):
3221    """
3222    Trigger redraw of parameter dialog.
3223    """
3224    global plotter
3225    if plotter and plotter.pardialog != None:
3226        wx.CallAfter(plotter.pardialog.updateVariables, None)
3227subscribe_ui_event(__updateparams, "updateparams")
3228
3229@ui_events
3230def __openparams(unused):
3231    """
3232    Open parameter dialog box.
3233    """
3234    global plotter
3235    if plotter and plotter.pardialog == None:
3236        wx.CallAfter(plotter.OnOpenParams, None)
3237subscribe_ui_event(__openparams, "openparams")
3238
3239@ui_events
3240def __openmagnify(unused):
3241    """
3242    Open magnify window.
3243    """
3244    global plotter
3245    if plotter and plotter.magnify == None:
3246        wx.CallAfter(plotter.openMagnifyWindow, None)
3247subscribe_ui_event(__openmagnify, "openmagnify")
3248
3249@ui_events
3250def __infodisplay(infotext):
3251    """
3252    Set text for infodisplay (in status line).
3253    """
3254    global statusinfotext
3255    statusinfotext = infotext
3256    if plotter:
3257        wx.CallAfter(plotter._setStatusText, 'Message')
3258subscribe_ui_event(__infodisplay, "infodisplay")
3259
Note: See TracBrowser for help on using the repository browser.