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

Revision 1190, 132.1 KB checked in by klaus, 4 years ago (diff)

drag and drop for command files

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