Пример #1
0
class DialogPeakThreshold(wx.Dialog):
    def __init__(self, parent, settings):
        wx.Dialog.__init__(self, parent=parent, title='Peak Thresold')

        self.settings = settings

        textThres = wx.StaticText(self, label='Threshold (dB)')
        self.ctrlThres = NumCtrl(self, integerWidth=3)
        self.ctrlThres.SetValue(settings.peaksThres)

        sizerButtons = wx.StdDialogButtonSizer()
        buttonOk = wx.Button(self, wx.ID_OK)
        buttonCancel = wx.Button(self, wx.ID_CANCEL)
        sizerButtons.AddButton(buttonOk)
        sizerButtons.AddButton(buttonCancel)
        sizerButtons.Realize()

        sizerGrid = wx.GridBagSizer(5, 5)
        sizerGrid.Add(textThres, pos=(0, 0),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(self.ctrlThres, pos=(0, 1),
                      flag=wx.ALL | wx.EXPAND, border=5)
        sizerGrid.Add(sizerButtons, pos=(1, 1),
                      flag=wx.ALIGN_RIGHT | wx.ALL, border=5)

        self.SetSizerAndFit(sizerGrid)

        self.Bind(wx.EVT_BUTTON, self.__on_ok, buttonOk)

    def __on_ok(self, _event):
        self.settings.peaksThres = self.ctrlThres.GetValue()

        self.EndModal(wx.ID_OK)
Пример #2
0
class DialogImageSize(wx.Dialog):
    def __init__(self, parent, settings, onlyDpi=False):
        wx.Dialog.__init__(self, parent=parent, title='Image settings')

        self.settings = settings

        textWidth = wx.StaticText(self, label="Width (inches)")
        self.ctrlWidth = NumCtrl(self, integerWidth=2, fractionWidth=1)
        self.ctrlWidth.SetValue(settings.exportWidth)
        self.Bind(EVT_NUM, self.__update_size, self.ctrlWidth)

        textHeight = wx.StaticText(self, label="Height (inches)")
        self.ctrlHeight = NumCtrl(self, integerWidth=2, fractionWidth=1)
        self.ctrlHeight.SetValue(settings.exportHeight)
        self.Bind(EVT_NUM, self.__update_size, self.ctrlHeight)

        textDpi = wx.StaticText(self, label="Dots per inch")
        self.spinDpi = wx.SpinCtrl(self)
        self.spinDpi.SetRange(32, 3200)
        self.spinDpi.SetValue(settings.exportDpi)
        self.Bind(wx.EVT_SPINCTRL, self.__update_size, self.spinDpi)

        textSize = wx.StaticText(self, label='Size')
        self.textSize = wx.StaticText(self)
        self.__update_size(None)

        sizerButtons = wx.StdDialogButtonSizer()
        buttonOk = wx.Button(self, wx.ID_OK)
        buttonCancel = wx.Button(self, wx.ID_CANCEL)
        sizerButtons.AddButton(buttonOk)
        sizerButtons.AddButton(buttonCancel)
        sizerButtons.Realize()
        self.Bind(wx.EVT_BUTTON, self.__on_ok, buttonOk)

        sizer = wx.GridBagSizer(5, 5)
        sizer.Add(textWidth, pos=(0, 0), flag=wx.ALL, border=5)
        sizer.Add(self.ctrlWidth, pos=(0, 1), flag=wx.ALL, border=5)
        sizer.Add(textHeight, pos=(1, 0), flag=wx.ALL, border=5)
        sizer.Add(self.ctrlHeight, pos=(1, 1), flag=wx.ALL, border=5)
        sizer.Add(textDpi, pos=(2, 0), flag=wx.ALL, border=5)
        sizer.Add(self.spinDpi, pos=(2, 1), flag=wx.ALL, border=5)
        sizer.Add(textSize, pos=(3, 0), flag=wx.ALL, border=5)
        sizer.Add(self.textSize, pos=(3, 1), flag=wx.ALL, border=5)
        sizer.Add(sizerButtons,
                  pos=(4, 0),
                  span=(1, 2),
                  flag=wx.ALL | wx.ALIGN_RIGHT,
                  border=5)
        sizer.SetEmptyCellSize((0, 0))

        if onlyDpi:
            textWidth.Hide()
            self.ctrlWidth.Hide()
            textHeight.Hide()
            self.ctrlHeight.Hide()
            textSize.Hide()
            self.textSize.Hide()

        self.SetSizerAndFit(sizer)

    def __update_size(self, _event):
        width = self.ctrlWidth.GetValue()
        height = self.ctrlHeight.GetValue()
        dpi = self.spinDpi.GetValue()

        self.textSize.SetLabel('{:.0f}px x {:.0f}px'.format(
            width * dpi, height * dpi))

    def __on_ok(self, _event):
        self.settings.exportWidth = self.ctrlWidth.GetValue()
        self.settings.exportHeight = self.ctrlHeight.GetValue()
        self.settings.exportDpi = self.spinDpi.GetValue()
        self.EndModal(wx.ID_OK)
Пример #3
0
class OptionsGraphPanel(wx.ScrolledWindow):
        '''
        Create the panel to choose basic options about graphs
        '''
        def __init__(self,  parent,  user_config,  customuser_config):

            self.temp_userconfig = user_config
            self.temp_customuserconfig = customuser_config

            wx.ScrolledWindow.__init__(self,  parent)
            sz1 = wx.BoxSizer(wx.VERTICAL)

            desc = []
            desc.append ( wx.StaticText(self, -1, '\nDefault Figure size.') )
            titleFont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)
            desc[-1].SetFont(titleFont)
            desc.append ( wx.StaticText(self, -1, 'Choose default size for all the figures.\nSize can be changed for every figure from their context menu.\n'))
            sz1.AddMany (desc)

            actual_size = self.temp_userconfig['FigureSize']
            inputsizer = wx.BoxSizer(wx.HORIZONTAL)
            self.fig_size_textctrl_x = NumCtrl (self, -1, size = (80,-1), value = actual_size[0], allowNegative = False, fractionWidth = 0, autoSize = False)
            self.fig_size_textctrl_y = NumCtrl (self, -1, size = (80,-1), value = actual_size[1], allowNegative = False, fractionWidth = 0, autoSize = False)
            self.fig_size_textctrl_x.Bind(wx.EVT_LEAVE_WINDOW, partial (self.OnUpdateSize, 'x'))
            self.fig_size_textctrl_y.Bind(wx.EVT_LEAVE_WINDOW, partial (self.OnUpdateSize, 'y'))
            self.fig_size_select_mu = wx.Choice(self, -1, choices = ['in', 'cm', 'mm', 'px'] )
            self.fig_size_select_mu.SetSelection(0)
            self.fig_size_select_mu.Bind(wx.EVT_CHOICE, self.OnChangeMeasureUnit)

            inputsizer.Add (self.fig_size_textctrl_x, 0, wx.ALL, 1)
            inputsizer.Add (self.fig_size_textctrl_y, 0, wx.ALL, 1)
            inputsizer.Add (self.fig_size_select_mu, 0, wx.ALL, 1)

            sz1.Add(inputsizer, 0, wx.ALL, 3)
            sz1.Add (wx.StaticLine(self), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5  )

            #Define default dpi size when exporting to file
            desc = []
            desc.append (  wx.StaticText(self, -1, '\nDefault dpi of exported images.') )
            desc[-1].SetFont(titleFont)
            desc.append (  wx.StaticText(self, -1, 'You may export images as bitmap files (tif, rgb).\nSelect here the default resolution at export time\n') )

            def_dpi = str(self.temp_userconfig['dpi'])
            txt_dpi = masked.TextCtrl(self , -1, def_dpi, mask = "###", name='DPI')
            txt_dpi.Bind(wx.EVT_TEXT, partial(self.OnChangeDPI, 'dpi'))
            desc.append(txt_dpi)
            sz1.AddMany (desc)
            sz1.Add (wx.StaticLine(self), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5  )


            # Group of radio controls:
            desc = []
            desc.append (  wx.StaticText(self, -1, '\nSelect the graphical interface.') )
            desc[-1].SetFont(titleFont)
            desc.append (  wx.StaticText(self, -1, 'The interactive interface is the default one.\nSelect the second one only in case the first one does not work.\nA restart is needed to see changes.\n') )

            grid1 = wx.FlexGridSizer( 0, 1, 0, 0 )
            self.canvas_type = []
            self.canvas_type.append( wx.RadioButton( self, -1, 'Interactive', name = 'wxmpl', style = wx.RB_GROUP ) )
            self.canvas_type.append( wx.RadioButton( self, -1, 'Not interactive', name = 'wx' ) )

            for radio in self.canvas_type:
                grid1.Add( radio, 0, wx.ALIGN_LEFT|wx.LEFT|wx.RIGHT|wx.TOP, 5 )
                radio.Bind(wx.EVT_RADIOBUTTON, self.OnCanvasSelect)
            ck = (self.temp_userconfig['Canvas'] == 'wx')*1
            self.canvas_type[ck].SetValue(True)

            desc.append (grid1)
            radio_sizer = wx.BoxSizer(wx.VERTICAL)
            radio_sizer.AddMany(desc)

            sz1.Add(radio_sizer, 0, wx.ALL, 3)

            self.SetSizer(sz1)
            sz1.Fit(self)

        def OnChangeDPI(self, key, event):
            value = event.EventObject.GetValue()
            try:
                self.temp_userconfig[key] = int(value)
            except:
                pass

        def OnChangeMeasureUnit(self, event):
            '''
            We update the value in the textbox when we change measure unit
            '''
            dpi = 96
            x = self.temp_userconfig['FigureSize'][0]
            y = self.temp_userconfig['FigureSize'][1]

            #conv_fact = (1, dpi*(1/2.54), dpi*(1/25.4), dpi)
            conv_fact = (1, 2.54, 25.4, dpi)   #in, cm, mm, px

            fra_width = (0, 2, 0, 2)
            mu = self.fig_size_select_mu.GetCurrentSelection()

            self.fig_size_textctrl_x.SetFractionWidth(fra_width[mu])
            self.fig_size_textctrl_y.SetFractionWidth(fra_width[mu])

            self.fig_size_textctrl_x.SetValue( x * conv_fact[mu])
            self.fig_size_textctrl_y.SetValue( y * conv_fact[mu] )

        def OnUpdateSize(self, ax, event):
            '''
            Size is always stored as inches but
            can be entered in different units (px, mm, cm)
            '''
            dpi = 96.0
            #conv_fact = (1, dpi*(1/2.54), dpi*(1/25.4), dpi)   #px, cm, mm, in
            conv_fact = (1, 1/2.54, 1/25.4, 1/dpi)   #in, cm, mm, px

            mu = self.fig_size_select_mu.GetCurrentSelection()

            if ax == 'x':
                size = (self.fig_size_textctrl_x.GetValue() * conv_fact[mu] , self.temp_userconfig['FigureSize'][1] )
            elif ax == 'y':
                size = (self.temp_userconfig['FigureSize'][0], self.fig_size_textctrl_y.GetValue() * conv_fact[mu] )

            self.temp_userconfig['FigureSize'] = size

        def OnCanvasSelect( self, event ):
            self.temp_userconfig['Canvas'] = event.EventObject.GetName()
Пример #4
0
class DialogPrefs(wx.Dialog):
    def __init__(self, parent, settings):
        self.settings = settings
        self.index = 0

        wx.Dialog.__init__(self, parent=parent, title="Preferences")

        self.colours = get_colours()
        self.winFunc = settings.winFunc
        self.background = settings.background

        self.checkSaved = wx.CheckBox(self, wx.ID_ANY, "Save warning")
        self.checkSaved.SetValue(settings.saveWarn)
        self.checkBackup = wx.CheckBox(self, wx.ID_ANY, "Backup")
        self.checkBackup.SetValue(settings.backup)
        self.checkBackup.SetToolTipString('Backup data after crash')
        self.checkAlert = wx.CheckBox(self, wx.ID_ANY, "Level alert (dB)")
        self.checkAlert.SetValue(settings.alert)
        self.checkAlert.SetToolTipString('Play alert when level exceeded')
        self.Bind(wx.EVT_CHECKBOX, self.__on_alert, self.checkAlert)
        self.spinLevel = wx.SpinCtrl(self, wx.ID_ANY, min=-100, max=20)
        self.spinLevel.SetValue(settings.alertLevel)
        self.spinLevel.Enable(settings.alert)
        self.spinLevel.SetToolTipString('Alert threshold')
        textBackground = wx.StaticText(self, label='Background colour')
        self.buttonBackground = wx.Button(self, wx.ID_ANY)
        self.buttonBackground.SetBackgroundColour(self.background)
        self.Bind(wx.EVT_BUTTON, self.__on_background, self.buttonBackground)
        textColour = wx.StaticText(self, label="Colour map")
        self.choiceColour = wx.Choice(self, choices=self.colours)
        self.choiceColour.SetSelection(self.colours.index(settings.colourMap))
        self.Bind(wx.EVT_CHOICE, self.__on_choice, self.choiceColour)
        self.colourBar = PanelColourBar(self, settings.colourMap)
        self.checkPoints = wx.CheckBox(self, wx.ID_ANY, "Limit points")
        self.checkPoints.SetValue(settings.pointsLimit)
        self.checkPoints.SetToolTipString('Limit the resolution of plots')
        self.Bind(wx.EVT_CHECKBOX, self.__on_points, self.checkPoints)
        self.spinPoints = wx.SpinCtrl(self, wx.ID_ANY, min=1000, max=100000)
        self.spinPoints.Enable(settings.pointsLimit)
        self.spinPoints.SetValue(settings.pointsMax)
        self.spinPoints.SetToolTipString(
            'Maximum number of points to plot_line')
        textDpi = wx.StaticText(self, label='Export DPI')
        self.spinDpi = wx.SpinCtrl(self, wx.ID_ANY, min=72, max=6000)
        self.spinDpi.SetValue(settings.exportDpi)
        self.spinDpi.SetToolTipString('DPI of exported images')
        self.checkTune = wx.CheckBox(self, wx.ID_ANY, "Tune SDR#")
        self.checkTune.SetValue(settings.clickTune)
        self.checkTune.SetToolTipString('Double click plot_line to tune SDR#')
        textPlugin = wx.HyperlinkCtrl(
            self,
            wx.ID_ANY,
            label="(Requires plugin)",
            url="http://eartoearoak.com/software/sdrsharp-net-remote")

        self.radioAvg = wx.RadioButton(self,
                                       wx.ID_ANY,
                                       'Average Scans',
                                       style=wx.RB_GROUP)
        self.radioAvg.SetToolTipString('Average level with each scan')
        self.Bind(wx.EVT_RADIOBUTTON, self.__on_radio, self.radioAvg)
        self.radioRetain = wx.RadioButton(self, wx.ID_ANY,
                                          'Retain previous scans')
        self.radioRetain.SetToolTipString('Can be slow')
        self.Bind(wx.EVT_RADIOBUTTON, self.__on_radio, self.radioRetain)
        self.radioRetain.SetValue(settings.retainScans)

        textMaxScans = wx.StaticText(self, label="Max scans")
        self.spinCtrlMaxScans = wx.SpinCtrl(self)
        self.spinCtrlMaxScans.SetRange(1, 5000)
        self.spinCtrlMaxScans.SetValue(settings.retainMax)
        self.spinCtrlMaxScans.SetToolTipString('Maximum previous scans'
                                               ' to display')

        textWidth = wx.StaticText(self, label="Line width")
        self.ctrlWidth = NumCtrl(self, integerWidth=2, fractionWidth=1)
        self.ctrlWidth.SetValue(settings.lineWidth)

        self.__on_radio(None)

        sizerButtons = wx.StdDialogButtonSizer()
        buttonOk = wx.Button(self, wx.ID_OK)
        buttonCancel = wx.Button(self, wx.ID_CANCEL)
        sizerButtons.AddButton(buttonOk)
        sizerButtons.AddButton(buttonCancel)
        sizerButtons.Realize()
        self.Bind(wx.EVT_BUTTON, self.__on_ok, buttonOk)

        gengrid = wx.GridBagSizer(10, 10)
        gengrid.Add(self.checkSaved, pos=(0, 0))
        gengrid.Add(self.checkBackup, pos=(1, 0))
        gengrid.Add(self.checkAlert, pos=(2, 0), flag=wx.ALIGN_CENTRE)
        gengrid.Add(self.spinLevel, pos=(2, 1))
        gengrid.Add(textBackground, pos=(3, 0), flag=wx.ALIGN_CENTRE)
        gengrid.Add(self.buttonBackground, pos=(3, 1))
        gengrid.Add(textColour, pos=(4, 0))
        gengrid.Add(self.choiceColour, pos=(4, 1))
        gengrid.Add(self.colourBar, pos=(4, 2))
        gengrid.Add(self.checkPoints, pos=(5, 0))
        gengrid.Add(self.spinPoints, pos=(5, 1))
        gengrid.Add(textDpi, pos=(6, 0))
        gengrid.Add(self.spinDpi, pos=(6, 1))
        gengrid.Add(self.checkTune, pos=(7, 0))
        gengrid.Add(textPlugin, pos=(7, 1))
        genbox = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "General"))
        genbox.Add(gengrid, 0, wx.ALL | wx.ALIGN_CENTRE_VERTICAL, 10)

        congrid = wx.GridBagSizer(10, 10)
        congrid.Add(self.radioAvg, pos=(0, 0))
        congrid.Add(self.radioRetain, pos=(1, 0))
        congrid.Add(textMaxScans, pos=(2, 0), flag=wx.ALIGN_CENTRE_VERTICAL)
        congrid.Add(self.spinCtrlMaxScans, pos=(2, 1))
        conbox = wx.StaticBoxSizer(
            wx.StaticBox(self, wx.ID_ANY, "Continuous Scans"), wx.VERTICAL)
        conbox.Add(congrid, 0, wx.ALL | wx.EXPAND, 10)

        plotgrid = wx.GridBagSizer(10, 10)
        plotgrid.Add(textWidth, pos=(0, 0))
        plotgrid.Add(self.ctrlWidth, pos=(0, 1))
        plotbox = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "Plot View"),
                                    wx.HORIZONTAL)
        plotbox.Add(plotgrid, 0, wx.ALL | wx.EXPAND, 10)

        grid = wx.GridBagSizer(10, 10)
        grid.Add(genbox, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
        grid.Add(conbox, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
        grid.Add(plotbox, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
        grid.Add(sizerButtons, pos=(3, 1), flag=wx.EXPAND)

        box = wx.BoxSizer()
        box.Add(grid, flag=wx.ALL | wx.ALIGN_CENTRE, border=10)

        self.SetSizerAndFit(box)

    def __on_alert(self, _event):
        enabled = self.checkAlert.GetValue()
        self.spinLevel.Enable(enabled)

    def __on_points(self, _event):
        enabled = self.checkPoints.GetValue()
        self.spinPoints.Enable(enabled)

    def __on_background(self, _event):
        colour = wx.ColourData()
        colour.SetColour(self.background)

        dlg = CubeColourDialog(self, colour, 0)
        if dlg.ShowModal() == wx.ID_OK:
            newColour = dlg.GetColourData().GetColour()
            self.background = newColour.GetAsString(wx.C2S_HTML_SYNTAX)
            self.buttonBackground.SetBackgroundColour(self.background)
        dlg.Destroy()

    def __on_radio(self, _event):
        enabled = self.radioRetain.GetValue()
        self.spinCtrlMaxScans.Enable(enabled)

    def __on_choice(self, _event):
        self.colourBar.set_map(self.choiceColour.GetStringSelection())
        self.choiceColour.SetFocus()

    def __on_ok(self, _event):
        self.settings.saveWarn = self.checkSaved.GetValue()
        self.settings.backup = self.checkBackup.GetValue()
        self.settings.alert = self.checkAlert.GetValue()
        self.settings.alertLevel = self.spinLevel.GetValue()
        self.settings.clickTune = self.checkTune.GetValue()
        self.settings.pointsLimit = self.checkPoints.GetValue()
        self.settings.pointsMax = self.spinPoints.GetValue()
        self.settings.exportDpi = self.spinDpi.GetValue()
        self.settings.retainScans = self.radioRetain.GetValue()
        self.settings.lineWidth = self.ctrlWidth.GetValue()
        self.settings.retainMax = self.spinCtrlMaxScans.GetValue()
        self.settings.colourMap = self.choiceColour.GetStringSelection()
        self.settings.background = self.background

        self.EndModal(wx.ID_OK)
Пример #5
0
class FrameMain(wx.Frame):
    def __init__(self, title, pool):

        self.pool = pool
        self.lock = threading.Lock()

        self.sdr = None
        self.threadScan = None
        self.threadUpdate = None
        self.threadLocation = None

        self.serverLocation = None

        self.isNewScan = True
        self.isScanning = False

        self.stopAtEnd = False
        self.stopScan = False

        self.dlgCal = None
        self.dlgSats = None
        self.dlgLog = None

        self.menuMain = None
        self.menuPopup = None

        self.graph = None
        self.toolbar = None
        self.canvas = None

        self.buttonStart = None
        self.buttonStop = None
        self.controlGain = None
        self.choiceMode = None
        self.choiceDwell = None
        self.choiceNfft = None
        self.spinCtrlStart = None
        self.spinCtrlStop = None
        self.choiceDisplay = None

        self.spectrum = OrderedDict()
        self.scanInfo = ScanInfo()
        self.locations = OrderedDict()
        self.lastLocation = [None] * 4

        self.isSaved = True

        self.settings = Settings()
        self.devicesRtl = get_devices_rtl(self.settings.devicesRtl)
        self.settings.indexRtl = limit(self.settings.indexRtl, 0,
                                       len(self.devicesRtl) - 1)
        self.filename = ""
        self.oldCal = 0

        self.remoteControl = None

        self.log = Log()

        self.pageConfig = wx.PageSetupDialogData()
        self.pageConfig.GetPrintData().SetOrientation(wx.LANDSCAPE)
        self.pageConfig.SetMarginTopLeft((20, 20))
        self.pageConfig.SetMarginBottomRight((20, 20))
        self.printConfig = wx.PrintDialogData(self.pageConfig.GetPrintData())
        self.printConfig.EnableSelection(False)
        self.printConfig.EnablePageNumbers(False)

        wx.Frame.__init__(self, None, title=title)

        self.timerGpsRetry = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.__on_gps_retry, self.timerGpsRetry)

        self.Bind(wx.EVT_CLOSE, self.__on_exit)

        self.status = Statusbar(self, self.log)
        self.status.set_info(title)
        self.SetStatusBar(self.status)

        add_colours()
        self.__create_widgets()
        self.__create_menu()
        self.__create_popup_menu()
        self.__set_control_state(True)
        self.Show()

        displaySize = wx.DisplaySize()
        toolbarSize = self.toolbar.GetBestSize()
        self.SetClientSize((toolbarSize[0] + 10, displaySize[1] / 2))
        self.SetMinSize((displaySize[0] / 4, displaySize[1] / 4))

        self.Connect(-1, -1, EVENT_THREAD, self.__on_event)

        self.SetDropTarget(DropTarget(self))

        self.SetIcon(load_icon('rtlsdr_scan'))

        self.steps = 0
        self.stepsTotal = 0

        self.__start_gps()
        self.__start_location_server()

    def __create_widgets(self):
        self.remoteControl = RemoteControl()

        self.graph = PanelGraph(self, self, self.settings, self.status,
                                self.remoteControl)
        self.toolbar = wx.Panel(self)

        self.buttonStart = MultiButton(self.toolbar, ['Start', 'Continue'],
                                       ['Start new scan', 'Continue scanning'])
        self.buttonStart.SetSelected(self.settings.startOption)
        self.buttonStop = MultiButton(self.toolbar, ['Stop', 'Stop at end'],
                                      ['Stop scan', 'Stop scan at end'])
        self.buttonStop.SetSelected(self.settings.stopOption)
        self.Bind(wx.EVT_BUTTON, self.__on_start, self.buttonStart)
        self.Bind(wx.EVT_BUTTON, self.__on_stop, self.buttonStop)

        textRange = wx.StaticText(self.toolbar,
                                  label="Range (MHz)",
                                  style=wx.ALIGN_CENTER)
        textStart = wx.StaticText(self.toolbar, label="Start")
        textStop = wx.StaticText(self.toolbar, label="Stop")

        self.spinCtrlStart = wx.SpinCtrl(self.toolbar)
        self.spinCtrlStop = wx.SpinCtrl(self.toolbar)
        self.spinCtrlStart.SetToolTipString('Start frequency')
        self.spinCtrlStop.SetToolTipString('Stop frequency')
        self.spinCtrlStart.SetRange(F_MIN, F_MAX - 1)
        self.spinCtrlStop.SetRange(F_MIN + 1, F_MAX)
        self.Bind(wx.EVT_SPINCTRL, self.__on_spin, self.spinCtrlStart)
        self.Bind(wx.EVT_SPINCTRL, self.__on_spin, self.spinCtrlStop)

        textGain = wx.StaticText(self.toolbar, label="Gain (dB)")
        self.controlGain = wx.Choice(self.toolbar, choices=[''])

        textMode = wx.StaticText(self.toolbar, label="Mode")
        self.choiceMode = wx.Choice(self.toolbar, choices=MODE[::2])
        self.choiceMode.SetToolTipString('Scanning mode')

        textDwell = wx.StaticText(self.toolbar, label="Dwell")
        self.choiceDwell = wx.Choice(self.toolbar, choices=DWELL[::2])
        self.choiceDwell.SetToolTipString('Scan time per step')

        textNfft = wx.StaticText(self.toolbar, label="FFT size")
        self.choiceNfft = wx.Choice(self.toolbar, choices=map(str, NFFT))
        self.choiceNfft.SetToolTipString('Higher values for greater'
                                         'precision')

        textDisplay = wx.StaticText(self.toolbar, label="Display")
        self.choiceDisplay = wx.Choice(self.toolbar, choices=DISPLAY[::2])
        self.Bind(wx.EVT_CHOICE, self.__on_choice, self.choiceDisplay)
        self.choiceDisplay.SetToolTipString('Spectrogram available in'
                                            'continuous mode')

        grid = wx.GridBagSizer(5, 5)
        grid.Add(self.buttonStart,
                 pos=(0, 0),
                 span=(3, 1),
                 flag=wx.ALIGN_CENTER)
        grid.Add(self.buttonStop,
                 pos=(0, 1),
                 span=(3, 1),
                 flag=wx.ALIGN_CENTER)
        grid.Add((20, 1), pos=(0, 2))
        grid.Add(textRange, pos=(0, 3), span=(1, 4), flag=wx.ALIGN_CENTER)
        grid.Add(textStart, pos=(1, 3), flag=wx.ALIGN_CENTER)
        grid.Add(self.spinCtrlStart, pos=(1, 4))
        grid.Add(textStop, pos=(1, 5), flag=wx.ALIGN_CENTER)
        grid.Add(self.spinCtrlStop, pos=(1, 6))
        grid.Add(textGain, pos=(0, 7), flag=wx.ALIGN_CENTER)
        grid.Add(self.controlGain, pos=(1, 7), flag=wx.ALIGN_CENTER)
        grid.Add((20, 1), pos=(0, 8))
        grid.Add(textMode, pos=(0, 9), flag=wx.ALIGN_CENTER)
        grid.Add(self.choiceMode, pos=(1, 9), flag=wx.ALIGN_CENTER)
        grid.Add(textDwell, pos=(0, 10), flag=wx.ALIGN_CENTER)
        grid.Add(self.choiceDwell, pos=(1, 10), flag=wx.ALIGN_CENTER)
        grid.Add(textNfft, pos=(0, 11), flag=wx.ALIGN_CENTER)
        grid.Add(self.choiceNfft, pos=(1, 11), flag=wx.ALIGN_CENTER)
        grid.Add((20, 1), pos=(0, 12))
        grid.Add(textDisplay, pos=(0, 13), flag=wx.ALIGN_CENTER)
        grid.Add(self.choiceDisplay, pos=(1, 13), flag=wx.ALIGN_CENTER)

        self.__set_controls()
        self.__set_gain_control()

        self.toolbar.SetSizer(grid)
        self.toolbar.Layout()

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.graph, 1, wx.EXPAND)
        sizer.Add(self.toolbar, 0, wx.EXPAND)
        self.SetSizer(sizer)
        self.Layout()

    def __create_menu(self):
        self.menuMain = MenuMain(self, self.settings)

        self.Bind(wx.EVT_MENU, self.__on_new, self.menuMain.new)
        self.Bind(wx.EVT_MENU, self.__on_open, self.menuMain.open)
        self.Bind(wx.EVT_MENU, self.__on_merge, self.menuMain.merge)
        self.Bind(wx.EVT_MENU_RANGE,
                  self.__on_file_history,
                  id=wx.ID_FILE1,
                  id2=wx.ID_FILE9)
        self.Bind(wx.EVT_MENU, self.__on_save, self.menuMain.save)
        self.Bind(wx.EVT_MENU, self.__on_export_scan, self.menuMain.exportScan)
        self.Bind(wx.EVT_MENU, self.__on_export_image,
                  self.menuMain.exportImage)
        self.Bind(wx.EVT_MENU, self.__on_export_image_seq,
                  self.menuMain.exportSeq)
        self.Bind(wx.EVT_MENU, self.__on_export_geo, self.menuMain.exportGeo)
        self.Bind(wx.EVT_MENU, self.__on_export_track,
                  self.menuMain.exportTrack)
        self.Bind(wx.EVT_MENU, self.__on_page, self.menuMain.page)
        self.Bind(wx.EVT_MENU, self.__on_preview, self.menuMain.preview)
        self.Bind(wx.EVT_MENU, self.__on_print, self.menuMain.printer)
        self.Bind(wx.EVT_MENU, self.__on_properties, self.menuMain.properties)
        self.Bind(wx.EVT_MENU, self.__on_exit, self.menuMain.close)
        self.Bind(wx.EVT_MENU, self.__on_pref, self.menuMain.pref)
        self.Bind(wx.EVT_MENU, self.__on_adv_pref, self.menuMain.advPref)
        self.Bind(wx.EVT_MENU, self.__on_formatting, self.menuMain.formatting)
        self.Bind(wx.EVT_MENU, self.__on_devices_rtl, self.menuMain.devicesRtl)
        self.Bind(wx.EVT_MENU, self.__on_devices_gps, self.menuMain.devicesGps)
        self.Bind(wx.EVT_MENU, self.__on_reset, self.menuMain.reset)
        self.Bind(wx.EVT_MENU, self.__on_clear_select,
                  self.menuMain.clearSelect)
        self.Bind(wx.EVT_MENU, self.__on_show_measure,
                  self.menuMain.showMeasure)
        self.Bind(wx.EVT_MENU, self.__on_start, self.menuMain.start)
        self.Bind(wx.EVT_MENU, self.__on_continue, self.menuMain.cont)
        self.Bind(wx.EVT_MENU, self.__on_stop, self.menuMain.stop)
        self.Bind(wx.EVT_MENU, self.__on_stop_end, self.menuMain.stopEnd)
        self.Bind(wx.EVT_MENU, self.__on_compare, self.menuMain.compare)
        self.Bind(wx.EVT_MENU, self.__on_smooth, self.menuMain.smooth)
        self.Bind(wx.EVT_MENU, self.__on_cal, self.menuMain.cal)
        self.Bind(wx.EVT_MENU, self.__on_gearth, self.menuMain.gearth)
        self.Bind(wx.EVT_MENU, self.__on_gmaps, self.menuMain.gmaps)
        self.Bind(wx.EVT_MENU, self.__on_sats, self.menuMain.sats)
        self.Bind(wx.EVT_MENU, self.__on_loc_clear, self.menuMain.locClear)
        self.Bind(wx.EVT_MENU, self.__on_log, self.menuMain.log)
        self.Bind(wx.EVT_MENU, self.__on_help, self.menuMain.helpLink)
        self.Bind(wx.EVT_MENU, self.__on_update, self.menuMain.update)
        self.Bind(wx.EVT_MENU, self.__on_sys_info, self.menuMain.sys)
        self.Bind(wx.EVT_MENU, self.__on_about, self.menuMain.about)

        idF1 = wx.wx.NewId()
        self.Bind(wx.EVT_MENU, self.__on_help, id=idF1)
        accelTable = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_F1, idF1)])
        self.SetAcceleratorTable(accelTable)

        self.Bind(wx.EVT_MENU_HIGHLIGHT, self.__on_menu_highlight)

        self.SetMenuBar(self.menuMain.menuBar)

    def __create_popup_menu(self):
        self.menuPopup = PopMenuMain(self.settings)

        self.Bind(wx.EVT_MENU, self.__on_start, self.menuPopup.start)
        self.Bind(wx.EVT_MENU, self.__on_continue, self.menuPopup.cont)
        self.Bind(wx.EVT_MENU, self.__on_stop, self.menuPopup.stop)
        self.Bind(wx.EVT_MENU, self.__on_stop_end, self.menuPopup.stopEnd)
        self.Bind(wx.EVT_MENU, self.__on_range_lim, self.menuPopup.rangeLim)
        self.Bind(wx.EVT_MENU, self.__on_points_lim, self.menuPopup.pointsLim)
        self.Bind(wx.EVT_MENU, self.__on_clear_select,
                  self.menuPopup.clearSelect)
        self.Bind(wx.EVT_MENU, self.__on_show_measure,
                  self.menuPopup.showMeasure)

        self.Bind(wx.EVT_CONTEXT_MENU, self.__on_popup_menu)

    def __on_menu_highlight(self, event):
        item = self.GetMenuBar().FindItemById(event.GetId())
        if item is not None:
            help = item.GetHelp()
        else:
            help = ''

        self.status.set_general(help, level=None)

    def __on_popup_menu(self, event):
        if not isinstance(event.GetEventObject(), NavigationToolbar):
            pos = event.GetPosition()
            pos = self.ScreenToClient(pos)
            self.PopupMenu(self.menuPopup.menu, pos)

    def __on_new(self, _event):
        if self.__save_warn(Warn.NEW):
            return True
        self.spectrum.clear()
        self.locations.clear()
        self.__saved(True)
        self.__set_plot(self.spectrum, False)
        self.graph.clear_selection()
        self.__set_control_state(True)
        return False

    def __on_open(self, _event):
        if self.__save_warn(Warn.OPEN):
            return

        dlg = wx.FileDialog(self, "Open a scan", self.settings.dirScans,
                            self.filename,
                            File.get_type_filters(File.Types.SAVE), wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.open(dlg.GetDirectory(), dlg.GetFilename())
        dlg.Destroy()

    def __on_merge(self, _event):
        if self.__save_warn(Warn.MERGE):
            return

        dlg = wx.FileDialog(self, "Merge a scan", self.settings.dirScans,
                            self.filename,
                            File.get_type_filters(File.Types.SAVE), wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.__merge(dlg.GetDirectory(), dlg.GetFilename())
        dlg.Destroy()

    def __on_file_history(self, event):
        selection = event.GetId() - wx.ID_FILE1
        path = self.settings.fileHistory.GetHistoryFile(selection)
        self.settings.fileHistory.AddFileToHistory(path)
        dirname, filename = os.path.split(path)
        self.open(dirname, filename)

    def __on_save(self, _event):
        dlg = wx.FileDialog(self, "Save a scan", self.settings.dirScans,
                            self.filename,
                            File.get_type_filters(File.Types.SAVE),
                            wx.SAVE | wx.OVERWRITE_PROMPT)
        if dlg.ShowModal() == wx.ID_OK:
            self.status.set_general("Saving...")
            fileName = dlg.GetFilename()
            dirName = dlg.GetDirectory()
            self.filename = os.path.splitext(fileName)[0]
            self.settings.dirScans = dirName
            fileName = extension_add(fileName, dlg.GetFilterIndex(),
                                     File.Types.SAVE)
            fullName = os.path.join(dirName, fileName)
            save_plot(fullName, self.scanInfo, self.spectrum, self.locations)
            self.__saved(True)
            self.status.set_general("Finished")
            self.settings.fileHistory.AddFileToHistory(fullName)
        dlg.Destroy()

    def __on_export_scan(self, _event):
        dlg = wx.FileDialog(self, "Export a scan", self.settings.dirExport,
                            self.filename, File.get_type_filters(),
                            wx.SAVE | wx.OVERWRITE_PROMPT)
        if dlg.ShowModal() == wx.ID_OK:
            self.status.set_general("Exporting...")
            fileName = dlg.GetFilename()
            dirName = dlg.GetDirectory()
            self.settings.dirExport = dirName
            fileName = extension_add(fileName, dlg.GetFilterIndex(),
                                     File.Types.PLOT)
            fullName = os.path.join(dirName, fileName)
            export_plot(fullName, dlg.GetFilterIndex(), self.spectrum)
            self.status.set_general("Finished")
        dlg.Destroy()

    def __on_export_image(self, _event):
        dlgFile = wx.FileDialog(self, "Export image to file",
                                self.settings.dirExport, self.filename,
                                File.get_type_filters(File.Types.IMAGE),
                                wx.SAVE | wx.OVERWRITE_PROMPT)
        dlgFile.SetFilterIndex(File.ImageType.PNG)
        if dlgFile.ShowModal() == wx.ID_OK:
            dlgImg = DialogImageSize(self, self.settings)
            if dlgImg.ShowModal() != wx.ID_OK:
                dlgFile.Destroy()
                return

            self.status.set_general("Exporting...")
            fileName = dlgFile.GetFilename()
            dirName = dlgFile.GetDirectory()
            self.settings.dirExport = dirName
            fileName = extension_add(fileName, dlgFile.GetFilterIndex(),
                                     File.Types.IMAGE)
            fullName = os.path.join(dirName, fileName)
            exportType = dlgFile.GetFilterIndex()
            export_image(fullName, exportType, self.graph.get_figure(),
                         self.settings)
            self.status.set_general("Finished")
        dlgFile.Destroy()

    def __on_export_image_seq(self, _event):
        dlgSeq = DialogExportSeq(self, self.spectrum, self.settings)
        dlgSeq.ShowModal()
        dlgSeq.Destroy()

    def __on_export_geo(self, _event):
        dlgGeo = DialogExportGeo(self, self.spectrum, self.locations,
                                 self.settings)
        if dlgGeo.ShowModal() == wx.ID_OK:
            self.status.set_general("Exporting...")
            extent = dlgGeo.get_extent()
            dlgFile = wx.FileDialog(self, "Export map to file",
                                    self.settings.dirExport, self.filename,
                                    File.get_type_filters(File.Types.GEO),
                                    wx.SAVE | wx.OVERWRITE_PROMPT)
            dlgFile.SetFilterIndex(File.GeoType.KMZ)
            if dlgFile.ShowModal() == wx.ID_OK:
                fileName = dlgFile.GetFilename()
                dirName = dlgFile.GetDirectory()
                self.settings.dirExport = dirName
                fileName = extension_add(fileName, dlgFile.GetFilterIndex(),
                                         File.Types.GEO)
                fullName = os.path.join(dirName, fileName)
                exportType = dlgFile.GetFilterIndex()
                image = None
                xyz = None
                if exportType == File.GeoType.CSV:
                    xyz = dlgGeo.get_xyz()
                else:
                    image = dlgGeo.get_image()
                export_map(fullName, exportType, extent, image, xyz)
            self.status.set_general("Finished")
            dlgFile.Destroy()
        dlgGeo.Destroy()

    def __on_export_track(self, _event):
        dlg = wx.FileDialog(self, "Export GPS to file",
                            self.settings.dirExport, self.filename,
                            File.get_type_filters(File.Types.TRACK),
                            wx.SAVE | wx.OVERWRITE_PROMPT)
        if dlg.ShowModal() == wx.ID_OK:
            self.status.set_general("Exporting...")
            fileName = dlg.GetFilename()
            dirName = dlg.GetDirectory()
            self.settings.dirExport = dirName
            fileName = extension_add(fileName, dlg.GetFilterIndex(),
                                     File.Types.TRACK)
            fullName = os.path.join(dirName, fileName)
            export_gpx(fullName, self.locations, self.GetName())
            self.status.set_general("Finished")
        dlg.Destroy()

    def __on_page(self, _event):
        dlg = wx.PageSetupDialog(self, self.pageConfig)
        if dlg.ShowModal() == wx.ID_OK:
            self.pageConfig = wx.PageSetupDialogData(
                dlg.GetPageSetupDialogData())
            self.printConfig.SetPrintData(self.pageConfig.GetPrintData())
        dlg.Destroy()

    def __on_preview(self, _event):
        printout = PrintOut(self.graph, self.filename, self.pageConfig)
        printoutPrinting = PrintOut(self.graph, self.filename, self.pageConfig)
        preview = wx.PrintPreview(printout, printoutPrinting, self.printConfig)
        frame = wx.PreviewFrame(preview, self, 'Print Preview')
        frame.Initialize()
        frame.SetSize(self.GetSize())
        frame.Show(True)

    def __on_print(self, _event):
        printer = wx.Printer(self.printConfig)
        printout = PrintOut(self.graph, self.filename, self.pageConfig)
        if printer.Print(self, printout, True):
            self.printConfig = wx.PrintDialogData(printer.GetPrintDialogData())
            self.pageConfig.SetPrintData(self.printConfig.GetPrintData())

    def __on_properties(self, _event):
        if len(self.spectrum) > 0:
            self.scanInfo.timeFirst = min(self.spectrum)
            self.scanInfo.timeLast = max(self.spectrum)

        dlg = DialogProperties(self, self.scanInfo)
        dlg.ShowModal()
        dlg.Destroy()

    def __on_exit(self, _event):
        self.Unbind(wx.EVT_CLOSE)
        if self.__save_warn(Warn.EXIT):
            self.Bind(wx.EVT_CLOSE, self.__on_exit)
            return
        self.__scan_stop(False)
        self.__stop_gps(False)
        self.__stop_location_server()
        self.__get_controls()
        self.settings.devicesRtl = self.devicesRtl
        self.settings.save()
        self.graph.close()
        self.Destroy()

    def __on_pref(self, _event):
        self.__get_controls()
        dlg = DialogPrefs(self, self.settings)
        if dlg.ShowModal() == wx.ID_OK:
            self.graph.create_plot()
            self.__set_control_state(True)
            self.__set_controls()
        dlg.Destroy()

    def __on_adv_pref(self, _event):
        dlg = DialogAdvPrefs(self, self.settings)
        if dlg.ShowModal() == wx.ID_OK:
            self.__set_control_state(True)
        dlg.Destroy()

    def __on_formatting(self, _event):
        dlg = DialogFormatting(self, self.settings)
        if dlg.ShowModal() == wx.ID_OK:
            self.__set_control_state(True)
            self.graph.update_measure()
            self.graph.redraw_plot()
        dlg.Destroy()

    def __on_devices_rtl(self, _event):
        self.__get_controls()
        self.devicesRtl = self.__refresh_devices()
        dlg = DialogDevicesRTL(self, self.devicesRtl, self.settings)
        if dlg.ShowModal() == wx.ID_OK:
            self.devicesRtl = dlg.get_devices()
            self.settings.indexRtl = dlg.get_index()
            self.__set_gain_control()
            self.__set_control_state(True)
            self.__set_controls()
        dlg.Destroy()

    def __on_devices_gps(self, _event):
        self.__stop_gps()
        self.status.set_gps('GPS Stopped')
        dlg = DialogDevicesGPS(self, self.settings)
        dlg.ShowModal()
        dlg.Destroy()
        self.__start_gps()

    def __on_reset(self, _event):
        dlg = wx.MessageDialog(
            self, 'Reset all settings to the default values\n'
            '(cannot be undone)?', 'Reset Settings',
            wx.YES_NO | wx.ICON_QUESTION)
        if dlg.ShowModal() == wx.ID_YES:
            self.devicesRtl = []
            self.settings.reset()
            self.__set_controls()
            self.graph.create_plot()
        dlg.Destroy()

    def __on_compare(self, _event):
        dlg = DialogCompare(self, self.settings, self.filename)
        dlg.Show()

    def __on_smooth(self, _event):
        dlg = DialogSmooth(self, self.spectrum, self.settings)
        if dlg.ShowModal() == wx.ID_OK:
            saved = self.isSaved
            self.isSaved = False
            if not self.__on_new(None):
                self.spectrum.clear()
                spectrum = dlg.get_spectrum()
                self.spectrum.update(OrderedDict(sorted(spectrum.items())))
                self.__set_plot(self.spectrum, False)
                self.graph.update_measure()
                self.graph.redraw_plot()
                self.__saved(False)
            else:
                self.__saved(saved)

    def __on_clear_select(self, _event):
        self.graph.clear_selection()

    def __on_show_measure(self, event):
        show = event.Checked()
        self.menuMain.showMeasure.Check(show)
        self.menuPopup.showMeasure.Check(show)
        self.settings.showMeasure = show
        self.graph.show_measure_table(show)
        self.Layout()

    def __on_cal(self, _event):
        self.dlgCal = DialogAutoCal(self, self.settings.calFreq,
                                    self.__auto_cal)
        self.dlgCal.ShowModal()

    def __on_gearth(self, _event):
        tempPath = tempfile.mkdtemp()
        tempFile = os.path.join(tempPath, 'RTLSDRScannerLink.kml')
        handle = open(tempFile, 'wb')
        create_gearth(handle)
        handle.close()

        if not run_file(tempFile):
            wx.MessageBox('Error starting Google Earth', 'Error',
                          wx.OK | wx.ICON_ERROR)

    def __on_gmaps(self, _event):
        url = 'http://localhost:{}/rtlsdr_scan.html'.format(LOCATION_PORT)
        webbrowser.open_new(url)

    def __on_sats(self, _event):
        if self.dlgSats is None:
            self.dlgSats = DialogSats(self)
            self.dlgSats.Show()

    def __on_loc_clear(self, _event):
        result = wx.MessageBox(
            'Remove {} locations from scan?'.format(len(self.locations)),
            'Clear location data', wx.YES_NO, self)
        if result == wx.YES:
            self.locations.clear()
            self.__set_control_state(True)

    def __on_log(self, _event):
        if self.dlgLog is None:
            self.dlgLog = DialogLog(self, self.log)
            self.dlgLog.Show()

    def __on_help(self, _event):
        webbrowser.open("http://eartoearoak.com/software/rtlsdr-scanner")

    def __on_update(self, _event):
        if self.threadUpdate is None:
            self.status.set_general("Checking for updates", level=None)
            self.threadUpdate = Thread(target=self.__update_check)
            self.threadUpdate.start()

    def __on_sys_info(self, _event):
        dlg = DialogSysInfo(self)
        dlg.ShowModal()
        dlg.Destroy()

    def __on_about(self, _event):
        dlg = DialogAbout(self)
        dlg.ShowModal()
        dlg.Destroy()

    def __on_spin(self, event):
        control = event.GetEventObject()
        if control == self.spinCtrlStart:
            self.spinCtrlStop.SetRange(self.spinCtrlStart.GetValue() + 1,
                                       F_MAX)

    def __on_choice(self, _event):
        self.__get_controls()
        self.graph.create_plot()

    def __on_start(self, event):
        self.__get_controls()

        if self.settings.start >= self.settings.stop:
            wx.MessageBox('Stop frequency must be greater that start',
                          'Warning', wx.OK | wx.ICON_WARNING)
            return

        self.devicesRtl = self.__refresh_devices()
        if len(self.devicesRtl) == 0:
            wx.MessageBox('No devices found', 'Error', wx.OK | wx.ICON_ERROR)
        else:
            if event.GetInt() == 0:
                self.isNewScan = True
            else:
                self.isNewScan = False
            self.__scan_start()
            if not self.settings.retainScans:
                self.status.set_info(
                    'Warning: Averaging is enabled in preferences',
                    level=Log.WARN)

    def __on_continue(self, event):
        event.SetInt(1)
        self.__on_start(event)

    def __on_stop(self, event):
        if event.GetInt() == 0:
            self.stopScan = True
            self.stopAtEnd = False
            self.__scan_stop()
        else:
            self.stopScan = False
            self.stopAtEnd = True

    def __on_stop_end(self, _event):
        self.stopAtEnd = True

    def __on_range_lim(self, _event):
        xmin, xmax = self.graph.get_axes().get_xlim()
        xmin = int(xmin)
        xmax = math.ceil(xmax)
        if xmax < xmin + 1:
            xmax = xmin + 1
        self.settings.start = xmin
        self.settings.stop = xmax
        self.__set_controls()

    def __on_points_lim(self, _event):
        self.settings.pointsLimit = self.menuPopup.pointsLim.IsChecked()
        self.__set_plot(self.spectrum, self.settings.annotate)

    def __on_gps_retry(self, _event):
        self.timerGpsRetry.Stop()
        self.__stop_gps()
        self.__start_gps()

    def __on_event(self, event):
        status = event.data.get_status()
        freq = event.data.get_arg1()
        data = event.data.get_arg2()
        if status == Event.STARTING:
            self.status.set_general("Starting")
            self.isScanning = True
        elif status == Event.STEPS:
            self.stepsTotal = (freq + 1) * 2
            self.steps = self.stepsTotal
            self.status.set_progress(0)
            self.status.show_progress()
        elif status == Event.CAL:
            self.__auto_cal(Cal.DONE)
        elif status == Event.INFO:
            if self.threadScan is not None:
                self.sdr = self.threadScan.get_sdr()
                if data is not None:
                    self.devicesRtl[self.settings.indexRtl].tuner = data
                    self.scanInfo.tuner = data
        elif status == Event.DATA:
            self.__saved(False)
            cal = self.devicesRtl[self.settings.indexRtl].calibration
            self.pool.apply_async(
                anaylse_data, (freq, data, cal, self.settings.nfft,
                               self.settings.overlap, self.settings.winFunc),
                callback=self.__on_process_done)
            self.__progress()
        elif status == Event.STOPPED:
            self.__cleanup()
            self.status.set_general("Stopped")
        elif status == Event.FINISHED:
            self.threadScan = None
        elif status == Event.ERROR:
            self.__cleanup()
            self.status.set_general("Error: {}".format(data), level=Log.ERROR)
            if self.dlgCal is not None:
                self.dlgCal.Destroy()
                self.dlgCal = None
        elif status == Event.PROCESSED:
            offset = self.settings.devicesRtl[self.settings.indexRtl].offset
            if self.settings.alert:
                alert = self.settings.alertLevel
            else:
                alert = None
            Thread(target=update_spectrum,
                   name='Update',
                   args=(self, self.lock, self.settings.start,
                         self.settings.stop, freq, data, offset, self.spectrum,
                         not self.settings.retainScans, alert)).start()
        elif status == Event.LEVEL:
            wx.Bell()
        elif status == Event.UPDATED:
            if data and self.settings.liveUpdate:
                self.__set_plot(
                    self.spectrum, self.settings.annotate
                    and self.settings.retainScans
                    and self.settings.mode == Mode.CONTIN)
            self.__progress()
        elif status == Event.DRAW:
            self.graph.draw()
        elif status == Event.VER_UPD:
            self.__update_checked(True, freq, data)
        elif status == Event.VER_NOUPD:
            self.__update_checked(False)
        elif status == Event.VER_UPDFAIL:
            self.__update_checked(failed=True)
        elif status == Event.LOC_WARN:
            self.status.set_gps("{}".format(data), level=Log.WARN)
            self.status.warn_gps()
        elif status == Event.LOC_ERR:
            self.status.set_gps("{}".format(data), level=Log.ERROR)
            self.status.error_gps()
            self.threadLocation = None
            if not self.timerGpsRetry.IsRunning():
                self.timerGpsRetry.Start(5000, True)
        elif status == Event.LOC:
            self.__update_location(data)
        elif status == Event.LOC_SAT:
            if self.dlgSats is not None:
                self.dlgSats.set_sats(data)

        wx.YieldIfNeeded()

    def __on_process_done(self, data):
        timeStamp, freq, scan = data
        post_event(self, EventThread(Event.PROCESSED, freq, (timeStamp, scan)))

    def __auto_cal(self, status):
        freq = self.dlgCal.get_arg1()
        if self.dlgCal is not None:
            if status == Cal.START:
                self.spinCtrlStart.SetValue(int(freq))
                self.spinCtrlStop.SetValue(math.ceil(freq))
                self.oldCal = self.devicesRtl[
                    self.settings.indexRtl].calibration
                self.devicesRtl[self.settings.indexRtl].calibration = 0
                self.__get_controls()
                self.spectrum.clear()
                self.locations.clear()
                if not self.__scan_start(isCal=True):
                    self.dlgCal.reset_cal()
            elif status == Cal.DONE:
                ppm = self.__calc_ppm(freq)
                self.dlgCal.set_cal(ppm)
                self.__set_control_state(True)
            elif status == Cal.OK:
                self.devicesRtl[self.settings.
                                indexRtl].calibration = self.dlgCal.get_cal()
                self.settings.calFreq = freq
                self.dlgCal = None
            elif status == Cal.CANCEL:
                self.dlgCal = None
                if len(self.devicesRtl) > 0:
                    self.devicesRtl[
                        self.settings.indexRtl].calibration = self.oldCal

    def __calc_ppm(self, freq):
        with self.lock:
            timeStamp = max(self.spectrum)
            spectrum = self.spectrum[timeStamp].copy()

            for x, y in spectrum.iteritems():
                spectrum[x] = (((x - freq) * (x - freq)) + 1) * y
                peak = max(spectrum, key=spectrum.get)

        return ((freq - peak) / freq) * 1e6

    def __scan_start(self, isCal=False):
        if self.isNewScan and self.__save_warn(Warn.SCAN):
            return False

        if not self.threadScan:
            self.__set_control_state(False)
            samples = calc_samples(self.settings.dwell)
            if self.isNewScan:
                self.spectrum.clear()
                self.locations.clear()
                self.graph.clear_plots()

                self.isNewScan = False
                self.status.set_info('', level=None)
                self.scanInfo.set_from_settings(self.settings)
                self.scanInfo.time = format_iso_time(time.time())
                self.scanInfo.lat = None
                self.scanInfo.lon = None
                self.scanInfo.desc = ''

            self.stopAtEnd = False
            self.stopScan = False
            self.threadScan = ThreadScan(self, self.sdr, self.settings,
                                         self.settings.indexRtl, samples,
                                         isCal)
            self.filename = "Scan {0:.1f}-{1:.1f}MHz".format(
                self.settings.start, self.settings.stop)
            self.graph.set_plot_title()

            self.__start_gps()

            return True

    def __scan_stop(self, join=True):
        if self.threadScan:
            self.status.set_general("Stopping")
            self.threadScan.abort()
            if join:
                self.threadScan.join()
        self.threadScan = None
        if self.sdr is not None:
            self.sdr.close()
        self.__set_control_state(True)

    def __progress(self):
        if self.steps == self.stepsTotal:
            self.status.set_general("Scanning ({} sweeps)".format(
                len(self.spectrum)))
        self.steps -= 1
        if self.steps > 0 and not self.stopScan:
            self.status.set_progress(
                (self.stepsTotal - self.steps) * 100.0 / (self.stepsTotal - 1))
            self.status.show_progress()
        else:
            self.status.hide_progress()
            self.__set_plot(self.spectrum, self.settings.annotate)
            if self.stopScan:
                self.status.set_general("Stopped")
                self.__cleanup()
            elif self.settings.mode == Mode.SINGLE:
                self.status.set_general("Finished")
                self.__cleanup()
            else:
                if self.settings.mode == Mode.CONTIN:
                    if self.dlgCal is None and not self.stopAtEnd:
                        self.__limit_spectrum()
                        self.__scan_start()
                    else:
                        self.status.set_general("Stopped")
                        self.__cleanup()

    def __cleanup(self):
        if self.sdr is not None:
            self.sdr.close()
            self.sdr = None

        self.status.hide_progress()
        self.steps = 0
        self.threadScan = None
        self.__set_control_state(True)
        self.stopAtEnd = False
        self.stopScan = True
        self.isScanning = False

    def __remove_last(self, data):
        while len(data) >= self.settings.retainMax:
            timeStamp = min(data)
            del data[timeStamp]

    def __limit_spectrum(self):
        with self.lock:
            self.__remove_last(self.spectrum)
            self.__remove_last(self.locations)

    def __start_gps(self):
        if self.settings.gps and len(self.settings.devicesGps):
            self.status.enable_gps()
            if self.threadLocation is None:
                device = self.settings.devicesGps[self.settings.indexGps]
                self.threadLocation = ThreadLocation(self, device)
        else:
            self.status.disable_gps()

    def __stop_gps(self, join=True):
        if self.threadLocation and self.threadLocation.isAlive():
            self.threadLocation.stop()
            if join:
                self.threadLocation.join()
        self.threadLocation = None

    def __start_location_server(self):
        self.serverLocation = LocationServer(self.locations, self.lastLocation,
                                             self.lock, self.log)

    def __stop_location_server(self):
        if self.serverLocation:
            self.serverLocation.close()

    def __update_location(self, data):
        i = 0
        for loc in data:
            self.lastLocation[i] = loc
            i += 1
        self.status.pulse_gps()
        if data[2] is None:
            gpsStatus = '{:.5f}, {:.5f}, {:.1f}'.format(data[0], data[1])
        else:
            gpsStatus = '{:.5f}, {:.5f}, {:.1f}m'.format(
                data[0], data[1], data[2])

        self.status.set_gps(gpsStatus, level=None)

        if not self.isScanning:
            return

        if self.scanInfo is not None:
            if data[0] and data[1]:
                self.scanInfo.lat = str(data[0])
                self.scanInfo.lon = str(data[1])

        with self.lock:
            if len(self.spectrum) > 0:
                self.locations[max(self.spectrum)] = (data[0], data[1],
                                                      data[2])

    def __saved(self, isSaved):
        self.isSaved = isSaved
        title = APP_NAME + " - " + self.filename
        if not isSaved:
            title += "*"
        self.SetTitle(title)

    def __set_plot(self, spectrum, annotate):
        if len(spectrum) > 0:
            total = count_points(spectrum)
            if total > 0:
                spectrum = sort_spectrum(spectrum)
                extent = Extent(spectrum)
                self.graph.set_plot(spectrum, self.settings.pointsLimit,
                                    self.settings.pointsMax, extent, annotate)
        else:
            self.graph.clear_plots()

    def __set_control_state(self, state):
        hasDevices = len(self.devicesRtl) > 0

        self.spinCtrlStart.Enable(state)
        self.spinCtrlStop.Enable(state)
        self.controlGain.Enable(state)
        self.choiceMode.Enable(state)
        self.choiceDwell.Enable(state)
        self.choiceNfft.Enable(state)
        self.buttonStart.Enable(state and hasDevices)
        self.buttonStop.Enable(not state and hasDevices)

        self.menuMain.set_state(state, self.spectrum, self.locations)
        self.menuPopup.set_state(state, self.spectrum)

    def __set_controls(self):
        self.spinCtrlStart.SetValue(self.settings.start)
        self.spinCtrlStop.SetValue(self.settings.stop)
        self.choiceMode.SetSelection(MODE[1::2].index(self.settings.mode))
        dwell = calc_real_dwell(self.settings.dwell)
        try:
            sel = DWELL[1::2].index(dwell)
        except ValueError:
            sel = DWELL[1::2][len(DWELL) / 4]
        self.choiceDwell.SetSelection(sel)
        self.choiceNfft.SetSelection(NFFT.index(self.settings.nfft))
        self.choiceDisplay.SetSelection(DISPLAY[1::2].index(
            self.settings.display))

    def __set_gain_control(self):
        grid = self.controlGain.GetContainingSizer()
        if len(self.devicesRtl) > 0:
            self.controlGain.Destroy()
            device = self.devicesRtl[self.settings.indexRtl]
            if device.isDevice:
                gains = device.get_gains_str()
                self.controlGain = wx.Choice(self.toolbar, choices=gains)
                gain = device.get_closest_gain_str(device.gain)
                self.controlGain.SetStringSelection(gain)
            else:
                self.controlGain = NumCtrl(self.toolbar,
                                           integerWidth=3,
                                           fractionWidth=1)
                font = self.controlGain.GetFont()
                dc = wx.WindowDC(self.controlGain)
                dc.SetFont(font)
                size = dc.GetTextExtent('####.#')
                self.controlGain.SetMinSize((size[0] * 1.2, -1))
                self.controlGain.SetValue(device.gain)

            grid.Add(self.controlGain, pos=(1, 7), flag=wx.ALIGN_CENTER)
            grid.Layout()

    def __get_controls(self):
        self.settings.start = self.spinCtrlStart.GetValue()
        self.settings.stop = self.spinCtrlStop.GetValue()
        self.settings.startOption = self.buttonStart.GetSelected()
        self.settings.stopOption = self.buttonStop.GetSelected()
        self.settings.mode = MODE[1::2][self.choiceMode.GetSelection()]
        self.settings.dwell = DWELL[1::2][self.choiceDwell.GetSelection()]
        self.settings.nfft = NFFT[self.choiceNfft.GetSelection()]
        self.settings.display = DISPLAY[1::2][
            self.choiceDisplay.GetSelection()]

        if len(self.devicesRtl) > 0:
            device = self.devicesRtl[self.settings.indexRtl]
            try:
                if device.isDevice:
                    device.gain = float(self.controlGain.GetStringSelection())
                else:
                    device.gain = self.controlGain.GetValue()
            except ValueError:
                device.gain = 0

    def __save_warn(self, warnType):
        if self.settings.saveWarn and not self.isSaved:
            dlg = DialogSaveWarn(self, warnType)
            code = dlg.ShowModal()
            if code == wx.ID_YES:
                self.__on_save(None)
                if self.isSaved:
                    return False
                else:
                    return True
            elif code == wx.ID_NO:
                return False
            else:
                return True

        return False

    def __update_check(self):
        local = get_version_timestamp(True)
        try:
            remote = get_version_timestamp_repo()
        except IOError:
            post_event(self, EventThread(Event.VER_UPDFAIL))
            return

        if remote > local:
            post_event(self, EventThread(Event.VER_UPD, local, remote))
        else:
            post_event(self, EventThread(Event.VER_NOUPD))

    def __update_checked(self,
                         updateFound=False,
                         local=None,
                         remote=None,
                         failed=False):
        self.threadUpdate = None
        self.status.set_general("", level=None)
        if failed:
            icon = wx.ICON_ERROR
            message = "Update check failed"
        else:
            icon = wx.ICON_INFORMATION
            if updateFound:
                message = "Update found\n\n"
                message += "Local: " + time.strftime('%c',
                                                     time.localtime(local))
                message += "\nRemote: " + time.strftime(
                    '%c', time.localtime(remote))
            else:
                message = "No updates found"

        dlg = wx.MessageDialog(self, message, "Update", wx.OK | icon)
        dlg.ShowModal()
        dlg.Destroy()

    def __refresh_devices(self):
        self.settings.devicesRtl = get_devices_rtl(self.devicesRtl,
                                                   self.status)
        self.settings.indexRtl = limit(self.settings.indexRtl, 0,
                                       len(self.devicesRtl) - 1)
        self.settings.save()
        return self.settings.devicesRtl

    def __merge(self, dirname, filename):
        if not os.path.exists(os.path.join(dirname, filename)):
            wx.MessageBox('File not found', 'Error', wx.OK | wx.ICON_ERROR)
            return

        self.filename = os.path.splitext(filename)[0]
        self.settings.dirScans = dirname
        self.status.set_general("Merging: {}".format(filename))

        _scanInfo, spectrum, locations = open_plot(dirname, filename)

        if len(spectrum) > 0:
            spectrum.update(self.spectrum)
            locations.update(self.locations)
            self.spectrum.clear()
            self.locations.clear()
            self.spectrum.update(OrderedDict(sorted(spectrum.items())))
            self.locations.update(OrderedDict(sorted(locations.items())))
            self.__set_plot(self.spectrum, self.settings.annotate)
            self.graph.scale_plot(True)
            self.status.set_general("Finished")
            self.settings.fileHistory.AddFileToHistory(
                os.path.join(dirname, filename))
        else:
            self.status.set_general("Merge failed", level=Log.ERROR)

    def open(self, dirname, filename):
        if not os.path.exists(os.path.join(dirname, filename)):
            wx.MessageBox('File not found', 'Error', wx.OK | wx.ICON_ERROR)
            return

        self.__on_new(None)
        self.graph.get_canvas().draw()

        self.filename = os.path.splitext(filename)[0]
        self.settings.dirScans = dirname
        self.status.set_general("Opening: {}".format(filename))

        self.scanInfo, spectrum, location = open_plot(dirname, filename)

        if len(spectrum) > 0:
            self.scanInfo.set_to_settings(self.settings)
            self.spectrum = spectrum
            self.locations.clear()
            self.locations.update(location)
            self.__saved(True)
            self.__set_controls()
            self.__set_control_state(True)
            self.__set_plot(spectrum, self.settings.annotate)
            self.graph.scale_plot(True)
            self.status.set_general("Finished")
            self.settings.fileHistory.AddFileToHistory(
                os.path.join(dirname, filename))
        else:
            self.status.set_general("Open failed", level=Log.ERROR)