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

Revision 1136, 69.0 KB checked in by klaus, 4 years ago (diff)

save and recover of traces and parameters

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