F_MIN = 0 F_MAX = 9999 MODE = ["Single", 0,
        "Continuous", 1,
        "Maximum", 2] NFFT = [16,
        32,
        64,
        128,
        256,
        512,
        1024,
        2048,
        4096,
        8192,
        16384,
        32768] DISPLAY = ["Plot", 0,
           "Spectrogram", 1,
           "3D Spectrogram", 2,
           "Status", 3,
           "Time Line", 4,
           "Preview", 5] class Warn(object):
    SCAN, OPEN, EXIT, NEW, MERGE = range(5) class Cal(object):
    START, DONE, OK, CANCEL = range(4) class Mode(object):
    SINGLE, CONTIN, MAX = range(3) APP_NAME = 'RTLSDR Scanner' LOCATION_PORT = 7786 def get_devices_rtl(currentDevices=None, statusBar=None):
    if statusBar is not None:
        statusBar.set_general("Refreshing device list...")

    if currentDevices is None:
        currentDevices = []

    devices = []
    count = rtlsdr.librtlsdr.rtlsdr_get_device_count()

    for dev in range(0, count):
        device = DeviceRTL()
        device.indexRtl = dev
        device.name = format_device_rtl_name(rtlsdr.librtlsdr.rtlsdr_get_device_name(dev))
        buffer1 = (c_ubyte * 256)()
        buffer2 = (c_ubyte * 256)()
        serial = (c_ubyte * 256)()
        rtlsdr.librtlsdr.rtlsdr_get_device_usb_strings(dev, buffer1, buffer2,
                                                       serial)
        device.serial = string_at(serial)
        try:
            sdr = rtlsdr.RtlSdr(dev)
        except IOError:
            continue
        device.gains = sdr.valid_gains_db
        device.calibration = 0.0
        device.lo = 0.0
        for conf in currentDevices:
            if conf.isDevice and device.name == conf.name and device.serial == conf.serial:
                device.set(conf)

        devices.append(device)

    for conf in currentDevices:
        if not conf.isDevice:
            devices.append(conf)

    if statusBar is not None:
        statusBar.set_general("")

    return devices class DialogDevicesRTL(wx.Dialog):
    COLS = 10
    COL_SEL, COL_DEV, COL_TUN, COL_SER, COL_IND, \
        COL_GAIN, COL_CAL, COL_LEVOFF, COL_LO, COL_OFF = range(COLS)

    def __init__(self, parent, devices, settings):
        self.devices = copy.copy(devices)
        self.settings = settings
        self.index = None

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

        self.gridDev = grid.Grid(self)
        self.gridDev.CreateGrid(len(self.devices), self.COLS)
        self.gridDev.SetRowLabelSize(0)
        self.gridDev.SetColLabelValue(self.COL_SEL, "Selected")
        self.gridDev.SetColLabelValue(self.COL_DEV, "Device")
        self.gridDev.SetColLabelValue(self.COL_TUN, "Tuner")
        self.gridDev.SetColLabelValue(self.COL_SER, "Serial Number")
        self.gridDev.SetColLabelValue(self.COL_IND, "Index")
        self.gridDev.SetColLabelValue(self.COL_GAIN, "Gain\n(dB)")
        self.gridDev.SetColLabelValue(self.COL_CAL, "Frequency\nCalibration\n(ppm)")
        self.gridDev.SetColLabelValue(self.COL_LEVOFF, "Level\nOffset\n(dB)")
        self.gridDev.SetColLabelValue(self.COL_LO, "LO\n(MHz)")
        self.gridDev.SetColLabelValue(self.COL_OFF, "Band Offset\n(kHz)")
        self.gridDev.SetColFormatFloat(self.COL_GAIN, -1, 1)
        self.gridDev.SetColFormatFloat(self.COL_CAL, -1, 3)
        self.gridDev.SetColFormatFloat(self.COL_LEVOFF, -1, 2)
        self.gridDev.SetColFormatFloat(self.COL_LO, -1, 3)
        self.gridDev.SetColFormatFloat(self.COL_OFF, -1, 0)

        dc = wx.ScreenDC()
        dc.SetFont(self.gridDev.GetLabelFont())
        maxHeight = 0
        for i in range(self.COLS - 1):
            _w, h, _hl = dc.GetMultiLineTextExtent(self.gridDev.GetColLabelValue(i))
            if h > maxHeight:
                maxHeight = h
        self.gridDev.SetColLabelSize(maxHeight * 1.25)

        self.__set_dev_grid()
        self.Bind(grid.EVT_GRID_CELL_LEFT_CLICK, self.__on_click)

        serverSizer = wx.BoxSizer(wx.HORIZONTAL)
        buttonAdd = wx.Button(self, wx.ID_ADD)
        self.buttonDel = wx.Button(self, wx.ID_DELETE)
        self.Bind(wx.EVT_BUTTON, self.__on_add, buttonAdd)
        self.Bind(wx.EVT_BUTTON, self.__on_del, self.buttonDel)
        serverSizer.Add(buttonAdd, 0, wx.ALL)
        serverSizer.Add(self.buttonDel, 0, wx.ALL)
        self.__set_button_state()

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

        self.devbox = wx.BoxSizer(wx.VERTICAL)
        self.devbox.Add(self.gridDev, 1, wx.ALL | wx.EXPAND, 10)
        self.devbox.Add(serverSizer, 0, wx.ALL | wx.EXPAND, 10)
        self.devbox.Add(sizerButtons, 0, wx.ALL | wx.EXPAND, 10)

        self.SetSizerAndFit(self.devbox)

    def __set_dev_grid(self):
        colourBackground = self.gridDev.GetLabelBackgroundColour()
        attributes = grid.GridCellAttr()
        attributes.SetBackgroundColour(colourBackground)
        self.gridDev.SetColAttr(self.COL_IND, attributes)

        self.gridDev.ClearGrid()

        i = 0
        for device in self.devices:
            self.gridDev.SetReadOnly(i, self.COL_SEL, True)
            self.gridDev.SetReadOnly(i, self.COL_DEV, device.isDevice)
            self.gridDev.SetReadOnly(i, self.COL_TUN, True)
            self.gridDev.SetReadOnly(i, self.COL_SER, True)
            self.gridDev.SetReadOnly(i, self.COL_IND, True)
            self.gridDev.SetCellRenderer(i, self.COL_SEL,
                                         TickCellRenderer())
            if device.isDevice:
                cell = grid.GridCellChoiceEditor(map(str, device.gains),
                                                 allowOthers=False)
                self.gridDev.SetCellEditor(i, self.COL_GAIN, cell)
            self.gridDev.SetCellEditor(i, self.COL_CAL,
                                       grid.GridCellFloatEditor(-1, 3))
            self.gridDev.SetCellEditor(i, self.COL_LEVOFF,
                                       grid.GridCellFloatEditor(-1, 3))
            self.gridDev.SetCellEditor(i, self.COL_LO,
                                       grid.GridCellFloatEditor(-1, 3))
            if device.isDevice:
                self.gridDev.SetCellValue(i, self.COL_DEV, device.name)
                self.gridDev.SetCellValue(i, self.COL_SER, str(device.serial))
                self.gridDev.SetCellValue(i, self.COL_IND, str(i))
                self.gridDev.SetCellBackgroundColour(i, self.COL_DEV,
                                                     colourBackground)
                self.gridDev.SetCellValue(i, self.COL_GAIN,
                                          str(nearest(device.gain,
                                                      device.gains)))
            else:
                self.gridDev.SetCellValue(i, self.COL_DEV,
                                          '{}:{}'.format(device.server,
                                                         device.port))
                self.gridDev.SetCellValue(i, self.COL_SER, '')
                self.gridDev.SetCellValue(i, self.COL_IND, '')
                self.gridDev.SetCellValue(i, self.COL_GAIN, str(device.gain))
            self.gridDev.SetCellBackgroundColour(i, self.COL_SER,
                                                 colourBackground)

            self.gridDev.SetCellValue(i, self.COL_TUN, TUNER[device.tuner])
            self.gridDev.SetCellValue(i, self.COL_CAL, str(device.calibration))
            self.gridDev.SetCellValue(i, self.COL_LEVOFF, str(device.levelOff))
            self.gridDev.SetCellValue(i, self.COL_LO, str(device.lo))
            self.gridDev.SetCellValue(i, self.COL_OFF, str(device.offset / 1e3))
            i += 1

        if self.settings.indexRtl >= len(self.devices):
            self.settings.indexRtl = len(self.devices) - 1
        self.__select_row(self.settings.indexRtl)
        self.index = self.settings.indexRtl

        self.gridDev.AutoSize()

    def __get_dev_grid(self):
        i = 0
        for device in self.devices:
            if not device.isDevice:
                server = self.gridDev.GetCellValue(i, self.COL_DEV)
                server = '//' + server
                url = urlparse(server)
                if url.hostname is not None:
                    device.server = url.hostname
                else:
                    device.server = 'localhost'
                if url.port is not None:
                    device.port = url.port
                else:
                    device.port = 1234
            device.gain = float(self.gridDev.GetCellValue(i, self.COL_GAIN))
            device.calibration = float(self.gridDev.GetCellValue(i, self.COL_CAL))
            device.levelOff = float(self.gridDev.GetCellValue(i, self.COL_LEVOFF))
            device.lo = float(self.gridDev.GetCellValue(i, self.COL_LO))
            device.offset = float(self.gridDev.GetCellValue(i, self.COL_OFF)) * 1e3
            i += 1

    def __set_button_state(self):
        if len(self.devices) > 0:
            self.buttonDel.Enable()
        else:
            self.buttonDel.Disable()
        if len(self.devices) == 1:
            self.__select_row(0)

    def __warn_duplicates(self):
        servers = []
        for device in self.devices:
            if not device.isDevice:
                servers.append("{}:{}".format(device.server, device.port))

        dupes = set(servers)
        if len(dupes) != len(servers):
            message = "Duplicate server found:\n'{}'".format(dupes.pop())
            dlg = wx.MessageDialog(self, message, "Warning",
                                   wx.OK | wx.ICON_WARNING)
            dlg.ShowModal()
            dlg.Destroy()
            return True

        return False

    def __on_click(self, event):
        col = event.GetCol()
        index = event.GetRow()
        if col == self.COL_SEL:
            self.index = event.GetRow()
            self.__select_row(index)
        elif col == self.COL_OFF:
            device = self.devices[index]
            dlg = DialogOffset(self, device,
                               float(self.gridDev.GetCellValue(index,
                                                               self.COL_OFF)),
                               self.settings.winFunc)
            if dlg.ShowModal() == wx.ID_OK:
                self.gridDev.SetCellValue(index, self.COL_OFF,
                                          str(dlg.get_offset()))
            dlg.Destroy()
        else:
            self.gridDev.ForceRefresh()
            event.Skip()

        self.__set_button_state()

    def __on_add(self, _event):
        device = DeviceRTL()
        device.isDevice = False
        self.devices.append(device)
        self.gridDev.AppendRows(1)
        self.__set_dev_grid()
        self.SetSizerAndFit(self.devbox)
        self.__set_button_state()

    def __on_del(self, _event):
        del self.devices[self.index]
        self.gridDev.DeleteRows(self.index)
        self.__set_dev_grid()
        self.SetSizerAndFit(self.devbox)
        self.__set_button_state()

    def __on_ok(self, _event):
        self.__get_dev_grid()
        if self.__warn_duplicates():
            return
        self.EndModal(wx.ID_OK)

    def __select_row(self, index):
        self.gridDev.ClearSelection()
        for i in range(0, len(self.devices)):
            tick = "0"
            if i == index:
                tick = "1"
            self.gridDev.SetCellValue(i, self.COL_SEL, tick)

    def get_index(self):
        return self.index

    def get_devices(self):
        return self.devices class DialogDevicesGPS(wx.Dialog):
    COL_SEL, COL_NAME, COL_TYPE, COL_HOST, COL_TEST = range(5)

    def __init__(self, parent, settings):
        self.settings = settings
        self.index = settings.indexGps
        self.devices = copy.copy(settings.devicesGps)
        self.comboType = None

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

        self.checkGps = wx.CheckBox(self, wx.ID_ANY, "Enable GPS")
        self.checkGps.SetToolTipString('Record GPS locations in scans')
        self.checkGps.SetValue(settings.gps)
        self.Bind(wx.EVT_CHECKBOX, self.__on_check, self.checkGps)

        self.checkGpsRetry = wx.CheckBox(self, wx.ID_ANY, "Retry after disconnection")
        self.checkGpsRetry.SetToolTipString('Retry GPS if disconnected')
        self.checkGpsRetry.SetValue(settings.gpsRetry)
        self.checkGpsRetry.Enable(settings.gps)

        self.gridDev = grid.Grid(self)
        self.gridDev.CreateGrid(len(self.devices), 5)
        self.gridDev.SetRowLabelSize(0)
        self.gridDev.SetColLabelValue(self.COL_SEL, "Selected")
        self.gridDev.SetColLabelValue(self.COL_NAME, "Name")
        self.gridDev.SetColLabelValue(self.COL_HOST, "Host")
        self.gridDev.SetColLabelValue(self.COL_TYPE, "Type")
        self.gridDev.SetColLabelValue(self.COL_TEST, "Test")

        try:
            self.gridDev.ShowScrollbars(wx.SHOW_SB_NEVER, wx.SHOW_SB_NEVER)
        except AttributeError:
            pass

        self.__set_dev_grid()

        sizerDevice = wx.BoxSizer(wx.HORIZONTAL)
        buttonAdd = wx.Button(self, wx.ID_ADD)
        self.buttonDel = wx.Button(self, wx.ID_DELETE)
        self.Bind(wx.EVT_BUTTON, self.__on_add, buttonAdd)
        self.Bind(wx.EVT_BUTTON, self.__on_del, self.buttonDel)
        sizerDevice.Add(buttonAdd, 0, wx.ALL)
        sizerDevice.Add(self.buttonDel, 0, wx.ALL)
        self.__set_button_state()

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

        self.devbox = wx.BoxSizer(wx.VERTICAL)
        self.devbox.Add(self.checkGps, 0, wx.ALL | wx.EXPAND, 10)
        self.devbox.Add(self.checkGpsRetry, 0, wx.ALL | wx.EXPAND, 10)
        self.devbox.Add(self.gridDev, 1, wx.ALL | wx.EXPAND, 10)
        self.devbox.Add(sizerDevice, 0, wx.ALL | wx.EXPAND, 10)
        self.devbox.Add(sizerButtons, 0, wx.ALL | wx.EXPAND, 10)

        self.SetSizerAndFit(self.devbox)

    def __set_dev_grid(self):
        self.gridDev.Unbind(grid.EVT_GRID_EDITOR_CREATED)
        self.Unbind(grid.EVT_GRID_CELL_LEFT_CLICK)
        self.Unbind(grid.EVT_GRID_CELL_CHANGE)
        self.gridDev.ClearGrid()

        i = 0
        for device in self.devices:
            self.gridDev.SetReadOnly(i, self.COL_SEL, True)
            self.gridDev.SetCellRenderer(i, self.COL_SEL,
                                         TickCellRenderer())
            self.gridDev.SetCellValue(i, self.COL_NAME, device.name)
            cell = grid.GridCellChoiceEditor(sorted(DeviceGPS.TYPE),
                                             allowOthers=False)
            self.gridDev.SetCellValue(i, self.COL_TYPE,
                                      DeviceGPS.TYPE[device.type])
            self.gridDev.SetCellEditor(i, self.COL_TYPE, cell)

            if device.type == DeviceGPS.NMEA_SERIAL:
                self.gridDev.SetCellValue(i, self.COL_HOST,
                                          device.get_serial_desc())
                self.gridDev.SetReadOnly(i, self.COL_HOST, True)
            else:
                self.gridDev.SetCellValue(i, self.COL_HOST, device.resource)
                self.gridDev.SetReadOnly(i, self.COL_HOST, False)

            self.gridDev.SetCellValue(i, self.COL_TEST, '...')
            self.gridDev.SetCellAlignment(i, self.COL_SEL,
                                          wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
            i += 1

        self.index = limit(self.index, 0, len(self.devices) - 1)
        self.__select_row(self.index)
        self.index = self.index

        self.gridDev.AutoSize()
        font = self.gridDev.GetFont()
        dc = wx.WindowDC(self.gridDev)
        dc.SetFont(font)
        width, _height = dc.GetTextExtent(max(DeviceGPS.TYPE, key=len))
        self.gridDev.SetColSize(self.COL_TYPE, width * 1.5)

        self.gridDev.Bind(grid.EVT_GRID_EDITOR_CREATED, self.__on_create)
        self.Bind(grid.EVT_GRID_CELL_LEFT_CLICK, self.__on_click)
        self.Bind(grid.EVT_GRID_CELL_CHANGE, self.__on_change)

    def __set_button_state(self):
        if len(self.devices) > 0:
            self.buttonDel.Enable()
        else:
            self.buttonDel.Disable()
        if len(self.devices) == 1:
            self.__select_row(0)

    def __warn_duplicates(self):
        devices = []
        for device in self.devices:
            devices.append(device.name)

        dupes = set(devices)
        if len(dupes) != len(devices):
            message = "Duplicate name found:\n'{}'".format(dupes.pop())
            dlg = wx.MessageDialog(self, message, "Warning",
                                   wx.OK | wx.ICON_WARNING)
            dlg.ShowModal()
            dlg.Destroy()
            return True

        return False

    def __on_create(self, event):
        col = event.GetCol()
        index = event.GetRow()
        device = self.devices[index]
        if col == self.COL_TYPE:
            self.comboType = event.GetControl()
            self.comboType.Bind(wx.EVT_COMBOBOX,
                                lambda event,
                                device=device: self.__on_type(event, device))
        event.Skip()

    def __on_check(self, _event):
        self.checkGpsRetry.Enable(self.checkGps.GetValue())

    def __on_click(self, event):
        col = event.GetCol()
        index = event.GetRow()
        device = self.devices[index]
        if col == self.COL_SEL:
            self.index = event.GetRow()
            self.__select_row(index)
        elif col == self.COL_HOST:
            if device.type == DeviceGPS.NMEA_SERIAL:
                dlg = DialogGPSSerial(self, device)
                dlg.ShowModal()
                dlg.Destroy()
                self.gridDev.SetCellValue(index, self.COL_HOST,
                                          device.get_serial_desc())
            else:
                event.Skip()

        elif col == self.COL_TEST:
            dlg = DialogGPSTest(self, device)
            dlg.ShowModal()
            dlg.Destroy()
        else:
            self.gridDev.ForceRefresh()
            event.Skip()

    def __on_change(self, event):
        col = event.GetCol()
        index = event.GetRow()
        device = self.devices[index]
        if col == self.COL_NAME:
            device.name = self.gridDev.GetCellValue(index, self.COL_NAME)
        elif col == self.COL_TYPE:
            device.type = DeviceGPS.TYPE.index(self.gridDev.GetCellValue(index,
                                                                         self.COL_TYPE))
            self.__set_dev_grid()
            self.SetSizerAndFit(self.devbox)
            event.Skip()
        elif col == self.COL_HOST:
            if device.type != DeviceGPS.NMEA_SERIAL:
                device.resource = self.gridDev.GetCellValue(index,
                                                            self.COL_HOST)

    def __on_type(self, event, device):
        device.type = DeviceGPS.TYPE.index(event.GetString())
        if device.type == DeviceGPS.NMEA_SERIAL:
            device.resource = get_serial_ports()[0]
        elif device.type == DeviceGPS.NMEA_TCP:
            device.resource = 'localhost:10110'
        else:
            device.resource = 'localhost:2947'

    def __on_add(self, _event):
        device = DeviceGPS()
        self.devices.append(device)
        self.gridDev.AppendRows(1)
        self.__set_dev_grid()
        self.SetSizerAndFit(self.devbox)
        self.__set_button_state()

    def __on_del(self, _event):
        del self.devices[self.index]
        self.gridDev.DeleteRows(self.index)
        self.__set_dev_grid()
        self.SetSizerAndFit(self.devbox)
        self.__set_button_state()

    def __on_ok(self, _event):
        if self.__warn_duplicates():
            return

        self.settings.gps = self.checkGps.GetValue()
        self.settings.gpsRetry = self.checkGpsRetry.GetValue()
        self.settings.devicesGps = self.devices
        if len(self.devices) == 0:
            self.index = -1
        self.settings.indexGps = self.index
        self.EndModal(wx.ID_OK)

    def __select_row(self, index):
        self.index = index
        self.gridDev.ClearSelection()
        for i in range(0, len(self.devices)):
            tick = "0"
            if i == index:
                tick = "1"
            self.gridDev.SetCellValue(i, self.COL_SEL, tick) 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) class DialogExportSeq(wx.Dialog):
    POLL = 250

    def __init__(self, parent, spectrum, settings):
        self.spectrum = spectrum
        self.settings = settings
        self.sweeps = None
        self.isExporting = False

        wx.Dialog.__init__(self, parent=parent, title='Export Plot Sequence')

        self.queue = Queue.Queue()
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.__on_timer, self.timer)
        self.timer.Start(self.POLL)

        self.figure = matplotlib.figure.Figure(facecolor='white')
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.plot = Plotter(self.queue, self.figure, settings)

        textPlot = wx.StaticText(self, label='Plot')

        self.checkAxes = wx.CheckBox(self, label='Axes')
        self.checkAxes.SetValue(True)
        self.Bind(wx.EVT_CHECKBOX, self.__on_axes, self.checkAxes)
        self.checkGrid = wx.CheckBox(self, label='Grid')
        self.checkGrid.SetValue(True)
        self.Bind(wx.EVT_CHECKBOX, self.__on_grid, self.checkGrid)
        self.checkBar = wx.CheckBox(self, label='Bar')
        self.checkBar.SetValue(True)
        self.Bind(wx.EVT_CHECKBOX, self.__on_bar, self.checkBar)

        sizerCheck = wx.BoxSizer(wx.HORIZONTAL)
        sizerCheck.Add(self.checkAxes, flag=wx.ALL, border=5)
        sizerCheck.Add(self.checkGrid, flag=wx.ALL, border=5)
        sizerCheck.Add(self.checkBar, flag=wx.ALL, border=5)

        textRange = wx.StaticText(self, label='Range')

        self.sweepTimeStamps = sorted([timeStamp for timeStamp in spectrum.keys()])
        sweepChoices = [format_time(timeStamp, True) for timeStamp in self.sweepTimeStamps]

        textStart = wx.StaticText(self, label="Start")
        self.choiceStart = wx.Choice(self, choices=sweepChoices)
        self.choiceStart.SetSelection(0)
        self.Bind(wx.EVT_CHOICE, self.__on_choice, self.choiceStart)

        textEnd = wx.StaticText(self, label="End")
        self.choiceEnd = wx.Choice(self, choices=sweepChoices)
        self.choiceEnd.SetSelection(len(self.sweepTimeStamps) - 1)
        self.Bind(wx.EVT_CHOICE, self.__on_choice, self.choiceEnd)

        textSweeps = wx.StaticText(self, label='Sweeps')
        self.textSweeps = wx.StaticText(self, label="")

        textOutput = wx.StaticText(self, label='Output')

        self.textSize = wx.StaticText(self)
        buttonSize = wx.Button(self, label='Change...')
        buttonSize.SetToolTipString('Change exported image size')
        self.Bind(wx.EVT_BUTTON, self.__on_imagesize, buttonSize)
        self.__show_image_size()

        buttonBrowse = wx.Button(self, label='Browse...')
        self.Bind(wx.EVT_BUTTON, self.__on_browse, buttonBrowse)

        self.editDir = wx.TextCtrl(self)
        self.editDir.SetValue(settings.dirExport)

        font = textPlot.GetFont()
        fontSize = font.GetPointSize()
        font.SetPointSize(fontSize + 4)
        textPlot.SetFont(font)
        textRange.SetFont(font)
        textOutput.SetFont(font)

        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)

        sizerGrid = wx.GridBagSizer(5, 5)
        sizerGrid.Add(self.canvas, pos=(0, 0), span=(10, 6),
                      flag=wx.EXPAND | wx.ALL, border=5)
        sizerGrid.Add(textPlot, pos=(0, 7),
                      flag=wx.TOP | wx.BOTTOM, border=5)
        sizerGrid.Add(sizerCheck, pos=(1, 7), span=(1, 2),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(textRange, pos=(2, 7),
                      flag=wx.TOP | wx.BOTTOM, border=5)
        sizerGrid.Add(textStart, pos=(3, 7),
                      flag=wx.ALIGN_CENTRE_VERTICAL | wx.ALL, border=5)
        sizerGrid.Add(self.choiceStart, pos=(3, 8),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(textEnd, pos=(4, 7),
                      flag=wx.ALIGN_CENTRE_VERTICAL | wx.ALL, border=5)
        sizerGrid.Add(self.choiceEnd, pos=(4, 8),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(textSweeps, pos=(5, 7),
                      flag=wx.ALIGN_CENTRE_VERTICAL | wx.ALL, border=5)
        sizerGrid.Add(self.textSweeps, pos=(5, 8),
                      flag=wx.ALIGN_CENTRE_VERTICAL | wx.ALL, border=5)
        sizerGrid.Add(textOutput, pos=(6, 7),
                      flag=wx.TOP | wx.BOTTOM, border=5)
        sizerGrid.Add(self.textSize, pos=(7, 7),
                      flag=wx.ALIGN_CENTRE_VERTICAL | wx.ALL, border=5)
        sizerGrid.Add(buttonSize, pos=(7, 8),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(self.editDir, pos=(8, 7), span=(1, 2),
                      flag=wx.ALL | wx.EXPAND, border=5)
        sizerGrid.Add(buttonBrowse, pos=(9, 7),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(sizerButtons, pos=(10, 7), span=(1, 2),
                      flag=wx.ALIGN_RIGHT | wx.ALL, border=5)

        self.SetSizerAndFit(sizerGrid)

        self.__draw_plot()

    def __on_choice(self, event):
        start = self.choiceStart.GetSelection()
        end = self.choiceEnd.GetSelection()
        control = event.GetEventObject()

        if start > end:
            if control == self.choiceStart:
                self.choiceStart.SetSelection(end)
            else:
                self.choiceEnd.SetSelection(start)

        self.__draw_plot()

    def __on_axes(self, _event):
        self.plot.set_axes(self.checkAxes.GetValue())
        self.__draw_plot()

    def __on_grid(self, _event):
        self.plot.set_grid(self.checkGrid.GetValue())
        self.__draw_plot()

    def __on_bar(self, _event):
        self.plot.set_bar(self.checkBar.GetValue())
        self.__draw_plot()

    def __on_imagesize(self, _event):
        dlg = DialogImageSize(self, self.settings)
        dlg.ShowModal()
        self.__show_image_size()

    def __on_browse(self, _event):
        directory = self.editDir.GetValue()
        dlg = wx.DirDialog(self, 'Output directory', directory)
        if dlg.ShowModal() == wx.ID_OK:
            directory = dlg.GetPath()
            self.editDir.SetValue(directory)

    def __on_timer(self, _event):
        self.timer.Stop()
        if not self.isExporting:
            while not self.queue.empty():
                event = self.queue.get()
                status = event.data.get_status()

                if status == Event.DRAW:
                    self.canvas.draw()

        self.timer.Start(self.POLL)

    def __on_ok(self, _event):
        self.isExporting = True
        extent = Extent(self.spectrum)
        dlgProgress = wx.ProgressDialog('Exporting', '', len(self.sweeps),
                                        style=wx.PD_AUTO_HIDE | 
                                        wx.PD_CAN_ABORT | 
                                        wx.PD_REMAINING_TIME)

        try:
            count = 1
            for timeStamp, sweep in self.sweeps.items():
                name = '{0:.0f}.png'.format(timeStamp)
                directory = self.editDir.GetValue()
                filename = os.path.join(directory, name)

                thread = self.plot.set_plot({timeStamp: sweep}, extent, False)
                thread.join()
                filename = os.path.join(directory, '{}.png'.format(timeStamp))
                export_image(filename, File.ImageType.PNG,
                             self.figure,
                             self.settings)

                cont, _skip = dlgProgress.Update(count, name)
                if not cont:
                    break
                count += 1
        except IOError as error:
            wx.MessageBox(error.strerror, 'Error', wx.OK | wx.ICON_WARNING)
        finally:
            dlgProgress.Destroy()
            self.EndModal(wx.ID_OK)

    def __spectrum_range(self, start, end):
        sweeps = {}
        for timeStamp, sweep in self.spectrum.items():
            if start <= timeStamp <= end:
                sweeps[timeStamp] = sweep

        self.sweeps = sweeps

    def __draw_plot(self):
        start, end = self.__get_range()
        self.__spectrum_range(start, end)

        self.textSweeps.SetLabel(str(len(self.sweeps)))

        if len(self.sweeps) > 0:
            total = count_points(self.sweeps)
            if total > 0:
                extent = Extent(self.spectrum)
                self.plot.set_plot(self.sweeps, extent, False)
        else:
            self.plot.clear_plots()

    def __show_image_size(self):
        self.textSize.SetLabel('{}" x {}" @ {}dpi'.format(self.settings.exportWidth,
                                                          self.settings.exportHeight,
                                                          self.settings.exportDpi))

    def __get_range(self):
        start = self.sweepTimeStamps[self.choiceStart.GetSelection()]
        end = self.sweepTimeStamps[self.choiceEnd.GetSelection()]

        return start, end class DialogExportGeo(wx.Dialog):
    IMAGE_SIZE = 500

    def __init__(self, parent, spectrum, location, settings):
        self.spectrum = spectrum
        self.location = location
        self.settings = settings
        self.directory = settings.dirExport
        self.colourMap = settings.colourMap
        self.colourHeat = settings.colourMap
        self.canvas = None
        self.extent = None
        self.xyz = None
        self.plotAxes = False
        self.plotMesh = True
        self.plotCont = True
        self.plotPoint = False
        self.plotHeat = False
        self.plot = None

        wx.Dialog.__init__(self, parent=parent, title='Export Map')

        colours = get_colours()
        freqMin = min(spectrum[min(spectrum)]) * 1000
        freqMax = max(spectrum[min(spectrum)]) * 1000
        bw = freqMax - freqMin

        self.figure = matplotlib.figure.Figure(facecolor='white')
        self.figure.set_size_inches((6, 6))
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.axes = self.figure.add_subplot(111)
        if matplotlib.__version__ >= '1.2':
            self.figure.tight_layout()
        self.figure.subplots_adjust(left=0, right=1, top=1, bottom=0)

        textPlot = wx.StaticText(self, label='Plot')
        self.checkAxes = wx.CheckBox(self, label='Axes')
        self.checkAxes.SetValue(self.plotAxes)
        self.Bind(wx.EVT_CHECKBOX, self.__on_axes, self.checkAxes)
        self.checkCont = wx.CheckBox(self, label='Contour lines')
        self.checkCont.SetValue(self.plotCont)
        self.Bind(wx.EVT_CHECKBOX, self.__on_cont, self.checkCont)
        self.checkPoint = wx.CheckBox(self, label='Locations')
        self.checkPoint.SetValue(self.plotPoint)
        self.Bind(wx.EVT_CHECKBOX, self.__on_point, self.checkPoint)
        sizerPlotCheck = wx.BoxSizer(wx.HORIZONTAL)
        sizerPlotCheck.Add(self.checkAxes, flag=wx.ALL, border=5)
        sizerPlotCheck.Add(self.checkCont, flag=wx.ALL, border=5)
        sizerPlotCheck.Add(self.checkPoint, flag=wx.ALL, border=5)
        sizerPlot = wx.BoxSizer(wx.VERTICAL)
        sizerPlot.Add(textPlot, flag=wx.ALL, border=5)
        sizerPlot.Add(sizerPlotCheck, flag=wx.ALL, border=5)

        textMesh = wx.StaticText(self, label='Mesh')
        self.checkMesh = wx.CheckBox(self, label='On')
        self.checkMesh.SetToolTipString('Signal level mesh')
        self.checkMesh.SetValue(self.plotMesh)
        self.Bind(wx.EVT_CHECKBOX, self.__on_mesh, self.checkMesh)
        self.choiceMapMesh = wx.Choice(self, choices=colours)
        self.choiceMapMesh.SetSelection(colours.index(self.colourMap))
        self.Bind(wx.EVT_CHOICE, self.__on_colour_mesh, self.choiceMapMesh)
        self.barMesh = PanelColourBar(self, self.colourMap)
        sizerMapMesh = wx.BoxSizer(wx.HORIZONTAL)
        sizerMapMesh.Add(self.choiceMapMesh, flag=wx.ALL, border=5)
        sizerMapMesh.Add(self.barMesh, flag=wx.ALL, border=5)
        sizerMesh = wx.BoxSizer(wx.VERTICAL)
        sizerMesh.Add(textMesh, flag=wx.ALL, border=5)
        sizerMesh.Add(self.checkMesh, flag=wx.ALL, border=5)
        sizerMesh.Add(sizerMapMesh, flag=wx.ALL, border=5)

        colours = get_colours()
        textHeat = wx.StaticText(self, label='Heat map')
        self.checkHeat = wx.CheckBox(self, label='On')
        self.checkHeat.SetToolTipString('GPS location heatmap')
        self.checkHeat.SetValue(self.plotHeat)
        self.Bind(wx.EVT_CHECKBOX, self.__on_heat, self.checkHeat)
        self.choiceMapHeat = wx.Choice(self, choices=colours)
        self.choiceMapHeat.SetSelection(colours.index(self.colourHeat))
        self.Bind(wx.EVT_CHOICE, self.__on_colour_heat, self.choiceMapHeat)
        self.barHeat = PanelColourBar(self, self.colourHeat)
        sizerMapHeat = wx.BoxSizer(wx.HORIZONTAL)
        sizerMapHeat.Add(self.choiceMapHeat, flag=wx.ALL, border=5)
        sizerMapHeat.Add(self.barHeat, flag=wx.ALL, border=5)
        sizerHeat = wx.BoxSizer(wx.VERTICAL)
        sizerHeat.Add(textHeat, flag=wx.ALL, border=5)
        sizerHeat.Add(self.checkHeat, flag=wx.ALL, border=5)
        sizerHeat.Add(sizerMapHeat, flag=wx.ALL, border=5)

        textRange = wx.StaticText(self, label='Range')
        textCentre = wx.StaticText(self, label='Centre')
        self.spinCentre = wx.SpinCtrl(self)
        self.spinCentre.SetToolTipString('Centre frequency (kHz)')
        self.spinCentre.SetRange(freqMin, freqMax)
        self.spinCentre.SetValue(freqMin + bw / 2)
        sizerCentre = wx.BoxSizer(wx.HORIZONTAL)
        sizerCentre.Add(textCentre, flag=wx.ALL, border=5)
        sizerCentre.Add(self.spinCentre, flag=wx.ALL, border=5)
        textBw = wx.StaticText(self, label='Bandwidth')
        self.spinBw = wx.SpinCtrl(self)
        self.spinBw.SetToolTipString('Bandwidth (kHz)')
        self.spinBw.SetRange(1, bw)
        self.spinBw.SetValue(bw / 10)
        sizerBw = wx.BoxSizer(wx.HORIZONTAL)
        sizerBw.Add(textBw, flag=wx.ALL, border=5)
        sizerBw.Add(self.spinBw, flag=wx.ALL, border=5)
        buttonUpdate = wx.Button(self, label='Update')
        self.Bind(wx.EVT_BUTTON, self.__on_update, buttonUpdate)
        sizerRange = wx.BoxSizer(wx.VERTICAL)
        sizerRange.Add(textRange, flag=wx.ALL, border=5)
        sizerRange.Add(sizerCentre, flag=wx.ALL, border=5)
        sizerRange.Add(sizerBw, flag=wx.ALL, border=5)
        sizerRange.Add(buttonUpdate, flag=wx.ALL, border=5)

        textOutput = wx.StaticText(self, label='Output')
        self.textRes = wx.StaticText(self)
        buttonRes = wx.Button(self, label='Change...')
        buttonRes.SetToolTipString('Change output resolution')
        self.Bind(wx.EVT_BUTTON, self.__on_imageres, buttonRes)
        sizerRes = wx.BoxSizer(wx.HORIZONTAL)
        sizerRes.Add(self.textRes, flag=wx.ALL, border=5)
        sizerRes.Add(buttonRes, flag=wx.ALL, border=5)
        sizerOutput = wx.BoxSizer(wx.VERTICAL)
        sizerOutput.Add(textOutput, flag=wx.ALL, border=5)
        sizerOutput.Add(sizerRes, flag=wx.ALL, border=5)

        self.__show_image_res()

        font = textPlot.GetFont()
        fontSize = font.GetPointSize()
        font.SetPointSize(fontSize + 4)
        textPlot.SetFont(font)
        textMesh.SetFont(font)
        textHeat.SetFont(font)
        textRange.SetFont(font)
        textOutput.SetFont(font)

        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)

        self.__setup_plot()

        sizerGrid = wx.GridBagSizer(5, 5)
        sizerGrid.Add(self.canvas, pos=(0, 0), span=(5, 6),
                      flag=wx.EXPAND | wx.ALL, border=5)
        sizerGrid.Add(sizerPlot, pos=(0, 7),
                      flag=wx.EXPAND | wx.ALL, border=5)
        sizerGrid.Add(sizerMesh, pos=(1, 7),
                      flag=wx.EXPAND | wx.ALL, border=5)
        sizerGrid.Add(sizerHeat, pos=(2, 7),
                      flag=wx.EXPAND | wx.ALL, border=5)
        sizerGrid.Add(sizerRange, pos=(3, 7),
                      flag=wx.EXPAND | wx.ALL, border=5)
        sizerGrid.Add(sizerOutput, pos=(4, 7),
                      flag=wx.EXPAND | wx.ALL, border=5)
        sizerGrid.Add(sizerButtons, pos=(5, 7), span=(1, 2),
                      flag=wx.ALIGN_RIGHT | wx.ALL, border=5)

        self.SetSizerAndFit(sizerGrid)

        self.__draw_plot()

    def __setup_plot(self):
        self.axes.clear()

        self.choiceMapMesh.Enable(self.plotMesh)
        self.choiceMapHeat.Enable(self.plotHeat)

        self.axes.set_xlabel('Longitude ($^\circ$)')
        self.axes.set_ylabel('Latitude ($^\circ$)')
        self.axes.set_xlim(auto=True)
        self.axes.set_ylim(auto=True)
        formatter = ScalarFormatter(useOffset=False)
        self.axes.xaxis.set_major_formatter(formatter)
        self.axes.yaxis.set_major_formatter(formatter)

    def __draw_plot(self):
        freqCentre = self.spinCentre.GetValue()
        freqBw = self.spinBw.GetValue()
        freqMin = (freqCentre - freqBw) / 1000.
        freqMax = (freqCentre + freqBw) / 1000.

        coords = {}
        for timeStamp in self.spectrum:
            spectrum = self.spectrum[timeStamp]
            sweep = [yv for xv, yv in spectrum.items() if freqMin <= xv <= freqMax]
            if len(sweep):
                peak = max(sweep)
                try:
                    location = self.location[timeStamp]
                except KeyError:
                    continue

                coord = tuple(location[0:2])
                if coord not in coords:
                    coords[coord] = peak
                else:
                    coords[coord] = (coords[coord] + peak) / 2

        x = []
        y = []
        z = []

        for coord, peak in coords.iteritems():
            x.append(coord[1])
            y.append(coord[0])
            z.append(peak)

        self.extent = (min(x), max(x), min(y), max(y))
        self.xyz = (x, y, z)

        xi, yi = numpy.meshgrid(numpy.linspace(min(x), max(x), self.IMAGE_SIZE),
                                numpy.linspace(min(y), max(y), self.IMAGE_SIZE))

        if self.plotMesh or self.plotCont:
            triangle = matplotlib.tri.Triangulation(x, y)
            interp = matplotlib.tri.CubicTriInterpolator(triangle, z, kind='geom')
            zi = interp(xi, yi)
            
        if self.plotMesh:
            self.plot = self.axes.pcolormesh(xi, yi, zi, cmap=self.colourMap)
            self.plot.set_zorder(1)
 
        if self.plotCont:
            contours = self.axes.contour(xi, yi, zi, linewidths=0.5,
                                         colors='k')
            self.axes.clabel(contours, inline=1, fontsize='x-small',
                            gid='clabel', zorder=3)

        if self.plotHeat:
            image = create_heatmap(x, y,
                                   self.IMAGE_SIZE, self.IMAGE_SIZE / 10,
                                   self.colourHeat)
            heatMap = self.axes.imshow(image, aspect='auto', extent=self.extent)
            heatMap.set_zorder(2)

        if self.plotPoint:
            self.axes.plot(x, y, 'wo')
            for posX, posY, posZ in zip(x, y, z):
                points = self.axes.annotate('{0:.2f}dB'.format(posZ), xy=(posX, posY),
                                            xytext=(-5, 5), ha='right',
                                            textcoords='offset points')
                points.set_zorder(3)

        if matplotlib.__version__ >= '1.3':
            effect = patheffects.withStroke(linewidth=2, foreground="w",
                                            alpha=0.75)
            for child in self.axes.get_children():
                child.set_path_effects([effect])

        if self.plotAxes:
            self.axes.set_axis_on()
        else:
            self.axes.set_axis_off()
        self.canvas.draw()

    def __draw_warning(self):
        self.axes.text(0.5, 0.5, 'Insufficient GPS data',
                       ha='center', va='center',
                       transform=self.axes.transAxes)

    def __on_update(self, _event):
        self.__setup_plot()
        self.__draw_plot()

    def __on_imageres(self, _event):
        dlg = DialogImageSize(self, self.settings, True)
        dlg.ShowModal()
        self.__show_image_res()

    def __on_ok(self, _event):
        self.EndModal(wx.ID_OK)

    def __on_axes(self, _event):
        self.plotAxes = self.checkAxes.GetValue()
        if self.plotAxes:
            self.axes.set_axis_on()
        else:
            self.axes.set_axis_off()
        self.canvas.draw()

    def __on_mesh(self, _event):
        self.plotMesh = self.checkMesh.GetValue()
        self.__on_update(None)

    def __on_cont(self, _event):
        self.plotCont = self.checkCont.GetValue()
        self.__on_update(None)

    def __on_point(self, _event):
        self.plotPoint = self.checkPoint.GetValue()
        self.__on_update(None)

    def __on_heat(self, _event):
        self.plotHeat = self.checkHeat.GetValue()
        self.__on_update(None)

    def __on_colour_mesh(self, _event):
        self.colourMesh = self.choiceMapMesh.GetStringSelection()
        self.barMesh.set_map(self.colourMesh)
        if self.plot:
            self.plot.set_cmap(self.colourMesh)
            self.canvas.draw()

    def __on_colour_heat(self, _event):
        self.colourHeat = self.choiceMapHeat.GetStringSelection()
        self.barHeat.set_map(self.colourHeat)
        self.__on_update(None)

    def __show_image_res(self):
        self.textRes.SetLabel('{}dpi'.format(self.settings.exportDpi))

    def get_filename(self):
        return self.filename

    def get_directory(self):
        return self.directory

    def get_extent(self):
        return self.extent

    def get_image(self):
        width = self.extent[1] - self.extent[0]
        height = self.extent[3] - self.extent[2]
        self.figure.set_size_inches((6, 6. * width / height))
        self.figure.set_dpi(self.settings.exportDpi)
        self.figure.patch.set_alpha(0)
        self.axes.patch.set_alpha(0)
        canvas = FigureCanvasAgg(self.figure)
        canvas.draw()

        renderer = canvas.get_renderer()
        if matplotlib.__version__ >= '1.2':
            buf = renderer.buffer_rgba()
        else:
            buf = renderer.buffer_rgba(0, 0)
        size = canvas.get_width_height()
        image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)

        return image

    def get_xyz(self):
        return self.xyz class DialogProperties(wx.Dialog):

    def __init__(self, parent, scanInfo):
        wx.Dialog.__init__(self, parent, title="Scan Properties")

        self.scanInfo = scanInfo

        box = wx.BoxSizer(wx.VERTICAL)

        grid = wx.GridBagSizer(0, 0)

        boxScan = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "Scan"),
                                    wx.HORIZONTAL)

        gridScan = wx.GridBagSizer(0, 0)

        textDesc = wx.StaticText(self, label="Description")
        gridScan.Add(textDesc, (0, 0), (1, 1), wx.ALL, 5)
        self.textCtrlDesc = wx.TextCtrl(self, value=scanInfo.desc,
                                        style=wx.TE_MULTILINE)
        gridScan.Add(self.textCtrlDesc, (0, 1), (2, 2), wx.ALL | wx.EXPAND, 5)

        textStart = wx.StaticText(self, label="Start")
        gridScan.Add(textStart, (2, 0), (1, 1), wx.ALL, 5)
        textCtrlStart = wx.TextCtrl(self, value="Unknown",
                                    style=wx.TE_READONLY)
        if scanInfo.start is not None:
            textCtrlStart.SetValue(str(scanInfo.start))
        gridScan.Add(textCtrlStart, (2, 1), (1, 1), wx.ALL, 5)
        textMHz1 = wx.StaticText(self, wx.ID_ANY, label="MHz")
        gridScan.Add(textMHz1, (2, 2), (1, 1), wx.ALL, 5)

        textStop = wx.StaticText(self, label="Stop")
        gridScan.Add(textStop, (3, 0), (1, 1), wx.ALL, 5)
        textCtrlStop = wx.TextCtrl(self, value="Unknown",
                                   style=wx.TE_READONLY)
        if scanInfo.stop is not None:
            textCtrlStop.SetValue(str(scanInfo.stop))
        gridScan.Add(textCtrlStop, (3, 1), (1, 1), wx.ALL, 5)
        textMHz2 = wx.StaticText(self, label="MHz")
        gridScan.Add(textMHz2, (3, 2), (1, 1), wx.ALL, 5)

        textDwell = wx.StaticText(self, label="Dwell")
        gridScan.Add(textDwell, (4, 0), (1, 1), wx.ALL, 5)
        textCtrlDwell = wx.TextCtrl(self, value="Unknown",
                                    style=wx.TE_READONLY)
        if scanInfo.dwell is not None:
            textCtrlDwell.SetValue(str(scanInfo.dwell))
        gridScan.Add(textCtrlDwell, (4, 1), (1, 1), wx.ALL, 5)
        textSeconds = wx.StaticText(self, label="seconds")
        gridScan.Add(textSeconds, (4, 2), (1, 1), wx.ALL, 5)

        textNfft = wx.StaticText(self, label="FFT Size")
        gridScan.Add(textNfft, (5, 0), (1, 1), wx.ALL, 5)
        textCtrlNfft = wx.TextCtrl(self, value="Unknown", style=wx.TE_READONLY)
        if scanInfo.nfft is not None:
            textCtrlNfft.SetValue(str(scanInfo.nfft))
        gridScan.Add(textCtrlNfft, (5, 1), (1, 1), wx.ALL, 5)

        textRbw = wx.StaticText(self, label="RBW")
        gridScan.Add(textRbw, (6, 0), (1, 1), wx.ALL, 5)
        rbw = ((SAMPLE_RATE / scanInfo.nfft) / 1000.0) * 2.0
        textCtrlStop = wx.TextCtrl(self, value="{0:.3f}".format(rbw),
                                   style=wx.TE_READONLY)
        gridScan.Add(textCtrlStop, (6, 1), (1, 1), wx.ALL, 5)
        textKHz = wx.StaticText(self, label="kHz")
        gridScan.Add(textKHz, (6, 2), (1, 1), wx.ALL, 5)

        textTime = wx.StaticText(self, label="First scan")
        gridScan.Add(textTime, (7, 0), (1, 1), wx.ALL, 5)
        textCtrlTime = wx.TextCtrl(self, value="Unknown", style=wx.TE_READONLY)
        if scanInfo.timeFirst is not None:
            textCtrlTime.SetValue(format_time(scanInfo.timeFirst, True))
        gridScan.Add(textCtrlTime, (7, 1), (1, 1), wx.ALL, 5)

        textTime = wx.StaticText(self, label="Last scan")
        gridScan.Add(textTime, (8, 0), (1, 1), wx.ALL, 5)
        textCtrlTime = wx.TextCtrl(self, value="Unknown", style=wx.TE_READONLY)
        if scanInfo.timeLast is not None:
            textCtrlTime.SetValue(format_time(scanInfo.timeLast, True))
        gridScan.Add(textCtrlTime, (8, 1), (1, 1), wx.ALL, 5)

        textLat = wx.StaticText(self, label="Latitude")
        gridScan.Add(textLat, (9, 0), (1, 1), wx.ALL, 5)
        self.textCtrlLat = wx.TextCtrl(self, value="Unknown")
        self.textCtrlLat.SetValidator(ValidatorCoord(True))
        if scanInfo.lat is not None:
            self.textCtrlLat.SetValue(str(scanInfo.lat))
        gridScan.Add(self.textCtrlLat, (9, 1), (1, 1), wx.ALL, 5)

        textLon = wx.StaticText(self, label="Longitude")
        gridScan.Add(textLon, (10, 0), (1, 1), wx.ALL, 5)
        self.textCtrlLon = wx.TextCtrl(self, value="Unknown")
        self.textCtrlLon.SetValidator(ValidatorCoord(False))
        if scanInfo.lon is not None:
            self.textCtrlLon.SetValue(str(scanInfo.lon))
        gridScan.Add(self.textCtrlLon, (10, 1), (1, 1), wx.ALL, 5)

        boxScan.Add(gridScan, 0, 0, 5)

        grid.Add(boxScan, (0, 0), (1, 1), wx.ALL | wx.EXPAND, 5)

        boxDevice = wx.StaticBoxSizer(wx.StaticBox(self, label="Device"),
                                      wx.VERTICAL)

        gridDevice = wx.GridBagSizer(0, 0)

        textName = wx.StaticText(self, label="Name")
        gridDevice.Add(textName, (0, 0), (1, 1), wx.ALL, 5)
        textCtrlName = wx.TextCtrl(self, value="Unknown", style=wx.TE_READONLY)
        if scanInfo.name is not None:
            textCtrlName.SetValue(scanInfo.name)
        gridDevice.Add(textCtrlName, (0, 1), (1, 2), wx.ALL | wx.EXPAND, 5)

        textTuner = wx.StaticText(self, label="Tuner")
        gridDevice.Add(textTuner, (1, 0), (1, 1), wx.ALL, 5)
        textCtrlTuner = wx.TextCtrl(self, value="Unknown",
                                    style=wx.TE_READONLY)
        if scanInfo.tuner != -1:
            textCtrlTuner.SetValue(TUNER[scanInfo.tuner])
        gridDevice.Add(textCtrlTuner, (1, 1), (1, 2), wx.ALL | wx.EXPAND, 5)

        testGain = wx.StaticText(self, label="Gain")
        gridDevice.Add(testGain, (2, 0), (1, 1), wx.ALL, 5)
        textCtrlGain = wx.TextCtrl(self, value="Unknown", style=wx.TE_READONLY)
        if scanInfo.gain is not None:
            textCtrlGain.SetValue(str(scanInfo.gain))
        gridDevice.Add(textCtrlGain, (2, 1), (1, 1), wx.ALL, 5)
        textDb = wx.StaticText(self, label="dB")
        gridDevice.Add(textDb, (2, 2), (1, 1), wx.ALL, 5)

        textLo = wx.StaticText(self, label="LO")
        gridDevice.Add(textLo, (3, 0), (1, 1), wx.ALL, 5)
        textCtrlLo = wx.TextCtrl(self, value="Unknown", style=wx.TE_READONLY)
        if scanInfo.lo is not None:
            textCtrlLo.SetValue(str(scanInfo.lo))
        gridDevice.Add(textCtrlLo, (3, 1), (1, 1), wx.ALL, 5)
        textMHz3 = wx.StaticText(self, label="MHz")
        gridDevice.Add(textMHz3, (3, 2), (1, 1), wx.ALL, 5)

        textCal = wx.StaticText(self, label="Calibration")
        gridDevice.Add(textCal, (4, 0), (1, 1), wx.ALL, 5)
        textCtrlCal = wx.TextCtrl(self, value="Unknown", style=wx.TE_READONLY)
        if scanInfo.calibration is not None:
            textCtrlCal.SetValue(str(scanInfo.calibration))
        gridDevice.Add(textCtrlCal, (4, 1), (1, 1), wx.ALL, 5)
        testPpm = wx.StaticText(self, label="ppm")
        gridDevice.Add(testPpm, (4, 2), (1, 1), wx.ALL, 5)

        boxDevice.Add(gridDevice, 1, wx.EXPAND, 5)

        grid.Add(boxDevice, (1, 0), (1, 1), wx.ALL | wx.EXPAND, 5)

        box.Add(grid, 1, wx.ALL | wx.EXPAND, 5)

        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)
        box.Add(sizerButtons, 0, wx.ALIGN_RIGHT | wx.ALL, 5)

        self.SetSizerAndFit(box)

    def __on_ok(self, _event):
        self.scanInfo.desc = self.textCtrlDesc.GetValue()
        if self.Validate():
            lat = self.textCtrlLat.GetValue()
            if len(lat) == 0 or lat == "-" or lat.lower() == "unknown":
                self.scanInfo.lat = None
            else:
                self.scanInfo.lat = float(lat)

            lon = self.textCtrlLon.GetValue()
            if len(lon) == 0 or lon == "-" or lon.lower() == "unknown":
                self.scanInfo.lon = None
            else:
                self.scanInfo.lon = float(lon)

            self.EndModal(wx.ID_CLOSE) class DialogSaveWarn(wx.Dialog):

    def __init__(self, parent, warnType):
        self.code = -1

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

        prompt = ["scanning again", "opening a file",
                  "exiting", "clearing", "merging"][warnType]
        text = wx.StaticText(self,
                             label="Save plot_line before {}?".format(prompt))
        icon = wx.StaticBitmap(self, wx.ID_ANY,
                               wx.ArtProvider.GetBitmap(wx.ART_INFORMATION,
                                                        wx.ART_MESSAGE_BOX))

        tbox = wx.BoxSizer(wx.HORIZONTAL)
        tbox.Add(text)

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add(icon, 0, wx.ALL, 5)
        hbox.Add(tbox, 0, wx.ALL, 5)

        buttonYes = wx.Button(self, wx.ID_YES, 'Yes')
        buttonNo = wx.Button(self, wx.ID_NO, 'No')
        buttonCancel = wx.Button(self, wx.ID_CANCEL, 'Cancel')

        buttonYes.Bind(wx.EVT_BUTTON, self.__on_close)
        buttonNo.Bind(wx.EVT_BUTTON, self.__on_close)

        buttons = wx.StdDialogButtonSizer()
        buttons.AddButton(buttonYes)
        buttons.AddButton(buttonNo)
        buttons.AddButton(buttonCancel)
        buttons.Realize()

        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(hbox, 1, wx.ALL | wx.EXPAND, 10)
        vbox.Add(buttons, 1, wx.ALL | wx.EXPAND, 10)

        self.SetSizerAndFit(vbox)

    def __on_close(self, event):
        self.EndModal(event.GetId())
        return

    def get_code(self):
        return self.code class DialogRestore(wx.Dialog):
    COL_SEL, COL_TIME, COL_SIZE = range(3)

    def __init__(self, parent, backups):
        self.selected = 0
        self.backups = backups
        self.restored = None

        wx.Dialog.__init__(self, parent=parent, title='Restore backups')

        self.grid = grid.Grid(self)
        self.grid.CreateGrid(1, 3)
        self.grid.SetRowLabelSize(0)
        self.grid.SetColLabelValue(self.COL_SEL, 'Selected')
        self.grid.SetColLabelValue(self.COL_TIME, 'Time')
        self.grid.SetColLabelValue(self.COL_SIZE, 'Size (k)')
        self.grid.SetColFormatFloat(self.COL_SIZE, -1, 1)

        self.Bind(grid.EVT_GRID_CELL_LEFT_CLICK, self.__on_click)

        buttonRest = wx.Button(self, wx.ID_OPEN, 'Restore')
        buttonDel = wx.Button(self, wx.ID_DELETE, 'Delete')
        buttonDelAll = wx.Button(self, wx.ID_DELETE, 'Delete all')
        buttonCancel = wx.Button(self, wx.ID_CANCEL, 'Close')
        self._buttonsBackup = [buttonRest, buttonDel, buttonDelAll]

        buttonRest.Bind(wx.EVT_BUTTON, self.__on_restore)
        buttonDel.Bind(wx.EVT_BUTTON, self.__on_delete)
        buttonDelAll.Bind(wx.EVT_BUTTON, self.__on_delete_all)

        sizerButtons = wx.BoxSizer(wx.HORIZONTAL)
        sizerButtons.Add(buttonRest)
        sizerButtons.Add(buttonDel)
        sizerButtons.Add(buttonDelAll)
        sizerButtons.Add(buttonCancel)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.grid, flag=wx.ALL | wx.EXPAND, border=5)
        sizer.Add(sizerButtons, flag=wx.ALL, border=5)

        self.__update()

        self.SetSizerAndFit(sizer)

    def __update(self):
        if self.grid.GetNumberRows():
            self.grid.DeleteRows(0, self.grid.GetNumberRows())
        self.grid.AppendRows(len(self.backups.backups))

        i = 0
        for backup in self.backups.backups:
            self.grid.SetCellRenderer(i, self.COL_SEL, TickCellRenderer())
            self.grid.SetCellRenderer(i, self.COL_TIME,
                                      GridCellDateTimeRenderer())
            self.grid.SetReadOnly(i, self.COL_TIME, True)
            self.grid.SetReadOnly(i, self.COL_SIZE, True)

            self.grid.SetCellValue(i, self.COL_TIME,
                                   str(backup[1].replace(microsecond=0)))
            self.grid.SetCellValue(i, self.COL_SIZE, str(backup[2]))
            i += 1

        self.__select_row(0)

        self.grid.AutoSize()

        for button in self._buttonsBackup:
            button.Enable(len(self.backups.backups))

    def __on_click(self, event):
        col = event.GetCol()

        if col == self.COL_SEL:
            row = event.GetRow()
            self.selected = row
            self.__select_row(row)

    def __on_restore(self, event):
        try:
            self.restored = self.backups.load(self.selected)
        except (cPickle.UnpicklingError, AttributeError,
                EOFError, ImportError, IndexError, ValueError):
            wx.MessageBox('The file could not be restored', 'Restore failed',
                          wx.OK | wx.ICON_ERROR)
            return

        self.EndModal(event.GetId())

    def __on_delete(self, _event):
        dlg = wx.MessageDialog(self, 'Delete the selected backup?',
                               'Delete backup',
                               wx.OK | wx.CANCEL | wx.ICON_QUESTION)
        if dlg.ShowModal() == wx.ID_OK:
            self.backups.delete(self.selected)
            self.__update()

    def __on_delete_all(self, _event):
        dlg = wx.MessageDialog(self, 'Delete all the backups?',
                               'Delete all backups',
                               wx.OK | wx.CANCEL | wx.ICON_QUESTION)
        if dlg.ShowModal() == wx.ID_OK:
            for i in range(len(self.backups.backups)):
                self.backups.delete(i)
        self.__update()

    def __select_row(self, index):
        self.grid.ClearSelection()
        for i in range(0, self.grid.GetNumberRows()):
            tick = "0"
            if i == index:
                tick = "1"
            self.grid.SetCellValue(i, self.COL_SEL, tick)

    def get_restored(self):
        return self.restored class DialogSysInfo(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent=parent, title="System Information")

        textVersions = wx.TextCtrl(self,
                                   style=wx.TE_MULTILINE |
                                   wx.TE_READONLY |
                                   wx.TE_DONTWRAP |
                                   wx.TE_NO_VSCROLL)
        buttonOk = wx.Button(self, wx.ID_OK)

        self.__populate_versions(textVersions)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(textVersions, 1, flag=wx.ALL, border=10)
        sizer.Add(buttonOk, 0, flag=wx.ALL | wx.ALIGN_RIGHT, border=10)
        self.SetSizerAndFit(sizer)
        self.Centre()

    def __populate_versions(self, control):
        imageType = 'Pillow'
        try:
            imageVer = Image.PILLOW_VERSION
        except AttributeError:
            imageType = 'PIL'
            imageVer = Image.VERSION

        visvisVer = 'Not installed'
        if not hasattr(sys, 'frozen'):
            try:
                import visvis as vv
                visvisVer = vv.__version__
            except ImportError:
                pass

        versions = ('Hardware:\n'
                    '\tProcessor: {}, {} cores\n\n'
                    'Software:\n'
                    '\tOS: {}, {}\n'
                    '\tPython: {}\n'
                    '\tmatplotlib: {}\n'
                    '\tNumPy: {}\n'
                    '\t{}: {}\n'
                    '\tpySerial: {}\n'
                    '\tvisvis: {}\n'
                    '\twxPython: {}\n'
                    ).format(platform.processor(), multiprocessing.cpu_count(),
                             platform.platform(), platform.machine(),
                             platform.python_version(),
                             matplotlib.__version__,
                             numpy.version.version,
                             imageType, imageVer,
                             serial.VERSION,
                             visvisVer,
                             wx.version())

        control.SetValue(versions)

        dc = wx.WindowDC(control)
        extent = list(dc.GetMultiLineTextExtent(versions, control.GetFont()))
        extent[0] += wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) * 2
        extent[1] += wx.SystemSettings.GetMetric(wx.SYS_HSCROLL_Y) * 2
        control.SetMinSize((extent[0], extent[1]))
        self.Layout() class DialogAbout(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent=parent, title="About")

        bitmapIcon = wx.StaticBitmap(self, bitmap=load_bitmap('icon'))
        textAbout = wx.StaticText(self, label="A simple spectrum analyser for "
                                  "scanning\n with a RTL-SDR compatible USB "
                                  "device", style=wx.ALIGN_CENTRE)
        textLink = wx.HyperlinkCtrl(self, wx.ID_ANY,
                                    label="http://eartoearoak.com/software/rtlsdr-scanner",
                                    url="http://eartoearoak.com/software/rtlsdr-scanner")
        textVersion = wx.StaticText(self,
                                    label='v' + '.'.join([str(x) for x in VERSION]))
        buttonOk = wx.Button(self, wx.ID_OK)

        grid = wx.GridBagSizer(10, 10)
        grid.Add(bitmapIcon, pos=(0, 0), span=(3, 1),
                 flag=wx.ALIGN_LEFT | wx.ALL, border=10)
        grid.Add(textAbout, pos=(0, 1), span=(1, 2),
                 flag=wx.ALIGN_CENTRE | wx.ALL, border=10)
        grid.Add(textLink, pos=(1, 1), span=(1, 2),
                 flag=wx.ALIGN_CENTRE | wx.ALL, border=10)
        grid.Add(textVersion, pos=(2, 1), span=(1, 2),
                 flag=wx.ALIGN_CENTRE | wx.ALL, border=10)
        grid.Add(buttonOk, pos=(3, 2),
                 flag=wx.ALIGN_RIGHT | wx.ALL, border=10)

        self.SetSizerAndFit(grid)
        self.Centre() 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) class DialogAdvPrefs(wx.Dialog):
    def __init__(self, parent, settings):
        self.settings = settings

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

        self.winFunc = settings.winFunc

        textOverlap = wx.StaticText(self, label='PSD Overlap (%)')
        self.slideOverlap = wx.Slider(self, wx.ID_ANY,
                                      settings.overlap * 100,
                                      0, 75,
                                      style=wx.SL_LABELS)
        self.slideOverlap.SetToolTipString('Power spectral density'
                                           ' overlap')
        textWindow = wx.StaticText(self, label='Window')
        self.buttonWindow = wx.Button(self, wx.ID_ANY, self.winFunc)
        self.Bind(wx.EVT_BUTTON, self.__on_window, self.buttonWindow)

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

        advgrid = wx.GridBagSizer(10, 10)
        advgrid.Add(textOverlap, pos=(0, 0),
                    flag=wx.ALL | wx.ALIGN_CENTRE)
        advgrid.Add(self.slideOverlap, pos=(0, 1), flag=wx.EXPAND)
        advgrid.Add(textWindow, pos=(1, 0), flag=wx.EXPAND)
        advgrid.Add(self.buttonWindow, pos=(1, 1))
        advgrid.Add(sizerButtons, pos=(2, 1), flag=wx.EXPAND)

        advBox = wx.BoxSizer()
        advBox.Add(advgrid, flag=wx.ALL | wx.ALIGN_CENTRE, border=10)

        self.SetSizerAndFit(advBox)

    def __on_window(self, _event):
        dlg = DialogWinFunc(self, self.winFunc)
        if dlg.ShowModal() == wx.ID_OK:
            self.winFunc = dlg.get_win_func()
            self.buttonWindow.SetLabel(self.winFunc)
        dlg.Destroy()

    def __on_ok(self, _event):
        self.settings.overlap = self.slideOverlap.GetValue() / 100.0
        self.settings.winFunc = self.winFunc

        self.EndModal(wx.ID_OK) class DialogFormatting(wx.Dialog):
    def __init__(self, parent, settings):
        self.settings = settings

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

        textFreq = wx.StaticText(self, label='Frequency precision')
        self.spinFreq = wx.SpinCtrl(self, wx.ID_ANY, min=0, max=6)
        self.spinFreq.SetValue(settings.precisionFreq)
        self.spinFreq.SetToolTipString('Displayed frequency decimal precision')

        textLevel = wx.StaticText(self, label='Level precision')
        self.spinLevel = wx.SpinCtrl(self, wx.ID_ANY, min=0, max=2)
        self.spinLevel.SetValue(settings.precisionLevel)
        self.spinLevel.SetToolTipString('Displayed level decimal precision')

        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(textFreq, pos=(0, 0),
                  flag=wx.ALL, border=5)
        sizer.Add(self.spinFreq, pos=(0, 1),
                  flag=wx.ALL, border=5)
        sizer.Add(textLevel, pos=(1, 0),
                  flag=wx.ALL, border=5)
        sizer.Add(self.spinLevel, pos=(1, 1),
                  flag=wx.ALL, border=5)
        sizer.Add(sizerButtons, pos=(2, 0), span=(1, 2),
                  flag=wx.ALL | wx.ALIGN_RIGHT, border=5)

        self.SetSizerAndFit(sizer)

    def __on_ok(self, _event):
        self.settings.precisionFreq = self.spinFreq.GetValue()
        self.settings.precisionLevel = self.spinLevel.GetValue()

        self.EndModal(wx.ID_OK) class DialogScanDelay(wx.Dialog):
    def __init__(self, parent, settings):
        wx.Dialog.__init__(self, parent=parent, title='Smoothing')

        self.settings = settings

        textDelay = wx.StaticText(self, label='Scan delay (s)')
        self.spinDelay = wx.SpinCtrl(self, value=str(settings.scanDelay),
                                     min=0, max=86400)

        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()

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(textDelay, flag=wx.ALL, border=5)
        sizer.Add(self.spinDelay, flag=wx.ALL, border=5)
        sizer.Add(sizerButtons, flag=wx.ALL, border=5)

        self.SetSizerAndFit(sizer)

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

    def __on_ok(self, _event):
        self.settings.scanDelay = self.spinDelay.GetValue()

        self.EndModal(wx.ID_OK) class DialogCompare(wx.Dialog):
    def __init__(self, parent, settings, filename):

        self.settings = settings
        self.dirname = settings.dirScans
        self.filename = filename

        wx.Dialog.__init__(self, parent=parent, title="Compare plots",
                           style=wx.DEFAULT_DIALOG_STYLE |
                           wx.RESIZE_BORDER |
                           wx.MAXIMIZE_BOX)

        self.graph = PanelGraphCompare(self, self.__on_cursor)
        self.graph.show_plot1(settings.compareOne)
        self.graph.show_plot2(settings.compareTwo)
        self.graph.show_plotdiff(settings.compareDiff)

        textPlot1 = wx.StaticText(self, label='Plot 1')
        linePlot1 = PanelLine(self, wx.BLUE)
        self.checkOne = wx.CheckBox(self, wx.ID_ANY)
        self.checkOne.SetValue(settings.compareOne)
        self.buttonPlot1 = wx.Button(self, wx.ID_ANY, 'Load...')
        self.textPlot1 = wx.StaticText(self, label="<None>")
        self.textLoc1 = wx.StaticText(self, label='\n')
        self.Bind(wx.EVT_BUTTON, self.__on_load_plot, self.buttonPlot1)

        textPlot2 = wx.StaticText(self, label='Plot 2')
        linePlot2 = PanelLine(self, wx.GREEN)
        self.checkTwo = wx.CheckBox(self, wx.ID_ANY)
        self.checkTwo.SetValue(settings.compareTwo)
        self.buttonPlot2 = wx.Button(self, wx.ID_ANY, 'Load...')
        self.textPlot2 = wx.StaticText(self, label="<None>")
        self.textLoc2 = wx.StaticText(self, label='\n')
        self.Bind(wx.EVT_BUTTON, self.__on_load_plot, self.buttonPlot2)

        textPlotDiff = wx.StaticText(self, label='Difference')
        linePlotDiff = PanelLine(self, wx.RED)
        self.checkDiff = wx.CheckBox(self, wx.ID_ANY)
        self.checkDiff.SetValue(settings.compareDiff)
        self.textLocDiff = wx.StaticText(self, label='\n')

        font = textPlot1.GetFont()
        fontSize = font.GetPointSize()
        font.SetPointSize(fontSize + 4)
        textPlot1.SetFont(font)
        textPlot2.SetFont(font)
        textPlotDiff.SetFont(font)

        fontStyle = font.GetStyle()
        fontWeight = font.GetWeight()
        font = wx.Font(fontSize, wx.FONTFAMILY_MODERN, fontStyle,
                       fontWeight)
        self.textLoc1.SetFont(font)
        self.textLoc2.SetFont(font)
        self.textLocDiff.SetFont(font)

        buttonClose = wx.Button(self, wx.ID_CLOSE, 'Close')

        self.Bind(wx.EVT_CHECKBOX, self.__on_check1, self.checkOne)
        self.Bind(wx.EVT_CHECKBOX, self.__on_check2, self.checkTwo)
        self.Bind(wx.EVT_CHECKBOX, self.__on_check_diff, self.checkDiff)
        self.Bind(wx.EVT_BUTTON, self.__on_close, buttonClose)

        grid = wx.GridBagSizer(5, 5)

        grid.Add(textPlot1, pos=(0, 0))
        grid.Add(linePlot1, pos=(0, 1), flag=wx.EXPAND)
        grid.Add(self.checkOne, pos=(0, 2), flag=wx.ALIGN_CENTER_VERTICAL)
        grid.Add(self.buttonPlot1, pos=(1, 0))
        grid.Add(self.textPlot1, pos=(2, 0))
        grid.Add(self.textLoc1, pos=(3, 0))

        grid.Add(wx.StaticLine(self), pos=(5, 0), span=(1, 3), flag=wx.EXPAND)
        grid.Add(textPlot2, pos=(6, 0))
        grid.Add(linePlot2, pos=(6, 1), flag=wx.EXPAND)
        grid.Add(self.checkTwo, pos=(6, 2), flag=wx.ALIGN_CENTER_VERTICAL)
        grid.Add(self.buttonPlot2, pos=(7, 0))
        grid.Add(self.textPlot2, pos=(8, 0))
        grid.Add(self.textLoc2, pos=(9, 0))

        grid.Add(wx.StaticLine(self), pos=(11, 0), span=(1, 3), flag=wx.EXPAND)
        grid.Add(textPlotDiff, pos=(12, 0))
        grid.Add(linePlotDiff, pos=(12, 1), flag=wx.EXPAND)
        grid.Add(self.checkDiff, pos=(12, 2), flag=wx.ALIGN_CENTER_VERTICAL)
        grid.Add(self.textLocDiff, pos=(13, 0))

        sizerV = wx.BoxSizer(wx.HORIZONTAL)
        sizerV.Add(self.graph, 1, wx.EXPAND)
        sizerV.Add(grid, 0, wx.ALL, border=5)

        sizerH = wx.BoxSizer(wx.VERTICAL)
        sizerH.Add(sizerV, 1, wx.EXPAND, border=5)
        sizerH.Add(buttonClose, 0, wx.ALL | wx.ALIGN_RIGHT, border=5)

        self.SetSizerAndFit(sizerH)

        close_modeless()

    def __on_cursor(self, locs):
        if locs is None:
            self.textLoc1.SetLabel('')
            self.textLoc2.SetLabel('')
            self.textLocDiff.SetLabel('')
        else:
            self.textLoc1.SetLabel(self.__format_loc(locs['x1'], locs['y1']))
            self.textLoc2.SetLabel(self.__format_loc(locs['x2'], locs['y2']))
            self.textLocDiff.SetLabel(self.__format_loc(locs['x3'], locs['y3']))

    def __on_load_plot(self, event):
        dlg = wx.FileDialog(self, "Open a scan", self.dirname, self.filename,
                            File.get_type_filters(File.Types.SAVE),
                            wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.dirname = dlg.GetDirectory()
            self.filename = dlg.GetFilename()
            _scanInfo, spectrum, _location = open_plot(self.dirname,
                                                       self.filename)
            if event.EventObject == self.buttonPlot1:
                self.textPlot1.SetLabel(self.filename)
                self.graph.set_spectrum1(spectrum)
            else:
                self.textPlot2.SetLabel(self.filename)
                self.graph.set_spectrum2(spectrum)

        dlg.Destroy()

    def __on_check1(self, _event):
        checked = self.checkOne.GetValue()
        self.settings.compareOne = checked
        self.graph.show_plot1(checked)

    def __on_check2(self, _event):
        checked = self.checkTwo.GetValue()
        self.settings.compareTwo = checked
        self.graph.show_plot2(checked)

    def __on_check_diff(self, _event):
        checked = self.checkDiff.GetValue()
        self.settings.compareDiff = checked
        self.graph.show_plotdiff(checked)

    def __on_close(self, _event):
        close_modeless()
        self.Destroy()

    def __format_loc(self, x, y):
        if None in [x, y]:
            return ""

        freq, level = format_precision(self.settings, x, y, units=False)

        return '{} MHz\n{}     dB/Hz'.format(freq, level) class DialogAutoCal(wx.Dialog):
    def __init__(self, parent, freq, callbackCal):
        self.callback = callbackCal
        self.cal = 0

        wx.Dialog.__init__(self, parent=parent, title="Auto Calibration",
                           style=wx.CAPTION)
        self.Bind(wx.EVT_CLOSE, self.__on_close)

        title = wx.StaticText(self, label="Calibrate to a known stable signal")
        font = title.GetFont()
        font.SetPointSize(font.GetPointSize() + 2)
        title.SetFont(font)
        text = wx.StaticText(self, label="Frequency (MHz)")
        self.textFreq = masked.NumCtrl(self, value=freq, fractionWidth=3,
                                       min=F_MIN, max=F_MAX)

        self.buttonCal = wx.Button(self, label="Calibrate")
        if len(parent.devicesRtl) == 0:
            self.buttonCal.Disable()
        self.buttonCal.Bind(wx.EVT_BUTTON, self.__on_cal)
        self.textResult = wx.StaticText(self)

        self.buttonOk = wx.Button(self, wx.ID_OK, 'OK')
        self.buttonOk.Disable()
        self.buttonCancel = wx.Button(self, wx.ID_CANCEL, 'Cancel')

        self.buttonOk.Bind(wx.EVT_BUTTON, self.__on_close)
        self.buttonCancel.Bind(wx.EVT_BUTTON, self.__on_close)

        buttons = wx.StdDialogButtonSizer()
        buttons.AddButton(self.buttonOk)
        buttons.AddButton(self.buttonCancel)
        buttons.Realize()

        sizer = wx.GridBagSizer(10, 10)
        sizer.Add(title, pos=(0, 0), span=(1, 2),
                  flag=wx.ALIGN_CENTRE | wx.ALL, border=10)
        sizer.Add(text, pos=(1, 0), flag=wx.ALL, border=10)
        sizer.Add(self.textFreq, pos=(1, 1), flag=wx.ALL,
                  border=5)
        sizer.Add(self.buttonCal, pos=(2, 0), span=(1, 2),
                  flag=wx.ALIGN_CENTRE | wx.ALL | wx.EXPAND, border=10)
        sizer.Add(self.textResult, pos=(3, 0), span=(1, 2),
                  flag=wx.ALL, border=10)
        sizer.Add(buttons, pos=(4, 0), span=(1, 2),
                  flag=wx.ALL | wx.ALIGN_RIGHT, border=10)

        self.SetSizerAndFit(sizer)

    def __on_cal(self, _event):
        self.buttonCal.Disable()
        self.buttonOk.Disable()
        self.buttonCancel.Disable()
        self.textFreq.Disable()
        self.textResult.SetLabel("Calibrating...")
        self.callback(Cal.START)

    def __on_close(self, event):
        status = [Cal.CANCEL, Cal.OK][event.GetId() == wx.ID_OK]
        self.callback(status)
        self.EndModal(event.GetId())
        return

    def __enable_controls(self):
        self.buttonCal.Enable(True)
        self.buttonOk.Enable(True)
        self.buttonCancel.Enable(True)
        self.textFreq.Enable()

    def set_cal(self, cal):
        self.cal = cal
        self.__enable_controls()
        self.textResult.SetLabel("Correction (ppm): {0:.3f}".format(cal))

    def get_cal(self):
        return self.cal

    def reset_cal(self):
        self.set_cal(self.cal)

    def get_arg1(self):
        return self.textFreq.GetValue() class DialogSats(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent=parent, title='GPS Satellite Levels')
        self.parent = parent

        self.satLevel = SatLevel(self)

        self.textSats = wx.StaticText(self)
        self.__set_text(0, 0)

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

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.satLevel, 1, flag=wx.ALL | wx.EXPAND, border=5)
        sizer.Add(self.textSats, 0, flag=wx.ALL | wx.EXPAND, border=5)

        self.SetSizerAndFit(sizer)

    def __set_text(self, used, seen):
        self.textSats.SetLabel('Satellites: {} used, {} seen'.format(used,
                                                                     seen))

    def __on_close(self, _event):
        self.Unbind(wx.EVT_CLOSE)
        self.parent.dlgSats = None
        self.Close()

    def set_sats(self, sats):
        self.satLevel.set_sats(sats)
        used = sum(1 for sat in sats.values() if sat[1])
        self.__set_text(used, len(sats)) class DialogSmooth(wx.Dialog):
    POLL = 250

    def __init__(self, parent, spectrum, settings):
        self.spectrum = spectrum
        self.settings = settings
        self.smoothed = None

        wx.Dialog.__init__(self, parent=parent, title='Smooth Spectrum',
                           style=wx.RESIZE_BORDER | wx.CAPTION | wx.SYSTEM_MENU |
                           wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX | wx.CLOSE_BOX)

        self.queue = Queue.Queue()
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.__on_timer, self.timer)
        self.timer.Start(self.POLL)

        self.figure = matplotlib.figure.Figure(facecolor='white')
        self.canvas = FigureCanvas(self, -1, self.figure)
        settings = copy.copy(settings)
        settings.plotFunc = PlotFunc.NONE
        self.plot = Plotter(self.queue, self.figure, settings)

        textFunc = wx.StaticText(self, label='Window function')
        self.choiceFunc = wx.Choice(self, choices=WINFUNC[::2])
        self.choiceFunc.SetSelection(WINFUNC[::2].index(settings.smoothFunc))

        textRatio = wx.StaticText(self, label='Smoothing')
        self.slideRatio = wx.Slider(self, value=settings.smoothRatio,
                                    minValue=2, maxValue=100,
                                    style=wx.SL_INVERSE)

        buttonSmooth = wx.Button(self, label='Smooth')
        self.Bind(wx.EVT_BUTTON, self.__on_smooth, buttonSmooth)

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

        sizerGrid = wx.GridBagSizer(5, 5)
        sizerGrid.Add(self.canvas, pos=(0, 0), span=(10, 6),
                      flag=wx.EXPAND | wx.ALL, border=5)
        sizerGrid.Add(textFunc, pos=(1, 6), border=5)
        sizerGrid.Add(self.choiceFunc, pos=(2, 6), span=(1, 2),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(textRatio, pos=(3, 6),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(self.slideRatio, pos=(4, 6), span=(1, 2),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(buttonSmooth, pos=(5, 6), span=(1, 2),
                      flag=wx.ALL, border=5)
        sizerGrid.Add(sizerButtons, pos=(11, 6), span=(1, 2),
                      flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
        sizerGrid.AddGrowableCol(0)
        sizerGrid.AddGrowableRow(0)

        self.SetSizerAndFit(sizerGrid)

        self.__draw_plot(self.spectrum)

    def __on_timer(self, _event):
        self.timer.Stop()
        while not self.queue.empty():
            event = self.queue.get()
            status = event.data.get_status()

            if status == Event.DRAW:
                self.canvas.draw()

        self.timer.Start(self.POLL)

    def __on_smooth(self, _event):
        dlg = wx.BusyInfo('Please wait...')
        func = self.choiceFunc.GetStringSelection()
        ratio = self.slideRatio.GetValue()
        self.smoothed = smooth_spectrum(self.spectrum, func, ratio)
        self.__draw_plot(self.smoothed)
        self.buttonOk.Enable()
        dlg.Destroy()

    def __on_ok(self, _event):
        self.EndModal(wx.ID_OK)

    def __draw_plot(self, spectrum):
        extent = Extent(spectrum)
        self.plot.set_plot(spectrum, extent, False)

    def get_spectrum(self):
        return self.smoothed class DialogLog(wx.Dialog):
    def __init__(self, parent, log):
        wx.Dialog.__init__(self, parent=parent, title="Log")

        self.parent = parent
        self.log = log

        self.gridLog = grid.Grid(self)
        self.gridLog.CreateGrid(log.MAX_ENTRIES, 3)
        self.gridLog.SetRowLabelSize(0)
        self.gridLog.SetColLabelValue(0, "Time")
        self.gridLog.SetColLabelValue(1, "Level")
        self.gridLog.SetColLabelValue(2, "Event")
        self.gridLog.EnableEditing(False)

        textFilter = wx.StaticText(self, label='Level')
        self.choiceFilter = wx.Choice(self,
                                      choices=['All'] + self.log.TEXT_LEVEL)
        self.choiceFilter.SetSelection(0)
        self.choiceFilter.SetToolTipString('Filter log level')
        self.Bind(wx.EVT_CHOICE, self.__on_filter, self.choiceFilter)
        sizerFilter = wx.BoxSizer()
        sizerFilter.Add(textFilter, flag=wx.ALL, border=5)
        sizerFilter.Add(self.choiceFilter, flag=wx.ALL, border=5)

        buttonRefresh = wx.Button(self, wx.ID_ANY, label='Refresh')
        buttonRefresh.SetToolTipString('Refresh the log')
        buttonClose = wx.Button(self, wx.ID_CLOSE)
        self.Bind(wx.EVT_BUTTON, self.__on_refresh, buttonRefresh)
        self.Bind(wx.EVT_BUTTON, self.__on_close, buttonClose)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.gridLog, 1, flag=wx.ALL | wx.EXPAND, border=5)
        sizer.Add(sizerFilter, 0, flag=wx.ALL, border=5)
        sizer.Add(buttonRefresh, 0, flag=wx.ALL, border=5)
        sizer.Add(buttonClose, 0, flag=wx.ALL | wx.ALIGN_RIGHT, border=5)

        self.sizer = sizer
        self.__update_grid()
        self.SetSizer(sizer)

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

    def __on_filter(self, _event):
        selection = self.choiceFilter.GetSelection()
        if selection == 0:
            level = None
        else:
            level = selection - 1
        self.__update_grid(level)

    def __on_refresh(self, _event):
        self.__update_grid()

    def __on_close(self, _event):
        self.Unbind(wx.EVT_CLOSE)
        self.parent.dlgLog = None
        self.Close()

    def __update_grid(self, level=None):
        self.gridLog.ClearGrid()

        fontCell = self.gridLog.GetDefaultCellFont()
        fontSize = fontCell.GetPointSize()
        fontStyle = fontCell.GetStyle()
        fontWeight = fontCell.GetWeight()
        font = wx.Font(fontSize, wx.FONTFAMILY_MODERN, fontStyle,
                       fontWeight)

        i = 0
        for event in self.log.get(level):
            self.gridLog.SetCellValue(i, 0, format_time(event[0], True))
            self.gridLog.SetCellValue(i, 1, self.log.TEXT_LEVEL[event[1]])
            eventText = '\n'.join(textwrap.wrap(event[2], width=70))
            self.gridLog.SetCellValue(i, 2, eventText)
            self.gridLog.SetCellFont(i, 0, font)
            self.gridLog.SetCellFont(i, 1, font)
            self.gridLog.SetCellFont(i, 2, font)
            self.gridLog.SetCellAlignment(i, 0, wx.ALIGN_LEFT, wx.ALIGN_CENTRE)
            self.gridLog.SetCellAlignment(i, 1, wx.ALIGN_LEFT, wx.ALIGN_CENTRE)
            i += 1

        self.gridLog.AppendRows()
        self.gridLog.SetCellValue(i, 0, '#' * 18)
        self.gridLog.SetCellValue(i, 1, '#' * 5)
        self.gridLog.SetCellValue(i, 2, '#' * 80)
        self.gridLog.AutoSize()
        self.gridLog.DeleteRows(i)

        size = self.gridLog.GetBestSize()
        size.width += wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 10
        size.height = 400
        self.SetClientSize(size)
        self.sizer.Layout() EVENT_THREAD = wx.NewId() class Event(object):
    STARTING, STEPS, INFO, DATA, STOPPED, ERROR, FINISHED, PROCESSED, \
        CAL, LEVEL, UPDATED, DRAW, \
        DELAY_COUNT, DELAY_START, \
        LOC, LOC_RAW, LOC_WARN, LOC_ERR, LOC_SAT = range(19) class Log(object):
    MAX_ENTRIES = 50

    INFO, WARN, ERROR = range(3)
    TEXT_LEVEL = ['Info', 'Warn', 'Error']

    def __init__(self):
        self.log = []

    def add(self, text, level=None):
        if level is None:
            return
        entry = [time.time(), level, text]
        self.log.append(entry)

        while len(self.log) > self.MAX_ENTRIES:
            self.log.pop(0)

    def get(self, level):
        if level is None:
            return self.log

        filtered = []
        for entry in self.log:
            if entry[1] == level:
                filtered.append(entry)

        return filtered class EventTimer(wx.Timer):
    def __init__(self, parent, delay,
                 eventCount=Event.DELAY_COUNT, eventStart=Event.DELAY_START):
        wx.Timer.__init__(self)
        self.parent = parent
        self.delay = delay
        self.count = delay
        self.eventCount = eventCount
        self.eventStart = eventStart

        self.Start(1000)
        post_event(parent,
                   EventThread(self.eventCount, self.delay, self.count))

    def Notify(self):
        self.count -= 1
        post_event(self.parent,
                   EventThread(self.eventCount, self.delay, self.count))
        if self.count == 0:
            self.Stop()
            post_event(self.parent,
                       EventThread(self.eventStart)) def save_plot(filename, scanInfo, spectrum, location):
    data = [File.HEADER, {'Version': File.VERSION,
                          'Start': scanInfo.start,
                          'Stop': scanInfo.stop,
                          'Dwell': scanInfo.dwell,
                          'Nfft': scanInfo.nfft,
                          'Device': scanInfo.name,
                          'Gain': scanInfo.gain,
                          'LO': scanInfo.lo,
                          'Calibration': scanInfo.calibration,
                          'Tuner': scanInfo.tuner,
                          'Time': scanInfo.time,
                          'Latitude': scanInfo.lat,
                          'Longitude': scanInfo.lon,
                          'Description': scanInfo.desc,
                          'Spectrum': spectrum,
                          'Location': location}]

    handle = open(os.path.join(filename), 'wb')
    handle.write(json.dumps(data, indent=4))
    handle.close() def export_plot(filename, exportType, spectrum):
    handle = open(filename, 'wb')
    if exportType == File.PlotType.CSV:
        export_csv(handle, spectrum)
    elif exportType == File.PlotType.GNUPLOT:
        export_plt(handle, spectrum)
    elif exportType == File.PlotType.FREEMAT:
        export_freemat(handle, spectrum)
    elif exportType == File.PlotType.WWB:
        export_wwb(handle, spectrum)
    handle.close() def export_cont(handle, filename, spectrum):
    if handle is None and filename is not None:
        handle = open(filename, 'wb')
        export_csv(handle, None)
    else:
        export_csv(handle, spectrum, False)

    return handle def open_plot(dirname, filename):
    pickle = True
    error = False
    dwell = 0.131
    nfft = 1024
    name = None
    gain = None
    lo = None
    calibration = None
    tuner = 0
    spectrum = OrderedDict()
    time = None
    lat = None
    lon = None
    desc = ''
    location = OrderedDict()

    path = os.path.join(dirname, filename)
    if not os.path.exists(path):
        return None, None, None
    handle = open(path, 'rb')
    try:
        header = cPickle.load(handle)
    except cPickle.UnpicklingError:
        pickle = False
    except EOFError:
        pickle = False

    if pickle:
        try:
            _version = cPickle.load(handle)
            start = cPickle.load(handle)
            stop = cPickle.load(handle)
            spectrum[1] = {}
            spectrum[1] = cPickle.load(handle)
        except pickle.PickleError:
            error = True
    else:
        try:
            handle.seek(0)
            data = json.loads(handle.read())
            header = data[0]
            version = data[1]['Version']
            start = data[1]['Start']
            stop = data[1]['Stop']
            if version > 1:
                dwell = data[1]['Dwell']
                nfft = data[1]['Nfft']
            if version > 2:
                name = data[1]['Device']
                gain = data[1]['Gain']
                lo = data[1]['LO']
                calibration = data[1]['Calibration']
            if version > 4:
                tuner = data[1]['Tuner']
            if version > 5:
                time = data[1]['Time']
                lat = data[1]['Latitude']
                lon = data[1]['Longitude']
            if version < 7:
                spectrum[1] = {}
                for f, p in data[1]['Spectrum'].iteritems():
                    spectrum[1][float(f)] = p
            else:
                for t, s in data[1]['Spectrum'].iteritems():
                    spectrum[float(t)] = {}
                    for f, p in s.iteritems():
                        spectrum[float(t)][float(f)] = p
            if version > 7:
                desc = data[1]['Description']
            if version > 8:
                location = {}
                for t, l in data[1]['Location'].iteritems():
                    location[float(t)] = l

        except ValueError:
            error = True
        except KeyError:
            error = True

    handle.close()

    if error or header != File.HEADER:
        wx.MessageBox('Invalid or corrupted file', 'Warning',
                      wx.OK | wx.ICON_WARNING)
        return None, None, None

    scanInfo = ScanInfo()
    scanInfo.start = start
    scanInfo.stop = stop
    scanInfo.dwell = dwell
    scanInfo.nfft = nfft
    scanInfo.name = name
    scanInfo.gain = gain
    scanInfo.lo = lo
    scanInfo.calibration = calibration
    scanInfo.tuner = tuner
    scanInfo.time = time
    scanInfo.lat = lat
    scanInfo.lon = lon
    scanInfo.desc = desc

    spectrum = sort_spectrum(spectrum)

    return scanInfo, spectrum, location class ScanInfo(object):
    start = None
    stop = None
    dwell = None
    nfft = None
    name = None
    gain = None
    lo = None
    calibration = None
    tuner = 0
    time = None
    timeFirst = None
    timeLast = None
    lat = None
    lon = None
    desc = ''

    def set_from_settings(self, settings):
        self.start = settings.start
        self.stop = settings.stop
        self.dwell = settings.dwell
        self.nfft = settings.nfft
        device = settings.devicesRtl[settings.indexRtl]
        if device.isDevice:
            self.name = device.name
        else:
            self.name = device.server + ":" + str(device.port)
        self.gain = device.gain
        self.lo = device.lo
        self.calibration = device.calibration
        self.tuner = device.tuner

    def set_to_settings(self, settings):
        settings.start = self.start
        settings.stop = self.stop
        settings.dwell = self.dwell
        settings.nfft = self.nfft def export_image(filename, format, figure, settings):
    oldSize = figure.get_size_inches()
    oldDpi = figure.get_dpi()
    figure.set_size_inches((settings.exportWidth, settings.exportHeight))
    figure.set_dpi(settings.exportDpi)

    canvas = FigureCanvasAgg(figure)
    canvas.draw()
    renderer = canvas.get_renderer()
    if matplotlib.__version__ >= '1.2':
        buf = renderer.buffer_rgba()
    else:
        buf = renderer.buffer_rgba(0, 0)
    size = canvas.get_width_height()
    image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
    image = image.convert('RGB')
    ext = File.get_type_ext(format, File.Types.IMAGE)
    image.save(filename, format=ext[1::], dpi=(settings.exportDpi,
                                               settings.exportDpi))

    figure.set_size_inches(oldSize)
    figure.set_dpi(oldDpi) def export_map(filename, exportType, bounds, image, xyz):
    if exportType == File.GeoType.KMZ:
        export_kmz(filename, bounds, image)
    elif exportType == File.GeoType.CSV:
        export_xyz(filename, xyz)
    else:
        export_map_image(filename, exportType, image) def extension_add(fileName, index, fileType):
    _name, extCurrent = os.path.splitext(fileName)
    ext = File.get_type_ext(index, fileType)
    if extCurrent != ext:
        return fileName + ext

    return fileName class File(object):
    class Types(object):
        SAVE, PLOT, IMAGE, GEO, GMAP, TRACK, CONT = range(7)

    class SaveType(object):
        RFS = 0

    class PlotType(object):
        CSV, GNUPLOT, FREEMAT, WWB = range(4)

    class ImageType(object):
        BMP, EPS, GIF, JPEG, PDF, PNG, PPM, TIFF = range(8)

    class GeoType(object):
        KMZ, CSV, BMP, EPS, GIF, JPEG, PDF, PNG, PPM, TIFF = range(10)

    class GMapType(object):
        HTML = 0

    class TrackType(object):
        GPX = 0

    SAVE = [''] * 1
    SAVE[SaveType.RFS] = 'RTLSDR frequency scan (*.rfs)|*.rfs'

    PLOT = [''] * 4
    PLOT[PlotType.CSV] = "CSV table (*.csv)|*.csv"
    PLOT[PlotType.GNUPLOT] = "gnuplot script (*.plt)|*.plt"
    PLOT[PlotType.FREEMAT] = "FreeMat script (*.m)|*.m"
    PLOT[PlotType.WWB] = "Wireless Workbench (*.sdb2)|*.sdb2"

    IMAGE = [''] * 8
    IMAGE[ImageType.BMP] = 'Bitmap image (*.bmp)|*.bmp'
    IMAGE[ImageType.EPS] = 'Encapsulated PostScript (*.eps)|*.eps'
    IMAGE[ImageType.GIF] = 'GIF image (*.gif)|*.gif'
    IMAGE[ImageType.JPEG] = 'JPEG image (*.jpeg)|*.jpeg'
    IMAGE[ImageType.PDF] = 'Portable Document (*.pdf)|*.pdf'
    IMAGE[ImageType.PNG] = 'Portable Network Graphics Image (*.png)|*.png'
    IMAGE[ImageType.PPM] = 'Portable Pixmap image (*.ppm)|*.ppm'
    IMAGE[ImageType.TIFF] = 'Tagged Image File (*.tiff)|*.tiff'

    GEO = [''] * 10
    GEO[GeoType.BMP] = 'Bitmap image (*.bmp)|*.bmp'
    GEO[GeoType.CSV] = 'CSV Table (*.csv)|*.csv'
    GEO[GeoType.EPS] = 'Encapsulated PostScript (*.eps)|*.eps'
    GEO[GeoType.GIF] = 'GIF image (*.gif)|*.gif'
    GEO[GeoType.JPEG] = 'JPEG image (*.jpeg)|*.jpeg'
    GEO[GeoType.KMZ] = 'Google Earth (*.kmz)|*.kmz'
    GEO[GeoType.PDF] = 'Portable Document (*.pdf)|*.pdf'
    GEO[GeoType.PNG] = 'Portable Network Graphics Image (*.png)|*.png'
    GEO[GeoType.PPM] = 'Portable Pixmap image (*.ppm)|*.ppm'
    GEO[GeoType.TIFF] = 'Tagged Image File (*.tiff)|*.tiff'

    GMAP = [''] * 1
    GMAP[GMapType.HTML] = 'HTML Document (*.html)|*.html'

    TRACK = [''] * 1
    TRACK[TrackType.GPX] = 'GPX track (*.gpx)|*.gpx'

    CONT = [''] * 1
    CONT[PlotType.CSV] = PLOT[PlotType.CSV]

    HEADER = APP_NAME
    VERSION = 9

    @staticmethod
    def __get_types(type):
        return [File.SAVE, File.PLOT, File.IMAGE,
                File.GEO, File.GMAP, File.TRACK, File.CONT][type]

    @staticmethod
    def get_type_ext(index, type=Types.PLOT):
        types = File.__get_types(type)
        filter = types[index]
        delim = filter.index('|*')
        return filter[delim + 2:]

    @staticmethod
    def get_type_filters(type=Types.PLOT):
        types = File.__get_types(type)

        filters = ''
        length = len(types)
        for i in xrange(length):
            filters += types[i]
            if i < length - 1:
                filters += '|'

        return filters

    @staticmethod
    def get_type_pretty(type):
        types = File.__get_types(type)

        pretty = ''
        length = len(types)
        for i in xrange(length):
            pretty += File.get_type_ext(i, type)
            if i < length - 2:
                pretty += ', '
            elif i < length - 1:
                pretty += ' or '

        return pretty

    @staticmethod
    def get_type_index(extension, type=Types.PLOT):
        exports = File.__get_types(type)
        for i in xrange(len(exports)):
            if extension == File.get_type_ext(i, type):
                return i

        return -1 def run_file(runFile):
    if os.name == 'nt':
        os.startfile(runFile)
        return True
    elif sys.platform.startswith('darwin'):
        return not subprocess.call(('open', runFile))
    elif os.name == 'posix':
        subprocess.call(('xdg-open', runFile))
        return True def export_gpx(filename, locations, name):
    handle = open(filename, 'wb')

    header = ('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n'
              '<gpx xmlns="http://www.topografix.com/GPX/1/1" creator="{}" version="1.1">\n'
              '\t<trk>\n'
              '\t\t<name>{}</name>\n'
              '\t\t<trkseg>\n').format(name, 'test name')
    handle.write(header)

    for location in sorted(locations.items()):
        timeStamp = format_iso_time(location[0])
        lat = location[1][0]
        lon = location[1][1]
        alt = location[1][2]
        point = ('\t\t\t<trkpt lat="{}" lon="{}">\n'
                 '\t\t\t\t<ele>{}</ele>\n'
                 '\t\t\t\t<time>{}</time>\n'
                 '\t\t\t</trkpt>\n').format(lat, lon, alt, timeStamp)
        handle.write(point)

    footer = ('\t\t</trkseg>\n'
              '\t</trk>\n'
              '</gpx>\n')
    handle.write(footer)
    handle.close() class Backups(object):
    PREFIX = 'rsba_'

    def __init__(self):
        self.homeDir = os.path.expanduser('~')
        self.homeDir = os.path.join(self.homeDir, '.rtlsdr_scanner')
        if not os.path.exists(self.homeDir):
            os.mkdir(self.homeDir)
        self.thread = None
        self.backup = None
        self.tempFd, self.tempFile = tempfile.mkstemp(prefix=self.PREFIX,
                                                      dir=self.homeDir)
        self.backups = self.__get()

    def __get(self):
        files = []
        backups = glob.glob(self.homeDir + '/' + self.PREFIX + '*')
        if self.tempFile in backups:
            backups.remove(self.tempFile)

        for backup in backups:
            fTime = datetime.datetime.utcfromtimestamp(os.path.getmtime(backup))
            fSize = os.path.getsize(backup)
            if fSize:
                files.append((backup, fTime, fSize))
            else:
                try:
                    os.remove(backup)
                except:
                    pass
        files.sort(lambda x, y: cmp(x[1], y[1]), reverse=True)

        return files

    def __save(self, data):
        handle = open(self.tempFile, 'wb')
        cPickle.dump(data, handle)
        handle.close()
        self.thread = None

    def set(self, backup):
        self.backup = backup

    def save(self, scanInfo, spectrum, location):
        if self.thread is None:
            data = scanInfo, spectrum, location
            self.thread = threading.Thread(target=self.__save, args=(data,),
                                           name='Backup')
            self.thread.start()

    def load(self, index):
        backup = self.backups[index][0]
        handle = open(backup, 'rb')
        data = cPickle.load(handle)
        handle.close()

        return data

    def delete(self, index):
        backup = self.backups[index][0]
        os.remove(backup)
        self.backups = self.__get()

    def close(self):
        os.close(self.tempFd)
        os.remove(self.tempFile) class PanelGraph(wx.Panel):

    def __init__(self, panel, notify, settings, status, remoteControl):
        self.panel = panel
        self.notify = notify
        self.plot = None
        self.settings = settings
        self.status = status
        self.remoteControl = remoteControl
        self.spectrum = None
        self.isLimited = None
        self.limit = None
        self.extent = None
        self.annotate = None

        self.isDrawing = False

        self.toolTip = wx.ToolTip('')

        self.mouseSelect = None
        self.mouseZoom = None
        self.measureTable = None

        self.background = None

        self.selectStart = None
        self.selectEnd = None

        self.menuClearSelect = []

        self.measure = None
        self.show = None

        self.doDraw = False

        wx.Panel.__init__(self, panel)

        self.figure = matplotlib.figure.Figure(facecolor='white')
        self.figure.set_size_inches(0, 0)
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.canvas.SetToolTip(self.toolTip)

        self.measureTable = PanelMeasure(self, settings)

        self.toolbar = NavigationToolbar(self.canvas, self, settings,
                                         self.__hide_overlay)
        self.toolbar.Realize()

        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(self.canvas, 1, wx.EXPAND)
        vbox.Add(self.measureTable, 0, wx.EXPAND)
        vbox.Add(self.toolbar, 0, wx.EXPAND)
        self.SetSizer(vbox)
        vbox.Fit(self)

        self.create_plot()

        self.canvas.mpl_connect('button_press_event', self.__on_press)
        self.canvas.mpl_connect('figure_enter_event', self.__on_enter)
        self.canvas.mpl_connect('axes_leave_event', self.__on_leave)
        self.canvas.mpl_connect('motion_notify_event', self.__on_motion)
        self.canvas.mpl_connect('draw_event', self.__on_draw)
        self.Bind (wx.EVT_IDLE, self.__on_idle)
        self.Bind(wx.EVT_SIZE, self.__on_size)

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.__on_timer, self.timer)
  
    def __set_fonts(self):
        axes = self.plot.get_axes()
        if axes is not None:
            axes.xaxis.label.set_size('small')
            axes.yaxis.label.set_size('small')
            if self.settings.display == Display.SURFACE:
                axes.zaxis.label.set_size('small')
            axes.tick_params(axis='both', which='major', labelsize='small')
        axes = self.plot.get_axes_bar()
        if axes is not None:
            axes.tick_params(axis='both', which='major', labelsize='small')

    def __enable_menu(self, state):
        for menu in self.menuClearSelect:
            menu.Enable(state)

    def __on_press(self, event):
        if self.settings.clickTune and matplotlib.__version__ >= '1.2' and event.dblclick:
            frequency = int(event.xdata * 1e6)
            self.remoteControl.tune(frequency)
        elif isinstance(self.plot, PlotterPreview):
            self.plot.to_front()

    def __on_enter(self, _event):
        self.toolTip.Enable(False)

    def __on_leave(self, _event):
        self.toolTip.Enable(True)
        self.status.set_info('', level=None)

    def __on_motion(self, event):
        axes = self.plot.get_axes()
        axesBar = self.plot.get_axes_bar()
        xpos = event.xdata
        ypos = event.ydata
        text = ""

        if (xpos is None or ypos is None or
                self.spectrum is None or event.inaxes is None):
            spectrum = None
        elif event.inaxes == axesBar:
            spectrum = None
            level = self.plot.get_bar().norm.inverse(ypos)
            text = "{}".format(format_precision(self.settings,
                                                level=level))
        elif self.settings.display == Display.PLOT:
            timeStamp = max(self.spectrum)
            spectrum = self.spectrum[timeStamp]
        elif self.settings.display == Display.SPECT:
            timeStamp = num2epoch(ypos)
            if timeStamp in self.spectrum:
                spectrum = self.spectrum[timeStamp]
            else:
                nearest = min(self.spectrum.keys(),
                              key=lambda k: abs(k - timeStamp))
                spectrum = self.spectrum[nearest]
        elif self.settings.display == Display.SURFACE:
            spectrum = None
            coords = axes.format_coord(event.xdata,
                                       event.ydata)
            match = re.match('x=([-|0-9|\.]+).*y=([0-9|\:]+).*z=([-|0-9|\.]+)',
                             coords)
            if match is not None and match.lastindex == 3:
                freq = float(match.group(1))
                level = float(match.group(3))
                text = "{}, {}".format(*format_precision(self.settings,
                                                         freq, level))
        else:
            spectrum = None

        if spectrum is not None and len(spectrum) > 0:
            x = min(spectrum.keys(), key=lambda freq: abs(freq - xpos))
            if min(spectrum.keys(), key=float) <= xpos <= max(spectrum.keys(),
                                                              key=float):
                y = spectrum[x]
                text = "{}, {}".format(*format_precision(self.settings, x, y))
            else:
                text = format_precision(self.settings, xpos)

            markers = find_artists(self.figure, 'peak')
            markers.extend(find_artists(self.figure, 'peakThres'))
            hit = False
            for marker in markers:
                if isinstance(marker, Line2D):
                    location = marker.get_path().vertices[0]
                    markX, markY = axes.transData.transform(location)
                    dist = abs(math.hypot(event.x - markX, event.y - markY))
                    if dist <= 5:
                        if self.settings.display == Display.PLOT:
                            tip = "{}, {}".format(*format_precision(self.settings,
                                                                    location[0],
                                                                    location[1]))
                        else:
                            tip = "{}".format(format_precision(self.settings,
                                                               location[0]))
                        self.toolTip.SetTip(tip)
                        hit = True
                        break
            self.toolTip.Enable(hit)

        self.status.set_info(text, level=None)

    def __on_size(self, event):
        ppi = wx.ScreenDC().GetPPI()
        size = [float(v) for v in self.canvas.GetSize()]
        width = size[0] / ppi[0]
        height = size[1] / ppi[1]
        self.figure.set_figwidth(width)
        self.figure.set_figheight(height)
        self.figure.set_dpi(ppi[0])
        event.Skip()

    def __on_draw(self, _event):
        axes = self.plot.get_axes()
        if axes is not None:
            self.background = self.canvas.copy_from_bbox(axes.bbox)
            self.__draw_overlay()

    def __on_idle(self, _event):
        if self.doDraw and self.plot.get_plot_thread() is None:
            self.__hide_overlay()
            self.doDraw = False
            if os.name == 'nt':
                threading.Thread(target=self.__draw_canvas, name='Draw').start()
            else:
                self.__draw_canvas()

    def __on_timer(self, _event):
        self.timer.Stop()
        self.set_plot(None, None, None, None, self.annotate)

    def __draw_canvas(self):
        try:
            self.isDrawing = True
            self.canvas.draw()
        except wx.PyDeadObjectError:
            pass

        self.isDrawing = False
        wx.CallAfter(self.status.set_busy, False)

    def __draw_overlay(self):
        if self.background is not None:
            self.canvas.restore_region(self.background)
            self.__draw_select()
            self.draw_measure()
            axes = self.plot.get_axes()
            if axes is not None:
                self.canvas.blit(axes.bbox)

    def __draw_select(self):
        if self.selectStart is not None and self.selectEnd is not None:
            self.mouseSelect.draw(self.selectStart, self.selectEnd)

    def __hide_overlay(self):
        if self.plot is not None:
            self.plot.hide_measure()
        self.__hide_select()

    def __hide_select(self):
        if self.mouseSelect is not None:
            self.mouseSelect.hide()

    def create_plot(self):
        if self.plot is not None:
            self.plot.close()

        self.toolbar.set_auto(True)

        if self.settings.display == Display.PLOT:
            self.plot = Plotter(self.notify, self.figure, self.settings)
        elif self.settings.display == Display.SPECT:
            self.plot = Spectrogram(self.notify, self.figure, self.settings)
        elif self.settings.display == Display.SURFACE:
            self.plot = Plotter3d(self.notify, self.figure, self.settings)
        elif self.settings.display == Display.STATUS:
            self.plot = PlotterStatus(self.notify, self.figure, self.settings)
        elif self.settings.display == Display.TIMELINE:
            self.plot = PlotterTime(self.notify, self.figure, self.settings)
        elif self.settings.display == Display.PREVIEW:
            self.plot = PlotterPreview(self.notify, self.figure, self.settings)
            self.plot.set_window(self)

        self.__set_fonts()

        self.toolbar.set_plot(self.plot)
        self.toolbar.set_type(self.settings.display)
        self.measureTable.set_type(self.settings.display)

        self.set_plot_title()
        self.figure.subplots_adjust(top=0.85)
        self.redraw_plot()
        self.plot.scale_plot(True)
        self.mouseZoom = MouseZoom(self.toolbar, plot=self.plot,
                                   callbackHide=self.__hide_overlay)
        self.mouseSelect = MouseSelect(self.plot, self.on_select,
                                       self.on_selected)
        self.measureTable.show(self.settings.showMeasure)
        self.panel.SetFocus()

    def on_select(self):
        self.hide_measure()

    def on_selected(self, start, end):
        self.__enable_menu(True)
        self.selectStart = start
        self.selectEnd = end
        self.measureTable.set_selected(self.spectrum, start, end)

    def add_menu_clear_select(self, menu):
        self.menuClearSelect.append(menu)
        menu.Enable(False)

    def draw(self):
        self.doDraw = True

    def show_measure_table(self, show):
        self.measureTable.show(show)
        self.Layout()

    def set_plot(self, spectrum, isLimited, limit, extent, annotate=False):
        if spectrum is not None and extent is not None:
            if isLimited is not None and limit is not None:
                self.spectrum = copy.copy(spectrum)
                self.extent = extent
                self.annotate = annotate
                self.isLimited = isLimited
                self.limit = limit

        if self.plot.get_plot_thread() is None and not self.isDrawing:
            self.timer.Stop()
            self.measureTable.set_selected(self.spectrum, self.selectStart,
                                           self.selectEnd)

            if isLimited:
                self.spectrum = reduce_points(spectrum, limit)

            self.status.set_busy(True)
            self.plot.set_plot(self.spectrum, self.extent, annotate)
            if self.settings.display == Display.PREVIEW:
                self.status.set_busy(False)

        else:
            self.timer.Start(200, oneShot=True)

    def set_plot_title(self):
        if len(self.settings.devicesRtl) > 0:
            gain = self.settings.devicesRtl[self.settings.indexRtl].gain
        else:
            gain = 0
        self.plot.set_title("Frequency Spectrogram\n{} - {} MHz,"
                            " gain = {}dB".format(self.settings.start,
                                                  self.settings.stop, gain))

    def redraw_plot(self):
        if self.spectrum is not None:
            self.set_plot(self.spectrum,
                          self.settings.pointsLimit,
                          self.settings.pointsMax,
                          self.extent, self.settings.annotate)

    def set_grid(self, on):
        self.plot.set_grid(on)

    def set_selected(self, start, end):
        self.selectStart = start
        self.selectEnd = end
        self.__draw_select()

    def hide_toolbar(self, hide):
        self.toolbar.Show(not hide)

    def hide_measure(self):
        if self.plot is not None:
            self.plot.hide_measure()

    def draw_measure(self):
        if self.measure is not None and self.measure.is_valid():
            self.plot.draw_measure(self.measure, self.show)

    def update_measure(self, measure=None, show=None):
        if not measure and not show:
            self.measureTable.update_measure()
        else:
            self.measure = measure
            self.show = show
            self.__draw_overlay()

    def get_figure(self):
        return self.figure

    def get_axes(self):
        return self.plot.get_axes()

    def get_canvas(self):
        return self.canvas

    def get_toolbar(self):
        return self.toolbar

    def get_mouse_select(self):
        return self.mouseSelect

    def scale_plot(self, force=False):
        self.plot.scale_plot(force)

    def clear_plots(self):
        self.plot.clear_plots()
        self.spectrum = None
        self.doDraw = True

    def clear_selection(self):
        self.measure = None
        self.measureTable.clear_measurement()
        self.selectStart = None
        self.selectEnd = None
        self.mouseSelect.clear()
        self.__enable_menu(False)

    def close(self):
        self.plot.close()
        close_modeless() class ThreadScan(threading.Thread):
    def __init__(self, notify, queue, sdr, settings, device, samples, isCal):
        threading.Thread.__init__(self)
        self.name = 'Scan'
        self.notify = notify
        self.queue = queue
        self.sdr = sdr
        self.fstart = settings.start * 1e6
        self.fstop = settings.stop * 1e6
        self.samples = int(samples)
        self.isCal = isCal
        self.indexRtl = settings.indexRtl
        self.isDevice = settings.devicesRtl[device].isDevice
        self.server = settings.devicesRtl[device].server
        self.port = settings.devicesRtl[device].port
        self.gain = settings.devicesRtl[device].gain
        self.lo = settings.devicesRtl[device].lo * 1e6
        self.offset = settings.devicesRtl[device].offset
        self.cancel = False

        post_event(self.notify, EventThread(Event.STARTING))
        steps = int((self.__f_stop() - self.__f_start()) / self.__f_step())
        post_event(self.notify, EventThread(Event.STEPS, steps))
        self.start()

    def __f_start(self):
        return self.fstart - self.offset - BANDWIDTH

    def __f_stop(self):
        return self.fstop + self.offset + BANDWIDTH * 2

    def __f_step(self):
        return BANDWIDTH / 2

    def __rtl_setup(self):

        if self.sdr is not None:
            return

        tuner = 0

        if self.isDevice:
            try:
                self.sdr = rtlsdr.RtlSdr(self.indexRtl)
                self.sdr.set_sample_rate(SAMPLE_RATE)
                self.sdr.set_gain(self.gain)
                tuner = self.sdr.get_tuner_type()
            except IOError as error:
                post_event(self.notify, EventThread(Event.ERROR,
                                                    0, error.message))
        else:
            try:
                self.sdr = RtlTcp(self.server, self.port, self.notify)
                self.sdr.set_sample_rate(SAMPLE_RATE)
                self.sdr.set_gain(self.gain)
                tuner = self.sdr.get_tuner_type()
            except IOError as error:
                post_event(self.notify, EventThread(Event.ERROR,
                                                    0, error))

        return tuner

    def run(self):
        tuner = self.__rtl_setup()
        if self.sdr is None:
            return
        post_event(self.notify, EventThread(Event.INFO, None, tuner))

        freq = self.__f_start()
        timeStamp = math.floor(time.time())
        while freq <= self.__f_stop():
            if self.cancel:
                post_event(self.notify, EventThread(Event.STOPPED))
                self.rtl_close()
                return
            try:
                scan = self.rtl_scan(freq)
                if len(scan):
                    self.queue.put([freq, (timeStamp, scan)])
                    post_event(self.notify, EventThread(Event.DATA))
                else:
                    post_event(self.notify, EventThread(Event.ERROR, 0,
                                                        'No samples returned'))
                    return
            except (AttributeError, MemoryError, TypeError) as error:
                post_event(self.notify, EventThread(Event.ERROR,
                                                    0, error.message))
                return
            except (IOError, OSError) as error:
                if self.sdr is not None:
                    self.rtl_close()
                post_event(self.notify, EventThread(Event.ERROR,
                                                    0, error.message))
                return

            freq += self.__f_step()

        post_event(self.notify, EventThread(Event.FINISHED, 0, None))

        if self.isCal:
            post_event(self.notify, EventThread(Event.CAL))

    def abort(self):
        self.cancel = True

    def rtl_scan(self, freq):
        self.sdr.set_center_freq(freq + self.lo)
        capture = self.sdr.read_samples(self.samples)

        return capture

    def rtl_close(self):
        self.sdr.close()

    def get_sdr(self):
        return self.sdr def update_spectrum(notify, lock, start, stop, data, offset,
                    spectrum, average, alertLevel=None):
    with lock:
        updated = False
        if average:
            if len(spectrum) > 0:
                timeStamp = min(spectrum)
            else:
                timeStamp = data[0]
        else:
            timeStamp = data[0]

        freqCentre = data[1]
        scan = data[2]

        upperStart = freqCentre + offset
        upperEnd = freqCentre + offset + BANDWIDTH / 2
        lowerStart = freqCentre - offset - BANDWIDTH / 2
        lowerEnd = freqCentre - offset

        if timeStamp not in spectrum:
            spectrum[timeStamp] = OrderedDict()

        for freq in scan:
            if start <= freq < stop:
                power = 10 * math.log10(scan[freq])
                if upperStart <= freq * 1e6 <= upperEnd or \
                   lowerStart <= freq * 1e6 <= lowerEnd:
                    if freq in spectrum[timeStamp]:
                        spectrum[timeStamp][freq] = \
                            (spectrum[timeStamp][freq] + power) / 2
                        if alertLevel is not None and (spectrum[timeStamp][freq] >
                                                       alertLevel):
                            post_event(notify, EventThread(Event.LEVEL))
                        updated = True
                    else:
                        spectrum[timeStamp][freq] = power
                        updated = True

        if updated:
            spectrum[timeStamp] = OrderedDict(sorted(spectrum[timeStamp].items()))

    post_event(notify, EventThread(Event.UPDATED, None, updated)) class ThreadProcess(threading.Thread):
    def __init__(self, notify, freq, scan, cal, levelOff, nfft, overlap, winFunc):
        threading.Thread.__init__(self)
        self.name = 'ThreadProcess'
        self.notify = notify
        self.freq = freq
        self.scan = scan
        self.cal = cal
        self.levelOff = math.pow(10, levelOff / 10.0)
        self.nfft = nfft
        self.overlap = overlap
        self.winFunc = winFunc
        self.window = matplotlib.numpy.hamming(nfft)

    def run(self):
        spectrum = {}
        timeStamp = self.scan[0]
        samples = self.scan[1]
        pos = WINFUNC[::2].index(self.winFunc)
        function = WINFUNC[1::2][pos]

        powers, freqs = matplotlib.mlab.psd(samples,
                                            NFFT=self.nfft,
                                            Fs=SAMPLE_RATE / 1e6,
                                            window=function(self.nfft))
        for freqPsd, pwr in itertools.izip(freqs, powers):
            xr = freqPsd + (self.freq / 1e6)
            xr = xr + (xr * self.cal / 1e6)
            spectrum[xr] = pwr * self.levelOff
        post_event(self.notify, EventThread(Event.PROCESSED,
                                            (timeStamp, self.freq, spectrum))) class Settings(object):
    def __init__(self, load=True):
        self.cfg = None

        self.saveWarn = True
        self.backup = True
        self.fileHistory = wx.FileHistory(5)

        self.dirScans = "."
        self.dirExport = "."

        self.display = Display.PLOT

        self.annotate = True
        self.peaks = False
        self.peaksThres = -30

        self.retainScans = True
        self.retainMax = 20
        self.fadeScans = True
        self.lineWidth = 0.4
        self.colourMapUse = True
        self.colourMap = 'jet'
        self.background = '#f0f0f0'
        self.wireframe = False
        self.pointsLimit = False
        self.pointsMax = 5000
        self.grid = True
        self.plotFunc = PlotFunc.NONE
        self.smoothFunc = 'Hamming'
        self.smoothRatio = 50

        self.clickTune = True

        self.precisionFreq = 6
        self.precisionLevel = 2

        self.compareOne = True
        self.compareTwo = True
        self.compareDiff = True

        self.start = 87
        self.stop = 108
        self.mode = Mode.SINGLE
        self.dwell = 0.1
        self.nfft = 1024
        self.scanDelay = 0
        self.overlap = 0.0
        self.winFunc = "Hamming"

        self.startOption = 0
        self.stopOption = 0

        self.liveUpdate = False
        self.calFreq = 1575.42
        self.autoF = True
        self.autoL = True
        self.autoT = True

        self.showMeasure = True

        self.alert = False
        self.alertLevel = -20

        self.gps = False
        self.gpsRetry = False

        self.exportWidth = 8
        self.exportHeight = 4.5
        self.exportDpi = 600

        self.devicesRtl = []
        self.indexRtl = 0
        self.devicesGps = []
        self.indexGps = 0

        if load:
            self.__load()

    def __clear_servers(self):
        self.cfg.SetPath("/DevicesRTL")
        group = self.cfg.GetFirstGroup()
        while group[0]:
            key = "/DevicesRTL/" + group[1]
            self.cfg.SetPath(key)
            if not self.cfg.ReadBool('isDevice', True):
                self.cfg.DeleteGroup(key)
            self.cfg.SetPath("/DevicesRTL")
            group = self.cfg.GetNextGroup(group[2])

    def __load_devices_rtl(self):
        self.cfg.SetPath("/DevicesRTL")
        group = self.cfg.GetFirstGroup()
        while group[0]:
            self.cfg.SetPath("/DevicesRTL/" + group[1])
            device = DeviceRTL()
            device.name = group[1]
            device.serial = self.cfg.Read('serial', '')
            device.isDevice = self.cfg.ReadBool('isDevice', True)
            device.server = self.cfg.Read('server', 'localhost')
            device.port = self.cfg.ReadInt('port', 1234)
            device.gain = self.cfg.ReadFloat('gain', 0)
            device.calibration = self.cfg.ReadFloat('calibration', 0)
            device.lo = self.cfg.ReadFloat('lo', 0)
            device.offset = self.cfg.ReadFloat('offset', 250e3)
            device.tuner = self.cfg.ReadInt('tuner', 0)
            device.levelOff = self.cfg.ReadFloat('levelOff', 0)
            self.devicesRtl.append(device)
            self.cfg.SetPath("/DevicesRTL")
            group = self.cfg.GetNextGroup(group[2])

    def __load_devices_gps(self):
        self.devicesGps = []
        self.cfg.SetPath("/DevicesGPS")
        group = self.cfg.GetFirstGroup()
        while group[0]:
            self.cfg.SetPath("/DevicesGPS/" + group[1])
            device = DeviceGPS()
            device.name = group[1]
            device.type = self.cfg.ReadInt('type', device.type)
            device.resource = self.cfg.Read('resource', device.resource)
            device.baud = self.cfg.ReadInt('baud', device.baud)
            device.bytes = self.cfg.ReadInt('bytes', device.bytes)
            device.parity = self.cfg.Read('parity', device.parity)
            device.stops = self.cfg.ReadInt('stops', device.stops)
            device.soft = self.cfg.ReadBool('soft', device.soft)
            self.devicesGps.append(device)
            self.cfg.SetPath("/DevicesGPS")
            group = self.cfg.GetNextGroup(group[2])

    def __save_devices_rtl(self):
        self.__clear_servers()

        if self.devicesRtl:
            for device in self.devicesRtl:
                if device.isDevice:
                    name = device.name
                else:
                    name = "{}:{}".format(device.server, device.port)
                self.cfg.SetPath("/DevicesRTL/" + format_device_rtl_name(name))
                self.cfg.Write('serial', device.serial)
                self.cfg.WriteBool('isDevice', device.isDevice)
                self.cfg.Write('server', device.server)
                self.cfg.WriteInt('port', device.port)
                self.cfg.WriteFloat('gain', device.gain)
                self.cfg.WriteFloat('lo', device.lo)
                self.cfg.WriteFloat('calibration', device.calibration)
                self.cfg.WriteFloat('offset', device.offset)
                self.cfg.WriteInt('tuner', device.tuner)
                self.cfg.WriteFloat('levelOff', device.levelOff)

    def __save_devices_gps(self):
        self.cfg.DeleteGroup('/DevicesGPS')
        for device in self.devicesGps:
            self.cfg.SetPath("/DevicesGPS/" + device.name)
            self.cfg.WriteInt('type', device.type)
            self.cfg.Write('resource', device.resource)
            self.cfg.WriteInt('baud', device.baud)
            self.cfg.WriteInt('bytes', device.bytes)
            self.cfg.Write('parity', device.parity)
            self.cfg.WriteInt('stops', device.stops)
            self.cfg.WriteBool('soft', device.soft)

    def __load(self):
        self.cfg = wx.Config('rtlsdr-scanner')

        self.cfg.RenameGroup('Devices', 'DevicesRTL')

        self.display = self.cfg.ReadInt('display', self.display)
        self.saveWarn = self.cfg.ReadBool('saveWarn', self.saveWarn)
        self.backup = self.cfg.ReadBool('backup', self.backup)
        self.fileHistory.Load(self.cfg)
        self.dirScans = self.cfg.Read('dirScans', self.dirScans)
        self.dirExport = self.cfg.Read('dirExport', self.dirExport)
        self.annotate = self.cfg.ReadBool('annotate', self.annotate)
        self.peaks = self.cfg.ReadBool('peaks', self.peaks)
        self.peaksThres = self.cfg.ReadInt('peaksThres', self.peaksThres)
        self.retainScans = self.cfg.ReadBool('retainScans', self.retainScans)
        self.fadeScans = self.cfg.ReadBool('fadeScans', self.fadeScans)
        self.lineWidth = self.cfg.ReadFloat('lineWidth', self.lineWidth)
        self.retainMax = self.cfg.ReadInt('retainMax', self.retainMax)
        self.colourMapUse = self.cfg.ReadBool('colourMapUse', self.colourMapUse)
        self.colourMap = self.cfg.Read('colourMap', self.colourMap)
        self.background = self.cfg.Read('background', self.background)
        self.wireframe = self.cfg.ReadBool('wireframe', self.wireframe)
        self.pointsLimit = self.cfg.ReadBool('pointsLimit', self.pointsLimit)
        self.pointsMax = self.cfg.ReadInt('pointsMax', self.pointsMax)
        self.grid = self.cfg.ReadBool('grid', self.grid)
        self.plotFunc = self.cfg.ReadInt('plotFunc', self.plotFunc)
        self.smoothFunc = self.cfg.Read('smoothFunc', self.smoothFunc)
        self.smoothRatio = self.cfg.ReadInt('smoothRatio', self.smoothRatio)
        self.clickTune = self.cfg.ReadBool('clickTune', self.clickTune)
        self.precisionFreq = self.cfg.ReadInt('precisionFreq', self.precisionFreq)
        self.precisionLevel = self.cfg.ReadInt('precisionLevel', self.precisionLevel)
        self.compareOne = self.cfg.ReadBool('compareOne', self.compareOne)
        self.compareTwo = self.cfg.ReadBool('compareTwo', self.compareTwo)
        self.compareDiff = self.cfg.ReadBool('compareDiff', self.compareDiff)
        self.start = self.cfg.ReadInt('start', self.start)
        self.stop = self.cfg.ReadInt('stop', self.stop)
        self.mode = self.cfg.ReadInt('mode', self.mode)
        self.dwell = self.cfg.ReadFloat('dwell', self.dwell)
        self.nfft = self.cfg.ReadInt('nfft', self.nfft)
        self.scanDelay = self.cfg.ReadInt('scanDelay', self.scanDelay)
        self.overlap = self.cfg.ReadFloat('overlap', self.overlap)
        self.winFunc = self.cfg.Read('winFunc', self.winFunc)
        self.startOption = self.cfg.ReadInt('startOption', self.startOption)
        self.stopOption = self.cfg.ReadInt('stopOption', self.stopOption)
        self.liveUpdate = self.cfg.ReadBool('liveUpdate', self.liveUpdate)
        self.calFreq = self.cfg.ReadFloat('calFreq', self.calFreq)
        self.autoF = self.cfg.ReadBool('autoF', self.autoF)
        self.autoL = self.cfg.ReadBool('autoL', self.autoL)
        self.autoT = self.cfg.ReadBool('autoT', self.autoT)
        self.showMeasure = self.cfg.ReadBool('showMeasure', self.showMeasure)
        self.alert = self.cfg.ReadBool('alert', self.alert)
        self.alertLevel = self.cfg.ReadFloat('alertLevel', self.alertLevel)
        self.gps = self.cfg.ReadBool('gps', self.gps)
        self.gpsRetry = self.cfg.ReadBool('gpsRetry', self.gpsRetry)
        self.exportWidth = self.cfg.ReadFloat('exportWidth', self.exportWidth)
        self.exportHeight = self.cfg.ReadFloat('exportHeight', self.exportHeight)
        self.exportDpi = self.cfg.ReadInt('exportDpi', self.exportDpi)
        self.indexRtl = self.cfg.ReadInt('index', self.indexRtl)
        self.indexRtl = self.cfg.ReadInt('indexRtl', self.indexRtl)
        self.indexGps = self.cfg.ReadInt('indexGps', self.indexGps)
        self.__load_devices_rtl()
        self.__load_devices_gps()

    def __check_conf_serial(self, device):
        if device.type not in range(len(DeviceGPS.TYPE)):
            return 'Type "{}" should be between 0 and {}'.format(device.type,
                                                                 len(DeviceGPS.TYPE) - 1)

        if device.type == 0:
            if device.baud not in device.get_bauds():
                return 'Baud "{}" should be one of:\n  {}'.format(device.baud,
                                                                  device.get_bauds())
            if device.bytes not in DeviceGPS.BYTES:
                return 'Bits "{}" should be one of:\n  {}'.format(device.bytes,
                                                                  DeviceGPS.BYTES)
            if device.parity not in DeviceGPS.PARITIES:
                return 'Parity "{}" should be one of:\n  {}'.format(device.parity,
                                                                    DeviceGPS.PARITIES)
            if device.stops not in DeviceGPS.STOPS:
                return 'Stops "{}" should be one of:\n  {}'.format(device.stops,
                                                                   DeviceGPS.STOPS)

    def load_conf(self, filename):
        config = ConfigParser.SafeConfigParser()
        try:
            config.read(filename)
            sections = config.sections()
            if len(sections):
                device = DeviceGPS()
                device.name = sections[0]
                device.type = config.getint(sections[0], 'type')
                device.resource = config.get(sections[0], 'resource')
                if device.type == DeviceGPS.NMEA_SERIAL:
                    if config.has_option(sections[0], 'baud'):
                        device.baud = config.getint(sections[0], 'baud')
                    if config.has_option(sections[0], 'bits'):
                        device.bytes = config.getint(sections[0], 'bits')
                    if config.has_option(sections[0], 'parity'):
                        device.parity = config.get(sections[0], 'parity')
                    if config.has_option(sections[0], 'stops'):
                        device.stops = config.getint(sections[0], 'stops')
                    if config.has_option(sections[0], 'soft'):
                        device.soft = config.getboolean(sections[0], 'soft')

                self.devicesGps.append(device)
                return self.__check_conf_serial(device)

        except ConfigParser.Error as e:
            return e.message
        except ValueError as e:
            return e.message

    def save(self):
        self.cfg.SetPath("/")
        self.cfg.WriteInt('display', self.display)
        self.cfg.WriteBool('saveWarn', self.saveWarn)
        self.cfg.WriteBool('backup', self.backup)
        self.fileHistory.Save(self.cfg)
        self.cfg.Write('dirScans', self.dirScans)
        self.cfg.Write('dirExport', self.dirExport)
        self.cfg.WriteBool('annotate', self.annotate)
        self.cfg.WriteBool('peaks', self.peaks)
        self.cfg.WriteInt('peaksThres', self.peaksThres)
        self.cfg.WriteBool('retainScans', self.retainScans)
        self.cfg.WriteBool('fadeScans', self.fadeScans)
        self.cfg.WriteFloat('lineWidth', self.lineWidth)
        self.cfg.WriteInt('retainMax', self.retainMax)
        self.cfg.WriteBool('colourMapUse', self.colourMapUse)
        self.cfg.Write('colourMap', self.colourMap)
        self.cfg.Write('background', self.background)
        self.cfg.WriteBool('wireframe', self.wireframe)
        self.cfg.WriteBool('pointsLimit', self.pointsLimit)
        self.cfg.WriteInt('pointsMax', self.pointsMax)
        self.cfg.WriteBool('grid', self.grid)
        self.cfg.WriteInt('plotFunc', self.plotFunc)
        self.cfg.WriteInt('smoothRatio', self.smoothRatio)
        self.cfg.Write('smoothFunc', self.smoothFunc)
        self.cfg.WriteBool('clickTune', self.clickTune)
        self.cfg.WriteInt('precisionFreq', self.precisionFreq)
        self.cfg.WriteInt('precisionLevel', self.precisionLevel)
        self.cfg.WriteBool('compareOne', self.compareOne)
        self.cfg.WriteBool('compareTwo', self.compareTwo)
        self.cfg.WriteBool('compareDiff', self.compareDiff)
        self.cfg.WriteInt('start', self.start)
        self.cfg.WriteInt('stop', self.stop)
        self.cfg.WriteInt('mode', self.mode)
        self.cfg.WriteFloat('dwell', self.dwell)
        self.cfg.WriteInt('nfft', self.nfft)
        self.cfg.WriteInt('scanDelay', self.scanDelay)
        self.cfg.WriteFloat('overlap', self.overlap)
        self.cfg.Write("winFunc", self.winFunc)
        self.cfg.WriteInt('startOption', self.startOption)
        self.cfg.WriteInt('stopOption', self.stopOption)
        self.cfg.WriteBool('liveUpdate', self.liveUpdate)
        self.cfg.WriteFloat('calFreq', self.calFreq)
        self.cfg.WriteBool('autoF', self.autoF)
        self.cfg.WriteBool('autoL', self.autoL)
        self.cfg.WriteBool('autoT', self.autoT)
        self.cfg.WriteBool('showMeasure', self.showMeasure)
        self.cfg.WriteBool('alert', self.alert)
        self.cfg.WriteFloat('alertLevel', self.alertLevel)
        self.cfg.WriteBool('gps', self.gps)
        self.cfg.WriteBool('gpsRetry', self.gpsRetry)
        self.cfg.WriteFloat('exportWidth', self.exportWidth)
        self.cfg.WriteFloat('exportHeight', self.exportHeight)
        self.cfg.WriteInt('exportDpi', self.exportDpi)
        self.cfg.WriteInt('indexRtl', self.indexRtl)
        self.cfg.WriteInt('indexGps', self.indexGps)
        self.__save_devices_rtl()
        self.__save_devices_gps()

        self.cfg.DeleteEntry('autoScale')
        self.cfg.DeleteEntry('yMax')
        self.cfg.DeleteEntry('yMin')
        self.cfg.DeleteEntry('average')
        self.cfg.DeleteEntry('index')

    def reset(self):
        self.cfg.SetPath("/")
        self.cfg.DeleteAll()
        self.__init__() def count_points(spectrum):
    points = 0
    for timeStamp in spectrum:
        points += len(spectrum[timeStamp])

    return points class Extent(object):
    def __init__(self, spectrum):
        self.__clear()
        self.__calc_extent(spectrum)

    def __clear(self):
        self.fMin = float('inf')
        self.fMax = float('-inf')
        self.lMin = float('inf')
        self.lMax = float('-inf')
        self.tMin = float('inf')
        self.tMax = float('-inf')
        self.fPeak = None
        self.lPeak = None
        self.tPeak = None

    def __calc_extent(self, spectrum):
        self.tMin = min(spectrum)
        self.tMax = max(spectrum)

        for timeStamp in spectrum:
            points = spectrum[timeStamp].items()
            if len(points) > 0:
                fMin = min(points, key=itemgetter(0))[0]
                fMax = max(points, key=itemgetter(0))[0]
                lMin = min(points, key=itemgetter(1))[1]
                lMax = max(points, key=itemgetter(1))[1]
                self.fMin = min(self.fMin, fMin)
                self.fMax = max(self.fMax, fMax)
                self.lMin = min(self.lMin, lMin)
                if(lMax >= self.lMax):
                    self.lMax = lMax
                    self.fPeak, self.lPeak = max(points, key=lambda(_f, l): l)
                    self.tPeak = timeStamp

    def get_f(self):
        if self.fMin == self.fMax:
            return self.fMin, self.fMax - 0.001
        return self.fMin, self.fMax

    def get_l(self):
        if self.lMin == self.lMax:
            return self.lMin, self.lMax + 0.001
        return self.lMin, self.lMax

    def get_t(self):
        return utc_to_mpl(self.tMax), utc_to_mpl(self.tMin - 1)

    def get_ft(self):
        tExtent = self.get_t()
        return [self.fMin, self.fMax, tExtent[0], tExtent[1]]

    def get_peak_fl(self):
        return self.fPeak, self.lPeak

    def get_peak_flt(self):
        return self.fPeak, self.lPeak, self.tPeak class Statusbar(wx.StatusBar):
    TEXT_GENERAL = 'Status: '
    TEXT_INFO = 'Info: '
    TEXT_GPS = 'GPS: '

    def __init__(self, parent, log):
        self.controls = [None] * 5
        self.timeStart = None
        self.log = log

        wx.StatusBar.__init__(self, parent, -1)
        self.SetFieldsCount(len(self.controls))

        self.controls[0] = wx.StaticText(self, label=self.TEXT_GENERAL,
                                         style=wx.ST_NO_AUTORESIZE)
        self.controls[1] = wx.StaticText(self, label=self.TEXT_INFO,
                                         style=wx.ST_NO_AUTORESIZE)
        self.controls[2] = Led(self, label=self.TEXT_GPS)
        self.controls[3] = wx.Gauge(self, -1,
                                    style=wx.GA_HORIZONTAL | wx.GA_SMOOTH)
        animation = Animation(get_resource('busy.gif'))
        busy = AnimationCtrl(self, anim=animation)
        busy.SetToolTipString('Updating plot')
        self.controls[4] = busy

        self.controls[3].Hide()
        self.controls[4].Hide()

        self.SetStatusWidths([-1, -1, -1, -1, busy.GetSize()[0] * 4])

        self.Bind(wx.EVT_SIZE, self.__on_size)
        wx.CallAfter(self.__on_size, None)

        self.Fit()

    def __on_size(self, event):
        pos = 0
        for control in self.controls:
            rect = self.GetFieldRect(pos)
            control.SetPosition((rect.x + 10, rect.y + 2))
            control.SetSize((rect.width - 20, rect.height - 4))
            pos += 1

        if event is not None:
            event.Skip()

    def __format_tooltip(self, text):
        if len(text):
            lines = text.splitlines()
            width = max(map(len, lines))
            lines[-1] += '\n' + ' ' * (width - len(lines[-1]))
        return text

    def set_general(self, text, level=Log.INFO):
        text = self.TEXT_GENERAL + text
        self.controls[0].SetLabel(text)
        self.controls[0].SetToolTipString(self.__format_tooltip(text))
        self.controls[0].Refresh()
        self.log.add(text, level)

    def set_info(self, text, level=Log.INFO):
        text = self.TEXT_INFO + text
        self.controls[1].SetLabel(text)
        self.controls[1].SetToolTipString(self.__format_tooltip(text))
        self.controls[1].Refresh()
        self.log.add(text, level)

    def set_gps(self, text, level=Log.INFO):
        text = self.TEXT_GPS + text
        self.controls[2].SetLabel(text)
        self.controls[2].SetToolTipString(self.__format_tooltip(text))
        self.controls[2].Refresh()
        self.log.add(text, level)

    def pulse_gps(self):
        self.controls[2].pulse()

    def warn_gps(self):
        self.controls[2].on("yellow")

    def error_gps(self):
        self.controls[2].on(wx.RED)

    def enable_gps(self):
        self.controls[2].on('w')
        self.set_gps('Enabled')

    def disable_gps(self):
        self.controls[2].on('grey')
        self.set_gps('Disabled')

    def set_progress(self, progress):
        if progress == 0:
            self.timeStart = time.time()
            text = '{:.1f}%\nUnknown'.format(progress)
        else:
            timeTotal = time.time() - self.timeStart
            timeLeft = ((timeTotal / (progress)) * 100.0) - timeTotal
            delta = timedelta(seconds=math.ceil(timeLeft))
            text = '{:.1f}%\n{}'.format(progress, delta)

        self.controls[3].SetValue(progress)
        self.controls[3].SetToolTipString(self.__format_tooltip(text))

    def show_progress(self):
        self.controls[3].Show()

    def hide_progress(self):
        self.controls[3].Hide()
        self.controls[3].SetToolTipString('')

    def set_busy(self, busy):
        animCtrl = self.controls[4]
        animCtrl.Show(busy)
        if busy:
            animCtrl.Play()
        else:
            animCtrl.Stop() class NavigationToolbar(NavigationToolbar2WxAgg):
    def __init__(self, canvas, panel, settings, callBackHideOverlay):
        self.panel = panel
        self.settings = settings
        self.callbackHide = callBackHideOverlay
        self.plot = None
        self.extraTools = []
        self.panPos = None

        NavigationToolbar2WxAgg.__init__(self, canvas)
        if matplotlib.__version__ >= '1.2':
            panId = self.wx_ids['Pan']
        else:
            panId = self.FindById(self._NTB2_PAN).GetId()

        self.ToggleTool(panId, True)
        self.pan()

        self.__add_spacer(False)

        liveId = wx.NewId()
        self.AddCheckTool(liveId, load_bitmap('auto_refresh'),
                          shortHelp='Real time plotting\n(slow and buggy)')
        self.ToggleTool(liveId, settings.liveUpdate)
        wx.EVT_TOOL(self, liveId, self.__on_check_update)

        gridId = wx.NewId()
        self.AddCheckTool(gridId, load_bitmap('grid'),
                          shortHelp='Toggle plot_line grid')
        self.ToggleTool(gridId, settings.grid)
        wx.EVT_TOOL(self, gridId, self.__on_check_grid)

        self.peakId = wx.NewId()
        self.peaksId = None

        self.autoFId = None
        self.autoLId = None
        self.autoTId = None

        self.maxId = None
        self.minId = None
        self.avgId = None
        self.varId = None
        self.smoothId = None
        self.diffId = None
        self.deltaId = None

        self.colourId = None

    def home(self, event):
        self.callbackHide()
        NavigationToolbar2.home(self, event)
        self.clear_auto()

    def back(self, event):
        self.callbackHide()
        NavigationToolbar2.back(self, event)
        self.clear_auto()

    def forward(self, event):
        self.callbackHide()
        NavigationToolbar2.forward(self, event)
        self.clear_auto()

    def drag_pan(self, event):
        if not self.panPos:
            self.panPos = (event.x, event.y)
        NavigationToolbar2.drag_pan(self, event)

    def release_pan(self, event):
        pos = (event.x, event.y)
        self.callbackHide()
        NavigationToolbar2.release_pan(self, event)
        if event.button != 2:
            if self.panPos and self.panPos != pos:
                self.clear_auto()
        self.panPos = None

    def release_zoom(self, event):
        self.callbackHide()
        NavigationToolbar2.release_zoom(self, event)
        self.clear_auto()

    def __on_check_auto_f(self, event):
        self.settings.autoF = event.Checked()
        self.panel.redraw_plot()

    def __on_check_auto_l(self, event):
        self.settings.autoL = event.Checked()
        self.panel.redraw_plot()

    def __on_check_auto_t(self, event):
        self.settings.autoT = event.Checked()
        self.panel.redraw_plot()

    def __on_check_update(self, event):
        self.settings.liveUpdate = event.Checked()

    def __on_check_grid(self, event):
        grid = event.Checked()
        self.settings.grid = grid
        self.panel.set_grid(grid)

    def __on_check_peak(self, event):
        peak = event.Checked()
        self.settings.annotate = peak
        self.panel.redraw_plot()

    def __on_check_peaks(self, event):
        peaks = event.Checked()
        self.settings.peaks = peaks
        self.panel.redraw_plot()

    def __on_check_fade(self, event):
        fade = event.Checked()
        self.settings.fadeScans = fade
        self.panel.redraw_plot()

    def __on_check_wire(self, event):
        wire = event.Checked()
        self.settings.wireframe = wire
        self.panel.create_plot()

    def __on_check_avg(self, event):
        check = event.Checked()
        if check:
            self.settings.plotFunc = PlotFunc.AVG
        else:
            self.settings.plotFunc = PlotFunc.NONE
        self.__set_func()
        self.panel.redraw_plot()

    def __on_check_min(self, event):
        check = event.Checked()
        if check:
            self.settings.plotFunc = PlotFunc.MIN
        else:
            self.settings.plotFunc = PlotFunc.NONE
        self.__set_func()
        self.panel.redraw_plot()

    def __on_check_max(self, event):
        check = event.Checked()
        if check:
            self.settings.plotFunc = PlotFunc.MAX
        else:
            self.settings.plotFunc = PlotFunc.NONE
        self.__set_func()
        self.panel.redraw_plot()

    def __on_check_var(self, event):
        check = event.Checked()
        if check:
            self.settings.plotFunc = PlotFunc.VAR
        else:
            self.settings.plotFunc = PlotFunc.NONE
        self.__set_func()
        self.panel.redraw_plot()

    def __on_check_smooth(self, event):
        check = event.Checked()
        if check:
            self.settings.plotFunc = PlotFunc.SMOOTH
        else:
            self.settings.plotFunc = PlotFunc.NONE
        self.__set_func()
        self.panel.redraw_plot()

    def __on_check_diff(self, event):
        check = event.Checked()
        if check:
            self.settings.plotFunc = PlotFunc.DIFF
        else:
            self.settings.plotFunc = PlotFunc.NONE
        self.__set_func()
        self.panel.redraw_plot()

    def __on_check_delta(self, event):
        check = event.Checked()
        if check:
            self.settings.plotFunc = PlotFunc.DELTA
        else:
            self.settings.plotFunc = PlotFunc.NONE
        self.__set_func()
        self.panel.redraw_plot()

    def __on_set_smooth(self, _event):
        dlg = DialogSmoothPrefs(self, self.settings)
        if dlg.ShowModal() == wx.ID_OK:
            self.panel.redraw_plot()

    def __on_set_peaks(self, _event):
        dlg = DialogPeakThreshold(self, self.settings)
        if dlg.ShowModal() == wx.ID_OK:
            self.panel.redraw_plot()

    def __on_colour(self, event):
        colourMap = event.GetString()
        self.settings.colourMap = colourMap
        self.plot.set_colourmap(colourMap)
        self.panel.redraw_plot()

    def __on_colour_use(self, event):
        check = event.Checked()
        self.settings.colourMapUse = check
        self.colourId.Enable(check)
        self.plot.set_colourmap_use(check)
        self.panel.redraw_plot()

    def __add_check_tool(self, bitmap, toolTip, callback, setting=None, toolId=None):
        if toolId is None:
            toolId = wx.NewId()
        self.AddCheckTool(toolId, load_bitmap(bitmap), shortHelp=toolTip)
        wx.EVT_TOOL(self, toolId, callback)
        if setting is not None:
            self.ToggleTool(toolId, setting)
        self.extraTools.append(toolId)

    def __add_spacer(self, temp=True):
        sepId = wx.NewId()
        self.AddCheckTool(sepId, load_bitmap('spacer'))
        self.EnableTool(sepId, False)
        if temp:
            self.extraTools.append(sepId)

    def __add_peak(self):
        self.__add_check_tool('peak', 'Label peak',
                              self.__on_check_peak,
                              self.settings.annotate,
                              toolId=self.peakId)

    def __add_peaks(self):
        self.peaksId = wx.NewId()
        self.__add_check_tool('peaks', 'Mark peaks above threshold '
                              '(right click for options)',
                              self.__on_check_peaks,
                              self.settings.peaks,
                              toolId=self.peaksId)
        wx.EVT_TOOL_RCLICKED(self, self.peaksId, self.__on_set_peaks)

    def __add_auto_range(self, scaleF, scaleL, scaleT):
        if scaleF:
            self.autoFId = wx.NewId()
            self.__add_check_tool('auto_f', 'Auto range frequency',
                                  self.__on_check_auto_f,
                                  self.settings.autoF,
                                  self.autoFId)

        if scaleL:
            self.autoLId = wx.NewId()
            self.__add_check_tool('auto_l', 'Auto range level',
                                  self.__on_check_auto_l,
                                  self.settings.autoL,
                                  self.autoLId)

        if scaleT:
            self.autoTId = wx.NewId()
            self.__add_check_tool('auto_t', 'Auto range time',
                                  self.__on_check_auto_t,
                                  self.settings.autoT,
                                  self.autoTId)

        self.__add_spacer()

    def __add_colourmap(self, useMapButton=True):
        if useMapButton:
            self.__add_check_tool('colourmap', 'Use colour maps',
                                  self.__on_colour_use,
                                  self.settings.colourMapUse)

        colours = get_colours()
        colourId = wx.NewId()
        self.colourId = wx.Choice(self, id=colourId, choices=colours)
        self.colourId.SetSelection(colours.index(self.settings.colourMap))
        self.AddControl(self.colourId)
        self.colourId.Enable(self.settings.colourMapUse)
        self.Bind(wx.EVT_CHOICE, self.__on_colour, self.colourId)
        self.extraTools.append(colourId)

    def __enable_tool(self, toolId, state):
        if toolId is not None:
            self.EnableTool(toolId, state)

    def __toggle_tool(self, toolId, state):
        if toolId is not None:
            self.ToggleTool(toolId, state)

    def __set_func(self):
        buttons = [self.avgId, self.minId, self.maxId,
                   self.varId, self.smoothId, self.diffId, self.deltaId]

        for button in buttons:
            self.__toggle_tool(button, False)

        if self.settings.plotFunc != PlotFunc.NONE:
            self.__toggle_tool(buttons[self.settings.plotFunc - 1], True)

        if self.settings.plotFunc == PlotFunc.NONE:
            self.__enable_tool(self.peakId, True)
            self.__enable_tool(self.peaksId, True)
        elif self.settings.plotFunc in [PlotFunc.AVG, PlotFunc.MIN,
                                        PlotFunc.MAX, PlotFunc.SMOOTH,
                                        PlotFunc.DIFF, PlotFunc.DELTA]:
            self.__enable_tool(self.peakId, True)
            self.__enable_tool(self.peaksId, False)
            self.__toggle_tool(self.peaksId, False)
            self.settings.peaks = False
        else:
            self.__enable_tool(self.peakId, False)
            self.__enable_tool(self.peaksId, False)
            self.__toggle_tool(self.peaksId, False)
            self.__toggle_tool(self.peakId, False)
            self.settings.annotate = False
            self.settings.peaks = False

    def set_auto(self, state):
        self.settings.autoF = state
        self.settings.autoL = state
        self.settings.autoT = state
        self.__toggle_tool(self.autoFId, state)
        self.__toggle_tool(self.autoLId, state)
        self.__toggle_tool(self.autoTId, state)

    def clear_auto(self):
        self.set_auto(False)

    def set_plot(self, plot):
        self.plot = plot

    def set_type(self, display):
        for toolId in self.extraTools:
            self.DeleteTool(toolId)
        self.extraTools = []

        self.__add_spacer()

        if display == Display.PLOT:
            self.__add_auto_range(True, True, False)
            self.__add_peak()
            self.__add_peaks()

            self.__add_check_tool('fade', 'Fade plots',
                                  self.__on_check_fade,
                                  self.settings.fadeScans)
            self.__add_spacer()
            self.avgId = wx.NewId()
            self.__add_check_tool('average', 'Show average',
                                  self.__on_check_avg,
                                  toolId=self.avgId)
            self.minId = wx.NewId()
            self.__add_check_tool('min', 'Show minimum',
                                  self.__on_check_min,
                                  toolId=self.minId)
            self.maxId = wx.NewId()
            self.__add_check_tool('max', 'Show maximum',
                                  self.__on_check_max,
                                  toolId=self.maxId)
            self.varId = wx.NewId()
            self.__add_check_tool('variance', 'Show variance',
                                  self.__on_check_var,
                                  toolId=self.varId)
            self.smoothId = wx.NewId()
            self.__add_check_tool('smooth', 'Smooth (right click for options)',
                                  self.__on_check_smooth,
                                  toolId=self.smoothId)
            wx.EVT_TOOL_RCLICKED(self, self.smoothId, self.__on_set_smooth)
            self.diffId = wx.NewId()
            self.__add_check_tool('diff', 'Differentiate spectrum',
                                  self.__on_check_diff,
                                  toolId=self.diffId)
            self.deltaId = wx.NewId()
            self.__add_check_tool('delta', 'Delta from first sweep',
                                  self.__on_check_delta,
                                  toolId=self.deltaId)

            self.__add_spacer()
            self.__add_colourmap()

        elif display == Display.SPECT:
            self.__add_auto_range(True, True, True)
            self.__add_peak()
            self.__add_peaks()
            self.smoothId = wx.NewId()
            self.__add_check_tool('smooth', 'Smooth (right click for options)',
                                  self.__on_check_smooth,
                                  toolId=self.smoothId)
            wx.EVT_TOOL_RCLICKED(self, self.smoothId, self.__on_set_smooth)
            self.diffId = wx.NewId()
            self.__add_check_tool('diff', 'Differentiate spectrum',
                                  self.__on_check_diff,
                                  toolId=self.diffId)
            self.__add_spacer()
            self.__add_colourmap(False)

        elif display == Display.SURFACE:
            self.__add_auto_range(True, True, True)
            self.__add_peak()
            self.__add_peaks()
            self.smoothId = wx.NewId()
            self.__add_check_tool('smooth', 'Smooth (right click for options)',
                                  self.__on_check_smooth,
                                  toolId=self.smoothId)
            wx.EVT_TOOL_RCLICKED(self, self.smoothId, self.__on_set_smooth)
            self.diffId = wx.NewId()
            self.__add_check_tool('diff', 'Differentiate spectrum',
                                  self.__on_check_diff,
                                  toolId=self.diffId)
            self.__add_spacer()
            self.__add_colourmap(False)
            self.__add_spacer()
            self.__add_check_tool('wireframe', 'Wireframe drawing',
                                  self.__on_check_wire,
                                  self.settings.wireframe)

        elif display == Display.TIMELINE:
            self.__add_auto_range(False, True, True)

        elif display == Display.PREVIEW:
            self.__add_check_tool('fade', 'Fade plots',
                                  self.__on_check_fade,
                                  self.settings.fadeScans)

        self.__set_func()

        self.Realize() def create_gearth(handle):
    handle.write('<?xml version="1.0" encoding="UTF-8"?>\n'
                 '<kml xmlns="http://www.opengis.net/kml/2.2">\n'
                 '\t<NetworkLink>\n'
                 '\t\t<name>{}</name>\n'
                 '\t\t<flyToView>1</flyToView>\n'
                 '\t\t<open>1</open>\n'
                 '\t\t<Link>\n'
                 '\t\t\t<href>http://localhost:{}/kml</href>\n'
                 '\t\t\t<refreshMode>onInterval</refreshMode>\n'
                 '\t\t\t<refreshInterval>10</refreshInterval>\n'
                 '\t\t</Link>\n'
                 '\t</NetworkLink>\n'
                 '</kml>\n'.format(APP_NAME, LOCATION_PORT)) def load_icon(name):
    filename = get_resource(name + '.png')

    return wx.Icon(filename, wx.BITMAP_TYPE_PNG) class MultiButton(wx.PyControl):
    PADDING = 5
    ARROW_SIZE = 6

    def __init__(self, parent, options, tips=None, selected=0):
        wx.PyControl.__init__(self, parent=parent, size=wx.DefaultSize,
                              style=wx.NO_BORDER)
        self.options = options
        self.tips = tips
        self.pressed = False
        self.selected = selected
        self.isOverArrow = False

        self.menu = wx.Menu()
        for option in options:
            item = self.menu.Append(wx.ID_ANY, option)
            self.Bind(wx.EVT_MENU, self.__on_menu, item)

        self.Bind(wx.EVT_PAINT, self.__on_paint)
        self.Bind(wx.EVT_SIZE, self.__on_size)
        self.Bind(wx.EVT_LEFT_DOWN, self.__on_left_down)
        self.Bind(wx.EVT_LEFT_UP, self.__on_left_up)
        self.Bind(wx.EVT_MOTION, self.__on_motion)
        self.Bind(wx.EVT_LEAVE_WINDOW, self.__on_leave)
        self.Bind(wx.EVT_CONTEXT_MENU, self.__on_context)

    def __on_paint(self, _event):
        dc = wx.GCDC(wx.PaintDC(self))
        self.__draw(dc)

    def __on_size(self, _event):
        self.Refresh()

    def __on_left_down(self, event):
        if not self.__is_over_arrow(event):
            self.pressed = True
            self.Refresh()

    def __on_left_up(self, event):
        if self.__is_over_arrow(event):
            self.__show_menu()
        else:
            self.pressed = False
            event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED,
                                    self.GetId())
            event.SetEventObject(self)
            event.SetInt(self.selected)
            event.SetString(self.GetLabel())
            self.Refresh()
            self.GetEventHandler().ProcessEvent(event)

    def __on_motion(self, event):
        if self.isOverArrow != self.__is_over_arrow(event):
            self.isOverArrow = self.__is_over_arrow(event)
            self.Refresh()

    def __on_leave(self, _event):
        self.isOverArrow = False
        self.Refresh()

    def __on_menu(self, event):
        item = self.menu.FindItemById(event.Id)
        label = item.GetLabel()
        self.selected = self.options.index(label)
        self.__set_text()

    def __on_context(self, _event):
        self.__show_menu()

    def __show_menu(self):
        self.PopupMenu(self.menu)

    def __set_text(self):
        self.SetLabel(self.options[self.selected])
        if self.tips is not None:
            self.SetToolTipString(self.tips[self.selected])
        self.Refresh()

    def __is_over_arrow(self, event):
        x = event.GetPosition()[0]
        y = event.GetPosition()[1]
        width = event.GetEventObject().GetSize()[0]
        height = event.GetEventObject().GetSize()[1]

        top = 0
        bottom = height
        right = width - self.PADDING
        left = right - self.ARROW_SIZE - self.PADDING * 3

        if (right >= x >= left) and (bottom >= y >= top):
            return True
        return False

    def __draw(self, dc):
        renderer = wx.RendererNative.Get()
        rect = self.GetClientRect()
        rect.Left += self.PADDING
        rect.Right -= self.PADDING * 2
        if self.pressed:
            flags = wx.CONTROL_PRESSED
        else:
            flags = 0
        renderer.DrawPushButton(self, dc, rect, flags)

        dc.SetFont(self.GetFont())

        if self.IsEnabled():
            colour = self.GetForegroundColour()
        else:
            colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)
        if not self.isOverArrow:
            brush = wx.Brush(colour, wx.SOLID)
            dc.SetBrush(brush)
        pen = wx.Pen(colour)
        dc.SetPen(pen)
        dc.SetTextForeground(colour)

        label = self.GetLabel()
        _textWidth, textHeight = dc.GetTextExtent(label)

        dc.DrawText(self.GetLabel(),
                    self.PADDING * 2,
                    (rect.height - textHeight) / 2)

        top = (rect.height / 2) - (self.ARROW_SIZE / 4)
        bottom = top + self.ARROW_SIZE / 2
        right = rect.width - self.PADDING
        left = right - self.ARROW_SIZE
        dc.DrawPolygon([(right, top),
                        (left, top),
                        (left + self.ARROW_SIZE / 2, bottom)])
        left = right - (self.ARROW_SIZE * 2)
        top = rect.height / 4
        bottom = rect.height * 3 / 4
        dc.DrawLine(left, top, left, bottom)

    def DoGetBestSize(self):
        label = max(self.options, key=len)
        font = self.GetFont()
        dc = wx.ClientDC(self)
        dc.SetFont(font)
        textWidth, textHeight = dc.GetTextExtent(label)
        width = textWidth + self.ARROW_SIZE * 3 + self.PADDING * 6
        height = textHeight + self.PADDING * 2

        return wx.Size(width, height)

    def Enable(self, enabled):
        self.Enabled = enabled
        self.Refresh()

    def SetSelected(self, selected):
        self.selected = selected
        self.__set_text()

    def GetSelected(self):
        return self.selected
import Queue
import math
import os.path
import tempfile
import thread
import threading
import time
import webbrowser
import wx
from collections import OrderedDict
from threading import Thread
from wx.lib.agw import aui
from wx.lib.masked.numctrl import NumCtrl
from location import ThreadLocation, LocationServer
from menus import MenuMain, PopMenuMain
from misc import RemoteControl, calc_samples, get_dwells, calc_real_dwell, \
    format_iso_time, limit
from rtlsdr_scanner.constants import F_MIN, F_MAX, MODE, NFFT, DISPLAY, Warn, \
    Cal, Mode, APP_NAME, LOCATION_PORT
from rtlsdr_scanner.devices import get_devices_rtl
from rtlsdr_scanner.dialogs_devices import DialogDevicesRTL, DialogDevicesGPS
from rtlsdr_scanner.dialogs_file import DialogImageSize, DialogExportSeq, DialogExportGeo, \
    DialogProperties, DialogSaveWarn, DialogRestore
from rtlsdr_scanner.dialogs_help import DialogSysInfo, DialogAbout
from rtlsdr_scanner.dialogs_prefs import DialogPrefs, DialogAdvPrefs, DialogFormatting
from rtlsdr_scanner.dialogs_scan import DialogScanDelay
from rtlsdr_scanner.dialogs_tools import DialogCompare, DialogAutoCal, DialogSats, DialogSmooth, \
    DialogLog
from rtlsdr_scanner.events import EVENT_THREAD, Event, Log, EventTimer
from rtlsdr_scanner.file import save_plot, export_plot, export_cont, open_plot, ScanInfo, export_image, \
    export_map, extension_add, File, run_file, export_gpx, Backups
from rtlsdr_scanner.panels import PanelGraph
from rtlsdr_scanner.printer import PrintOut
from rtlsdr_scanner.scan import ThreadScan, update_spectrum, ThreadProcess
from rtlsdr_scanner.settings import Settings
from rtlsdr_scanner.spectrum import count_points, Extent
from rtlsdr_scanner.toolbars import Statusbar, NavigationToolbar
from rtlsdr_scanner.utils_google import create_gearth
from rtlsdr_scanner.utils_mpl import add_colours
from rtlsdr_scanner.utils_wx import load_icon
from rtlsdr_scanner.widgets import MultiButton
        self.locations = OrderedDict()
        self.lastLocation = [None] * 4
        self.backups = Backups()

        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.exportCont = None

        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)

        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_toolbars()
        self.__create_menu()
        self.__create_popup_menu()
        self.__set_control_state(True)
        self.__set_size()
        self.Show()

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

        self.SetDropTarget(DropTarget(self))

        self.SetIcon(load_icon('icon'))

        self.steps = 0
        self.stepsTotal = 0

        self.__start_gps()
        self.__start_location_server()

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

        self.graph = PanelGraph(self, self,
                                self.settings, self.status,
                                self.remoteControl)

        self.toolbar1 = wx.Panel(self)

        self.buttonStart = MultiButton(self.toolbar1,
                                       ['Start', 'Continue'],
                                       ['Start new scan', 'Continue scanning'])
        self.buttonStart.SetSelected(self.settings.startOption)
        self.buttonStop = MultiButton(self.toolbar1,
                                      ['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.toolbar1, label="Range (MHz)",
                                  style=wx.ALIGN_CENTER)
        textStart = wx.StaticText(self.toolbar1, label="Start")
        textStop = wx.StaticText(self.toolbar1, label="Stop")

        self.spinCtrlStart = wx.SpinCtrl(self.toolbar1)
        self.spinCtrlStop = wx.SpinCtrl(self.toolbar1)
        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.toolbar1, label="Gain (dB)")
        self.controlGain = wx.Choice(self.toolbar1, choices=[''])

        grid1 = wx.GridBagSizer(5, 5)
        grid1.Add(self.buttonStart, pos=(0, 0), span=(3, 1),
                  flag=wx.ALIGN_CENTER)
        grid1.Add(self.buttonStop, pos=(0, 1), span=(3, 1),
                  flag=wx.ALIGN_CENTER)
        grid1.Add((20, 1), pos=(0, 2))
        grid1.Add(textRange, pos=(0, 3), span=(1, 4), flag=wx.ALIGN_CENTER)
        grid1.Add(textStart, pos=(1, 3), flag=wx.ALIGN_CENTER)
        grid1.Add(self.spinCtrlStart, pos=(1, 4))
        grid1.Add(textStop, pos=(1, 5), flag=wx.ALIGN_CENTER)
        grid1.Add(self.spinCtrlStop, pos=(1, 6))
        grid1.Add(textGain, pos=(0, 7), flag=wx.ALIGN_CENTER)
        grid1.Add(self.controlGain, pos=(1, 7), flag=wx.ALIGN_CENTER)
        grid1.Add((5, 1), pos=(0, 8))
        grid1.AddGrowableCol(2)

        self.toolbar2 = wx.Window(self)

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

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

        textNfft = wx.StaticText(self.toolbar2, label="FFT size")

        self.choiceNfft = wx.Choice(self.toolbar2, choices=map(str, NFFT))