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

Revision 1137, 68.8 KB checked in by klaus, 4 years ago (diff)

bugfixes

  • 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        windowstart, windowend = timewindow
1162        try:
1163            pt = trc.slice_relative(windowstart, windowend)
1164        except:
1165            return []
1166           
1167        # scaling in time direction
1168        duration_total = windowend - windowstart
1169        duration_trace = pt.stats.endtime - pt.stats.starttime
1170        pixel_width = duration_trace * width / duration_total
1171       
1172        # save pixel_width (but dont' really understand)
1173        shx_graphics = trc.get_graphics()
1174        if shx_graphics != None:
1175            shx_graphics.PlotPixels = pixel_width
1176
1177        # make sure, norm is a number
1178        if hasattr(norm, "lower"):
1179            norm = norm.upper()[0]
1180            if norm == "W":
1181                norm = abs(pt.max())
1182            elif norm == "T":
1183                norm = self.max()
1184            else:
1185                raise ValueError("Invalid input for normation!")
1186       
1187        # scaling in amplitude direction
1188        try:
1189            trczoom = trc.stats.sh.ZOOM
1190        except:
1191            trczoom = 1.0
1192        amplscale = 1. / norm * height / 2 * zoom * trczoom
1193        return zip(
1194            np.linspace( 0., pixel_width, len(pt.data) ),
1195            pt.data*amplscale + height/2
1196        )
1197
1198    #@timeit
1199    def save_shot(self, dc):
1200        """
1201        Save PNG screen shot (PS later)
1202        """
1203        size = dc.Size
1204
1205        shot = wx.EmptyBitmap(size.width, size.height)
1206        shot_dc = wx.MemoryDC()
1207        shot_dc.SelectObject(shot)
1208
1209        shot_dc.Blit(0, 0, size.width, size.height, dc, 0, 0)
1210        shot_dc.SelectObject(wx.NullBitmap)
1211        img = shot.ConvertToImage()
1212        img.SaveFile(self.do_screenshot, wx.BITMAP_TYPE_PNG)
1213        log_message("info", "screen dump saved in: %s" % self.do_screenshot)
1214   
1215    #@timeit
1216    def niceNumbers( self, start, end, pixwidth, parts ):
1217        "2150 surf min"
1218        num = (end - start) / parts
1219        pixscaling = pixwidth / (end-start)
1220        tmp = 10**int(math.log10(num))  # integer power of 10
1221        nn = tmp   # best nice number is either this number or *2, *5, *10
1222        dist = abs(tmp-num)
1223        for i in (2,5,10):
1224            ndist = abs(num - i*tmp)
1225            if ndist < dist:
1226                nn = i*tmp
1227                dist = ndist
1228        labelstart = nn*int(start/nn)
1229        if (labelstart-start) < -1.e-5:
1230            labelstart += nn
1231        repeat = int((end-labelstart)/nn) + 1
1232        pixspan = nn * pixscaling
1233        pixoffset = (labelstart-start) * pixscaling
1234        labeloffset = labelstart - start
1235        #print nn, labelstart, pixoffset, repeat, '--', num, '.', start, end
1236        return (nn, pixspan, labeloffset, pixoffset, repeat)
1237
1238    @staticmethod
1239    def _debug(*args):
1240        log_message('debug.wx', " ".join([str(i) for i in args]))
1241        #print "dbg:", " ".join([str(i) for i in args])
1242
1243    #@timeit
1244    def _resetTimeRange(self, **kwargs):
1245        """
1246        Gather information about traces. Called on redraw (at idle state).
1247        """
1248        mint = UTCDateTime() # now
1249        maxt = UTCDateTime(0) # 1970
1250        minrel = 1e20
1251        maxrel = 0
1252        maxDuration = 0
1253        amplitude = 0
1254
1255        for t in self.traces:
1256            s = t.get_info("relstart")
1257            e = t.get_info("relend")
1258            if s < minrel:
1259                minrel = s
1260            if e > maxrel:
1261                maxrel = e
1262            if t.stats.starttime < mint:
1263                mint = t.stats.starttime
1264            if t.stats.endtime > maxt:
1265                maxt = t.stats.endtime
1266            # needed for relative time axis
1267            l = e - s
1268            if l > maxDuration:
1269                maxDuration = l
1270            # normation
1271            # no time window set or normtype forces full traces
1272            #if self._timewindow[1] is None or self._normtype.startswith("A"):
1273            if self._timewindow[1] is None:
1274                data = t.data
1275            else:
1276                try:
1277                    data = t.get_datawindow(*self._timewindow)
1278                except:
1279                    continue
1280            t_ampl = max( abs(data.max()), abs(data.min()) )
1281            if t_ampl > amplitude:
1282                amplitude = t_ampl
1283
1284        self.start = mint
1285        self.relstart = minrel
1286        self.end = maxt
1287        self.relend = maxrel
1288        self.maxDuration = maxDuration
1289        self.maxAmplitude = amplitude
1290        recent = self.timewindow
1291        self.timewindow = get_runtime("timewindow")
1292        if recent != self.timewindow:
1293            # time window changed, delete zoom box
1294            self.ZoomWindow = None
1295            if plotter:
1296                plotter.setZoomWindow( None, None, None )
1297
1298   
1299    #@timeit
1300    def __zoombox(self, start, end, midpoint, color="DARKORANGE", size=1):
1301        if start == None:
1302            self._drawXor( 'rect', (None,None,None,None) )
1303            return
1304        marginfactor = 0.3
1305        mf2 = (1 - marginfactor) * 2
1306        th2 = self.traceheight / 2
1307        offset = midpoint - th2 - self.Scrolled
1308        if self.traceOrder in [0, 1]:
1309            self._drawXor( 'rect', (start[0], offset + th2 * marginfactor,
1310                end[0] - start[0], th2 * mf2) )
1311        elif self.traceOrder in [2, 3]:
1312            self._drawXor( 'rect', (offset + th2 * marginfactor, start[1],
1313                th2 * mf2, end[1] - start[1]) )
1314
1315
1316class plotterWindow(wx.Frame):
1317    """
1318    Basic program frame including menu.
1319    """
1320    def __init__(self, parent, title, size=(640,280), position=(100,100)):
1321        # save instance for external access
1322        global plotter
1323        plotter = self
1324        wx.Frame.__init__(self, parent, title=title, size=size,
1325              style=wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE |
1326                    wx.FRAME_FLOAT_ON_PARENT)
1327
1328        self.SetPosition(position)
1329        self.addMenuEntries()
1330        self.canvas = traceCanvas(self, [])
1331        self.CreateStatusBar()
1332        self.SetStatusText("%s %s" % (NAME, VERSION))
1333        self.Disable()
1334        self.Show()
1335        self.Enable()
1336        self._user_selection = 999.
1337        self._zoomwdw = None
1338        self._seltrace = None
1339        self.flag_beam = False
1340        self.flag_norm = False
1341        self.flag_overlap = True
1342        self.pardialog = None
1343   
1344    def showMessage( self, msg, error=False ):
1345        mode = wx.OK
1346        if error:
1347            mode |= wx.ICON_ERROR
1348        dlg = wx.MessageDialog(self, msg, "An error occured!", mode )
1349        dlg.ShowModal()
1350        dlg.Destroy()
1351       
1352    def addMenuEntries( self ):
1353        menubar = wx.MenuBar()
1354        # file menu
1355        fileMenu = wx.Menu()
1356        self.addEntry( fileMenu, 'Read Data File(s)',
1357            'Read data file(s) in one of various formats', self.OnReadDataFile )
1358        self.addEntry( fileMenu, 'Write Data File',
1359            'Write all traces into file. Specify format as extension.',
1360            self.OnWriteDataFile )
1361        fileMenu.AppendSeparator()
1362        self.addEntry( fileMenu, 'Read FDSN net/station list',
1363            'Read station list from FDSN webserver', self.OnReadFdsnStations )
1364        self.addEntry( fileMenu, 'Read FDSN circle area',
1365            'Read stations in circle area from FDSN webserver',
1366            self.OnReadFdsnCircle )
1367        fileMenu.AppendSeparator()
1368        self.addEntry( fileMenu, 'Complete Metadata (FDSN)',
1369            'Complete metadata of traces using FDSN webserver',
1370            self.OnCompleteMeta )
1371        fileMenu.AppendSeparator()
1372        self.addEntry( fileMenu, '&Quit\tCtrl+Q', 'Quit Graphics', self.OnQuit )
1373        # window menu
1374        windowMenu = wx.Menu()
1375        self.addEntry( windowMenu, '&Set Time Window\tS',
1376            'Set Zoom window as new time window', self.OnSetTimeWindow )
1377        self.addEntry( windowMenu, '&Del Time Window\tD',
1378            'Delete time window', self.OnDelTimeWindow )
1379        self.addEntry( windowMenu, '&Norm All/Single\tCtrl+N',
1380            'Normalize All Traces Together / Normalize Traces Separately',
1381            self.OnNorm )
1382        self.addEntry( windowMenu, '&Overlapping/Clipped Traces\tCtrl+O',
1383            'Traces overlap when zooming / Traces are clipped when zooming '\
1384            +'amplitudes', self.OnOverlappingTraces )
1385        # traces menu
1386        tracesMenu = wx.Menu()
1387        self.addEntry( tracesMenu, '&Demean\tCtrl+D',
1388            'Remove mean value from all traces', self.OnDemean )
1389        self.addEntry( tracesMenu, '&Sort by Distance\tCtrl+S',
1390            'Sort traces by distance', self.OnSortByDistance )
1391        tracesMenu.AppendSeparator()
1392        self.addEntry( tracesMenu, 'Delete Selected Trace',
1393            'Delete trace with zoom window', self.OnDeleteTrace )
1394        self.addEntry( tracesMenu, 'Delete Selected And Above',
1395            'Delete trace with zoom window and all above',
1396            self.OnDeleteTraceAndAbove )
1397        # array menu
1398        arrayMenu = wx.Menu()
1399        self.addEntry( arrayMenu, 'FK',
1400            'Compute FK for all traces on selected time window', self.OnFK )
1401        self.addEntry( arrayMenu, '&FK (Auto Freq)\tCtrl+F',
1402            'Compute FK with automatic freq window', self.OnFKAutoFreq )
1403        self.addEntry( arrayMenu, 'Beam / Del Beam\tB',
1404            'Compute/delete beam for all traces', self.OnBeam )
1405        # simulate menu
1406        simulateMenu = wx.Menu()
1407        self.addEntry( simulateMenu, '&WWSSN-SP\tCtrl+W',
1408            'Simulate WWSSN-SP instrument on all traces', self.OnSimulateWWSSNSP )
1409        self.addEntry( simulateMenu, 'WWSSN-LP',
1410            'Simulate WWSSN-LP instrument on all traces', self.OnSimulateWWSSNLP )
1411        self.addEntry( simulateMenu, 'LRSM-SP',
1412            'Simulate WWSSN-SP instrument on all traces', self.OnSimulateLRSMSP )
1413        self.addEntry( simulateMenu, 'LRSM-LP',
1414            'Simulate WWSSN-LP instrument on all traces', self.OnSimulateLRSMLP )
1415        self.addEntry( simulateMenu, 'KIRNOS',
1416            'Simulate KIRNOS instrument on all traces', self.OnSimulateKIRNOS )
1417        self.addEntry( simulateMenu, 'SRO-&LP\tCtrl+L',
1418            'Simulate SRO-LP instrument on all traces', self.OnSimulateSROLP )
1419        self.addEntry( simulateMenu, 'Undo',
1420            'Return to original traces', self.OnSimulateUndo )
1421        # events menu
1422        eventsMenu = wx.Menu()
1423        self.addEntry( eventsMenu, 'Get Eventlist',
1424            'Retrieve event list from FDSN server', self.OnGetEventlist )
1425        self.addEntry( eventsMenu, 'Next Event\t1',
1426            'Prepare next event time from list', self.OnNextEvent )
1427        self.addEntry( eventsMenu, 'Previous Event\t0',
1428            'Prepare previous event time from list', self.OnPrevEvent )
1429        # control menu
1430        controlMenu = wx.Menu()
1431        self.addEntry( controlMenu, 'Save Traces & Params',
1432            'Save trace info and parameters into a recovery command file',
1433            self.OnSaveTracesAndParams )
1434        self.addEntry( controlMenu, 'Recover Traces & Params',
1435            'Recover traces and parameters from a a recovery command file',
1436            self.OnRecoverTracesAndParams )
1437        tracesMenu.AppendSeparator()
1438        self.addEntry( controlMenu, 'Open Parameter Window',
1439            'Open parameter dialog', self.OnOpenParams )
1440        # test menu
1441        testMenu = wx.Menu()
1442        self.addEntry( testMenu, 'Read GR * BHZ of an event',
1443            'fdsnws gr * * bhz 7-aug-15_00:16:30 420', self.OnTest1 )
1444        self.addEntry( testMenu, 'Read SX,TH * BHZ of an event',
1445            'fdsnws sx,th * * bhz 7-aug-15_00:16:30 420', self.OnTest2 )
1446        # put menus and menu bar in place
1447        menubar.Append( fileMenu, 'File' )
1448        menubar.Append( windowMenu, 'Window' )
1449        menubar.Append( tracesMenu, 'Traces' )
1450        menubar.Append( arrayMenu, 'Array' )
1451        menubar.Append( simulateMenu, 'Simulate' )
1452        menubar.Append( eventsMenu, 'Events' )
1453        menubar.Append( controlMenu, 'Control' )
1454        menubar.Append( testMenu, 'Test' )
1455        self.SetMenuBar( menubar )
1456        self.Centre()
1457   
1458    def addEntry( self, menutitle, entrytext, entrydescr, callback ):
1459        #self.menu_entry_id += 1
1460        #qmi = wx.MenuItem( menutitle, self.menu_entry_id, entrytext,
1461        #    help=entrydescr )
1462        #menutitle.AppendItem( qmi )
1463        #self.Bind( wx.EVT_MENU, callback, id=self.menu_entry_id )       
1464        item = menutitle.Append( wx.ID_ANY, entrytext, entrydescr )
1465        self.Bind( wx.EVT_MENU, callback, item )
1466
1467    def OnQuit( self, e ):
1468        # Cannot quit SHX, _sendShCommand waits for command to be executed
1469        # i.e. no way to terminate wx smoothly. Quit SHX after Close also not
1470        # possible, runs into communication issues between threads.
1471        #_sendShCommand( "quit y" )
1472        self.Close()
1473   
1474    def OnNorm( self, e ):
1475        if self.flag_norm:
1476            _sendShCommand( 'norm aw' )
1477        else:
1478            _sendShCommand( 'norm sw' )
1479        self.flag_norm = not self.flag_norm
1480
1481    def OnOverlappingTraces( self, e ):
1482        if self.flag_overlap:
1483            _sendShCommand( 'fct overlapping_traces false' )
1484        else:
1485            _sendShCommand( 'fct overlapping_traces true' )
1486        self.flag_overlap = not self.flag_overlap
1487
1488    def OnDemean( self, e ):
1489        _sendShCommand( 'demean all' )
1490   
1491    def OnDeleteTrace( self, e ):
1492        if self._seltrace == None:
1493            self.showMessage( "no trace selected" )
1494        else:
1495            _sendShCommand( "del %d" % self._seltrace )
1496
1497    def OnDeleteTraceAndAbove( self, e ):
1498        if self._seltrace == None:
1499            self.showMessage( "no trace selected" )
1500        else:
1501            _sendShCommand( "del |%d|-|$dsptrcs|" % self._seltrace )
1502
1503    def OnTest1( self, e ):
1504        _sendShCommand( 'fdsnws gr * * bhz 7-aug-15_00:16:30 420' )
1505
1506    def OnTest2( self, e ):
1507        _sendShCommand( 'fdsnws sx,th * * bhz 7-aug-15_00:16:30 420' )
1508
1509    def OnSetTimeWindow( self, e ):
1510        _sendShCommand( 'stw %g %g' % self._zoomwdw )
1511
1512    def OnDelTimeWindow( self, e ):
1513        _sendShCommand( 'dtw' )
1514
1515    def OnReadDataFile( self, e ):
1516        "Read one or more data files."
1517        style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
1518        dlg = wx.FileDialog( self, "Select Data File", defaultDir=os.getcwd(),
1519            style=style )
1520        dlg.ShowModal()
1521        filelist = dlg.GetPaths()
1522        retcode = dlg.GetReturnCode()
1523        dlg.Destroy()
1524        if retcode != wx.ID_OK:
1525            return
1526        cmd = '\n'.join(["@READO %s ALL" % fname.replace('/','\\') \
1527            for fname in filelist])
1528        _sendShCommand( cmd )
1529   
1530    def OnWriteDataFile( self, e ):
1531        "Writes all traces to file."
1532        legal_formats = ('mseed','q','gse','gse2','sac')
1533        style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
1534        dlg = wx.FileDialog( self, "Select Output File", defaultDir=os.getcwd(),
1535            style=style )
1536        dlg.ShowModal()
1537        outfile = dlg.GetPath()
1538        retcode = dlg.GetReturnCode()
1539        dlg.Destroy()
1540        if retcode != wx.ID_OK:
1541            return
1542        format = os.path.splitext(outfile)[1].lstrip('.').lower()
1543        if format not in legal_formats:
1544            msg = "Format '%s' not supported. Nothing written." % format
1545            self.showMessage( msg, error=True )
1546            return
1547        cmd = "@WRITEO %s ALL %s" % (outfile.replace('/','\\'),format.upper())
1548        _sendShCommand( cmd )
1549   
1550    def OnReadFdsnStations( self, e ):
1551        ap = AnalysisPar()
1552        cmd = "fdsnws %s %s %s %s %s %s /addr=%s" % (
1553            ap.getValueAsString( 'readws_net' ),
1554            ap.getValueAsString( 'readws_station' ),
1555            ap.getValueAsString( 'readws_loc' ),
1556            ap.getValueAsString( 'readws_chan' ),
1557            ap.getValueAsString( 'read_time' ),
1558            ap.getValueAsString( 'read_length' ),
1559            ap.getValueAsString( 'readws_server' ),
1560        )
1561        cmd += "\nmeta all complete /addr=%s" \
1562            % ap.getValueAsString('readws_server')
1563        _sendShCommand( cmd )       
1564
1565    def OnReadFdsnCircle( self, e ):
1566        ap = AnalysisPar()
1567        cmd = "fdsnws %s,%s,%s * * %s %s %s /addr=%s" % (
1568            ap.getValueAsString( 'sta_latitude' ),
1569            ap.getValueAsString( 'sta_longitude' ),
1570            ap.getValueAsString( 'sta_radius' ),
1571            ap.getValueAsString( 'readws_chan' ),
1572            ap.getValueAsString( 'read_time' ),
1573            ap.getValueAsString( 'read_length' ),
1574            ap.getValueAsString( 'readws_server' ),
1575        )
1576        cmd += "\nmeta all complete /addr=%s" \
1577            % ap.getValueAsString('readws_server')
1578        _sendShCommand( cmd )
1579   
1580    def OnCompleteMeta( self, e ):
1581        ap = AnalysisPar()
1582        _sendShCommand(
1583            "meta all complete /addr=%s" % ap.getValueAsString('readws_server')
1584        )
1585
1586    def OnSortByDistance( self, e ):
1587        ap = AnalysisPar()
1588        lat = ap.getValueAsString( 'epi_latitude' )
1589        lon = ap.getValueAsString( 'epi_longitude' )
1590        if not lat or not lon:
1591            self.showMessage( "no epicenter defined" )
1592            return
1593        _sendShCommand( "sort_by_distance %s %s" % (lat,lon) )
1594   
1595    def OnFK( self, e ):
1596        if not self._zoomwdw:
1597            return
1598        timea, timeb = self._zoomwdw
1599        if timea == None or timeb == None:
1600            return
1601        ap = AnalysisPar()
1602        cmd = "shx_menu_fk %g %g %s %s %s" % (timea,timeb,
1603            ap.getValueAsString('fk_min_freq'),
1604            ap.getValueAsString('fk_max_freq'),
1605            ap.getValueAsString('fk_max_slowness'))
1606        _sendShCommand( cmd )
1607   
1608    def OnFKAutoFreq( self, e ):
1609        if not self._zoomwdw:
1610            return
1611        timea, timeb = self._zoomwdw
1612        if timea == None or timeb == None:
1613            return
1614        frq = 1./(timeb-timea)
1615        ap = AnalysisPar()
1616        cmd = "shx_menu_fk %g %g %s %s %s" % (timea,timeb,frq,(10.*frq),
1617            ap.getValueAsString('fk_max_slowness'))
1618        print "dbg: fkcmd:", cmd
1619        _sendShCommand( cmd )
1620    def OnBeam( self, e ):
1621        if self.flag_beam:
1622            _sendShCommand( "shx_menu_delbeam" )
1623        else:
1624            _sendShCommand( "shx_menu_beam" )
1625        self.flag_beam = not self.flag_beam
1626   
1627    def OnSimulateWWSSNSP( self, e ):
1628        _sendShCommand( 'shx_menu_simulate wwssn-sp' )
1629
1630    def OnSimulateWWSSNLP( self, e ):
1631        _sendShCommand( 'shx_menu_simulate wwssn-lp' )
1632
1633    def OnSimulateLRSMSP( self, e ):
1634        _sendShCommand( 'shx_menu_simulate lrsm-sp' )
1635
1636    def OnSimulateLRSMLP( self, e ):
1637        _sendShCommand( 'shx_menu_simulate lrsm-lp' )
1638
1639    def OnSimulateKIRNOS( self, e ):
1640        _sendShCommand( 'shx_menu_simulate kirnos' )
1641
1642    def OnSimulateSROLP( self, e ):
1643        _sendShCommand( 'shx_menu_simulate sro-lp' )
1644
1645    def OnSimulateUndo( self, e ):
1646        _sendShCommand( 'shx_menu_simulate undo' )
1647   
1648    def OnOpenParams( self, e ):
1649        self.pardialog = ParamDialog( self )
1650        self.pardialog.Show()
1651
1652    def OnGetEventlist( self, e ):
1653        _sendShCommand( 'shx_menu_get_eventlist' )
1654
1655    def OnNextEvent( self, e ):
1656        _sendShCommand( 'shx_menu_next_event;;' )
1657
1658    def OnPrevEvent( self, e ):
1659        _sendShCommand( 'shx_menu_next_event -1' )
1660       
1661    def OnSaveTracesAndParams( self, e ):
1662        style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
1663        dlg = wx.FileDialog( self, "Select Save File", defaultDir=os.getcwd(),
1664            style=style, wildcard='*.SHC' )
1665        dlg.ShowModal()
1666        outfile = dlg.GetFilename()
1667        retcode = dlg.GetReturnCode()
1668        dlg.Destroy()
1669        if retcode != wx.ID_OK:
1670            return
1671        sloutfile = outfile.replace("/","\\")
1672        cmd = "param savetraces %s\n" % sloutfile\
1673            +"param saveascmd %s /append" % sloutfile
1674        print "dbg: cmd:", cmd
1675        _sendShCommand( cmd )
1676   
1677    def OnRecoverTracesAndParams( self, e ):
1678        style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
1679        dlg = wx.FileDialog( self, "Select Recovery File",
1680            defaultDir=os.getcwd(), style=style, wildcard='*.SHC' )
1681        dlg.ShowModal()
1682        recfile = dlg.GetFilename()
1683        retcode = dlg.GetReturnCode()
1684        dlg.Destroy()
1685        if retcode != wx.ID_OK:
1686            return
1687        slrecfile = recfile.replace('/','\\')
1688        if slrecfile.endswith('.SHC'):
1689            slrecfile = os.path.splitext(slrecfile)[0]
1690        _sendShCommand( slrecfile )
1691
1692    def redraw(self):
1693        self.canvas.OnSize(None)
1694   
1695    def setZoomWindow( self, trace, start, end ):
1696        self._seltrace = trace
1697        self._zoomwdw = (start,end)
1698
1699
1700class ParamDialog(wx.Dialog):
1701
1702    def __init__( self, parent ):
1703        wx.Dialog.__init__( self, parent, -1, "Parameters", size=(350,750) )
1704        #sizer =  self.CreateTextSizer('Param X')
1705       
1706        ypos = 10
1707        height = 26
1708        textwidth = 270
1709        btnwidth = 70
1710        space = 11
1711       
1712        self.SetBackgroundColour( 'white' )
1713        self.vtext = {}
1714        self.vbutton = {}
1715
1716        self.ap = AnalysisPar()
1717        for pname in self.ap.sortedParams():
1718            pvalue = self.ap.getValueAsString( pname )
1719            ptext = "%s : %s" % (pname,pvalue)
1720            self.vbutton[pname] = wx.Button( self, label='Change',
1721                pos=(space,ypos-4), size=(btnwidth,height) )
1722            self.vtext[pname] = wx.StaticText( self, label=ptext,
1723                pos=(btnwidth+2*space,ypos), size=(textwidth,height) )
1724            #self.vtext[pname].SetBackgroundColour( 'red' )
1725            self.Bind( wx.EVT_BUTTON, partial(self.OnButton,pname),
1726                self.vbutton[pname] )
1727            ypos += 28
1728   
1729    def OnButton( self, pname, e, *args ):
1730        oldvalue = self.ap.getValueAsString( pname )
1731        ptype = self.ap.getType( pname )
1732        dlg = QueryString( self, "Change %s" % pname,
1733            "New value for '%s' (type %s):" % (pname,ptype), oldvalue )
1734        btn = dlg.ShowModal()
1735        result = dlg.getValue()
1736        dlg.Destroy()
1737        if result == None:
1738            # Clicked on Cancel
1739            return
1740        if self.validate( result, ptype ):
1741            if result == "":
1742                cmd = "param set %s ;;" % pname
1743            else:
1744                cmd = "param set %s %s" % (pname,result)
1745            _sendShCommand( cmd )
1746        else:
1747            msg = "Illegal input: got '%s' but need type %s" % (result,ptype)
1748            dlg = wx.MessageDialog(self, msg, "Illegal input",
1749                                                     wx.OK | wx.ICON_ERROR)
1750            dlg.ShowModal()
1751            dlg.Destroy()
1752        #self.updateVariables() # done via command line
1753   
1754    def updateVariables( self, *args ):
1755        for pname in self.ap.sortedParams():
1756            pvalue = self.ap.getValueAsString( pname )
1757            self.vtext[pname].SetLabel( "%s : %s" % (pname,pvalue) )
1758   
1759    def validate( self, result, ptype ):
1760        if ptype == 'int':
1761            try:
1762                i = int( result )
1763                return True
1764            except:
1765                return False
1766        elif ptype in ('float','floatN'):
1767            if ptype == 'floatN' and result == "":
1768                return True
1769            try:
1770                i = float( result )
1771                return True
1772            except:
1773                return False
1774        elif ptype == 'datetime':
1775            try:
1776                r = toUTCDateTime( result )
1777                return (r != None)
1778            except:
1779                return False
1780        elif ptype in ('string','file'):
1781            return (result != "")
1782        elif ptype == 'fileN':
1783            return True
1784        else:
1785            print "Program bug, unknown type '%s'" % ptype
1786            return False
1787
1788
1789class QueryString(wx.Dialog):
1790
1791    def __init__( self, parent, title, prompt, oldvalue ):
1792        wx.Dialog.__init__( self, parent, -1, title, size=(350,120) )
1793        self.SetBackgroundColour( 'white' )
1794        wx.StaticText( self, label=prompt, pos=(10,10) )
1795        self.text = wx.TextCtrl( self, value=oldvalue, pos=(10,30),
1796            size=(200,26) )
1797        tbtn = wx.Button( self, label='OK', pos=(50,70) )
1798        self.Bind( wx.EVT_BUTTON, self.OnTextOk, tbtn )
1799        cbtn = wx.Button( self, label='Cancel', pos=(150,70) )
1800        self.Bind( wx.EVT_BUTTON, self.OnTextCancel, cbtn )
1801        self.result = None
1802   
1803    def OnTextOk( self, e ):
1804        self.result = self.text.GetValue()
1805        self.Destroy()
1806   
1807    def OnTextCancel( self, e ):
1808        self.result = None
1809        self.Destroy()
1810   
1811    def getValue( self ):
1812        return self.result
1813       
1814
1815
1816
1817def _sendShCommand( cmdstring, name="default" ):
1818    "Send command to command line interpreter."
1819    wx.BeginBusyCursor()
1820    try:
1821        msgs.sendMessage("ui.command", cmd=cmdstring, name=name )
1822        exc = None
1823    except Exception as exc:
1824        pass
1825    wx.EndBusyCursor()
1826    if exc:
1827        raise exc
1828
1829
1830def ui_events(func):
1831    """
1832    """
1833    def wrapper(payload, msgid):
1834        result = func(payload)
1835        if msgid is not None:
1836            msgs.sendMessage(msgid, value=result, msgid=msgid)
1837    return wrapper
1838
1839
1840def _cursorNameToID( mode ):
1841    mode = mode.lower()
1842    if mode == "cross":
1843        _c = wx.CURSOR_CROSS
1844    elif mode == "normal":
1845        _c = wx.CURSOR_DEFAULT
1846    elif mode == "left":
1847        _c = wx.CURSOR_POINT_LEFT
1848    elif mode == "right":
1849        _c = wx.CURSOR_POINT_RIGHT
1850    elif mode == "busy":
1851        _c = wx.CURSOR_WAIT
1852    elif mode == "wait":
1853        _c = wx.CURSOR_ARROWWAIT
1854    return _c
1855
1856
1857@ui_events
1858def __set_cursor(mode):
1859    """
1860    Interface method for altering cursor style on canvas. Thread-safe.
1861    """
1862    _c = _cursorNameToID( mode )
1863    myCursor = wx.StockCursor(_c)
1864    global plotter
1865    wx.CallAfter(plotter.canvas.SetCursor, myCursor)
1866subscribe_ui_event(__set_cursor, "cursor")
1867
1868
1869@ui_events
1870def __select_gui(mode):
1871    """
1872    Interface method for selecting time and/or trace by user interaction. While
1873    UI remains responsive, everything waits for user input.
1874
1875    mode is one of the following commands:
1876    - relative (screen coordinates to relative offset) - for selecting windows
1877    - trace_time (trace under cursor and timestamp) - for picking
1878    """
1879    global plotter
1880    wx.CallAfter(plotter.canvas._set_modes, "interactive", mode)
1881    plotter._user_selection = None
1882    while plotter._user_selection is None:
1883        time.sleep(TIME_SLOT)
1884    return plotter._user_selection
1885subscribe_ui_event(__select_gui, "input")
1886
1887
1888@ui_events
1889def __screenshot(fname):
1890    """
1891    Trigger screen shot creation. Thread-safe. Image will be saved immediately.
1892    """
1893    global plotter
1894    plotter.canvas.do_screenshot = fname
1895    plotter.canvas.refresh = True
1896    wx.CallAfter(plotter.canvas.OnIdle, None)
1897subscribe_ui_event(__screenshot, "screenshot")
1898
1899
1900@ui_events
1901def __updateparams(unused):
1902    """
1903    Trigger redraw of parameter dialog.
1904    """
1905    global plotter
1906    if plotter and plotter.pardialog != None:
1907        wx.CallAfter(plotter.pardialog.updateVariables, None)
1908subscribe_ui_event(__updateparams, "updateparams")
1909
1910@ui_events
1911def __openparams(unused):
1912    """
1913    Open parameter dialog box.
1914    """
1915    global plotter
1916    if plotter and plotter.pardialog == None:
1917        wx.CallAfter(plotter.OnOpenParams, None)
1918subscribe_ui_event(__openparams, "openparams")
1919
Note: See TracBrowser for help on using the repository browser.