# -*- coding: utf-8 -*-
#
# This file is a plugin for EventGhost.
# Copyright (C) 2005-2009 Lars-Peter Voss <bitmonster@eventghost.org>
#
# EventGhost is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 2 as published by the
# Free Software Foundation;
#
# EventGhost is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""<rst>
Hardware plugin for the `USB-UIRT <http://www.usbuirt.com/>`_ transceiver.

|

.. image:: picture.jpg
   :align: center
   :target: http://www.usbuirt.com/
"""

import eg

eg.RegisterPlugin(
    name = "USB-UIRT",
    author = "Bitmonster",
    version = "1.0",
    kind = "remote",
    hardwareId = "USB\\VID_0403&PID_F850",
    guid = "{36FD4F40-653C-4626-97CC-BE363C05F933}",
    canMultiLoad = True,
    description = __doc__,
    icon = (
        "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACbklEQVR42mXTa4hMYRzH"
        "8f9zzoxmsblMJrzYRHgj140XkiG1Vomd1OwoRVHKreSyRWSRvJHSZktKq0E7LvvCFG/k"
        "kh0ZG7UoS1q7G7vjuqsxu2vOeXyfzvPO1Odc5jzP//+cc35HicgQOrEaU3EMO5HAdlxC"
        "NRrQjgfoxyrUKTaDeIz1mIwdOIvX2IgPWIt7uIubuIK3OGIKfLeVTYGo7WoK5BFCM7Lo"
        "s5O/4g2WYY8poNFmloOxOIBGjMESHMZSTMd1dOOJbXRQ0aKpLLKQk62YhNm4jd04jzW2"
        "SNxOvIPLzOswY5QoFqH1CvsQu2yXcTiEEUzBSYTtyswtTLTjnirWP5cS78yoCnwROc35"
        "BA6LcPALpkGfL9LC/hyONgYPtV4NxWKD4UKhKyJSW63UzA6tM1x4jz+20zCqcMI+h+Xc"
        "3+YY3ceLLFa9iUQpPDAQieZyN44rdarS82r9oLOZnMMMxFhVN4Hxf4g83CdyplckyaBh"
        "cwteIR733WIxFM3n59tQmd9e+wodm40LPWw+8sAp9pImZfau8h1Ha8cp99fUuNFsNs2f"
        "2xjnsa9n/xmPTDJLIs9IVicRbGFpKap6NA8pKvkqOHHZpxm8xa5gnSmE+0jxOj4RyXZi"
        "m+Y4Za+55hZ8lM19E702qm+aE6x7v02eSWDDb5GLz0V+UuQWQdnAdd8kVVFNEzmP1+fy"
        "FSUJfSutF1UEUW22H800mlwzbWfRYKVIhsh6I3YFpVGRyFUSRpfkPC4wo8oNAlMM5oh5"
        "fn93sTDGKjKTIZZ1lSKjijh534jlK/JuosaxamVVSv7/NXGtJwiI5rt/QdoW/AOsdcg6"
        "Kwb6cgAAAABJRU5ErkJggg=="
    ),
)

import wx
from ctypes import (
    c_int, c_uint, c_ulong, byref, c_ubyte, c_char_p, c_void_p, POINTER,
    WINFUNCTYPE, Structure, GetLastError, create_string_buffer, WinDLL,
    string_at,
)
import datetime
import threading


class Text:
    uuInfo = "USB-UIRT Info"
    uuProtocol = "Protocol Version: "
    uuFirmVersion = "Firmware Version: "
    uuFirmDate = "Firmware Date: "
    redIndicator = "Red Indicator LED Operation"
    blinkRx = "Blink when Receiving IR"
    blinkTx = "Blink when Transmitting IR"
    notFound = "<not found>"
    irReception = "IR Reception"
    legacyCodes = "Generate 'legacy' UIRT2-compatible events"
    stopCodes = "Pass short repeat codes as enduring events"

    class TransmitIR:
        name = "Transmit IR"
        description = "Transmits an IR code via the USB-UIRT hardware."
        irCode = "IR Code:"
        learnButton = "Learn an IR Code..."
        repeatCount = "Repeat Count:"
        infinite = "Infinite"
        wait1 = "Wait:"
        wait2 = "ms of IR inactivity before transmission"
        zone = "Zone:"
        zoneChoices = (
            "All",
            "Ext. Jack R-Pin",
            "Ext. Jack L-Pin",
            "Internal Emitter",
        )
        class LearnDialog:
            title = "Learn IR Code"
            frequency = "Frequency"
            signalQuality = "Signal"
            progress = "Learn Progress"
            acceptBurstButton = "Accept Burst"
            forceRaw = "Force RAW-Mode learning"
            helpText = \
                "1. Aim remote directly at USB-UIRT\n"\
                "approximately 6 inches from USB-UIRT face.\n\n"\
                "2. PRESS and HOLD the desired button on\n"\
                "your remote until learning is complete..."


INVALID_HANDLE_VALUE = -1
UINT32 = c_uint

class UUINFO(Structure):
    _fields_ = (
        ('fwVersion',   c_uint),
        ('protVersion', c_uint),
        ('fwDateDay',   c_ubyte),
        ('fwDateMonth', c_ubyte),
        ('fwDateYear',  c_ubyte),
    )
PUUINFO = POINTER(UUINFO)

UUIRTDRV_ERR_NO_DEVICE = 0x20000001
UUIRTDRV_ERR_NO_RESP = 0x20000002
UUIRTDRV_ERR_NO_DLL = 0x20000003
UUIRTDRV_ERR_VERSION = 0x20000004

UUIRTDRV_CFG_LEDRX = 0x0001
UUIRTDRV_CFG_LEDTX = 0x0002
UUIRTDRV_CFG_LEGACYRX = 0x0004

UUIRTDRV_IRFMT_UUIRT = 0x0000
UUIRTDRV_IRFMT_PRONTO = 0x0010

UUIRTDRV_IRFMT_LEARN_FORCERAW = 0x0100
UUIRTDRV_IRFMT_LEARN_FORCESTRUC    = 0x0200
UUIRTDRV_IRFMT_LEARN_FORCEFREQ = 0x0400
UUIRTDRV_IRFMT_LEARN_FREQDETECT    = 0x0800


UUCALLBACKPROC = WINFUNCTYPE(c_int, POINTER(c_ubyte), c_ulong, c_ulong)
LEARNCALLBACKPROC = WINFUNCTYPE(c_int, c_uint, c_uint, c_ulong, c_void_p)


class USB_UIRT(eg.IrDecoderPlugin):
    text = Text

    def __init__(self):
        eg.IrDecoderPlugin.__init__(self, 50.0)
        self.dll = None
        self.enabled = False
        self.AddAction(TransmitIR)


    def __close__(self):
        self.irDecoder.Close()


    def __start__(
        self,
        ledRX=True,
        ledTX=True,
        legacyRX=False,
        repeatStopCodes=False,
    ):
        self.args = (ledRX, ledTX, legacyRX, repeatStopCodes)
        self.codeFormat = UUIRTDRV_IRFMT_PRONTO
        try:
            dll = WinDLL('uuirtdrv')
        except:
            raise self.Exceptions.DriverNotFound
        puDrvVersion = c_uint(0)
        if not dll.UUIRTGetDrvInfo(byref(puDrvVersion)):
            raise self.Exception("Unable to retrieve uuirtdrv version!")
        if puDrvVersion.value != 0x0100:
            raise self.Exception("Invalid uuirtdrv version!")

        if self.info.evalName[-1].isdigit():
            self.deviceStr = "USB-UIRT-%s" % self.info.evalName[-1]
        else:
            self.deviceStr = "USB-UIRT"
        hDrvHandle = dll.UUIRTOpenEx(self.deviceStr, 0, 0, 0)
        if hDrvHandle == INVALID_HANDLE_VALUE:
            err = GetLastError()
            if err == UUIRTDRV_ERR_NO_DLL:
                raise self.Exceptions.DriverNotFound
            elif err == UUIRTDRV_ERR_NO_DEVICE:
                raise self.Exceptions.DeviceNotFound
            elif err == UUIRTDRV_ERR_NO_RESP:
                raise self.Exceptions.DeviceInitFailed
            else:
                raise self.Exceptions.DeviceInitFailed
        self.hDrvHandle = hDrvHandle

        puuInfo = UUINFO()
        if not dll.UUIRTGetUUIRTInfo(hDrvHandle, byref(puuInfo)):
            raise self.Exceptions.DeviceInitFailed
        self.firmwareVersion = "%d.%d" % (
            puuInfo.fwVersion >> 8,
            puuInfo.fwVersion & 0xFF
        )
        self.protocolVersion = "%d.%d" % (
            puuInfo.protVersion >> 8,
            puuInfo.protVersion & 0xFF
        )
        self.firmwareDate = datetime.date(
            puuInfo.fwDateYear+2000,
            puuInfo.fwDateMonth,
            puuInfo.fwDateDay
        )
        self.dll = dll
        self.receiveProc = UUCALLBACKPROC(self.ReceiveCallback)
        res = dll.UUIRTSetRawReceiveCallback(
            self.hDrvHandle,
            self.receiveProc,
            0
        )
        if not res:
            self.dll = None
            raise self.Exception("Error calling UUIRTSetRawReceiveCallback")

        self.SetConfig(ledRX, ledTX, legacyRX, repeatStopCodes)
        self.enabled = True
        eg.Bind("System.DeviceRemoved", self.OnDeviceRemoved)


    def __stop__(self):
        eg.Unbind("System.DeviceRemoved", self.OnDeviceRemoved)
        self.enabled = False
        dll = self.dll
        if dll:
            if not dll.UUIRTClose(self.hDrvHandle):
                raise self.Exception("Error calling UUIRTClose")

            # fix for USB-UIRT driver bug, See OnComputerSuspend for details.
            self.hDrvHandle = dll.UUIRTOpenEx(self.deviceStr, 0, 0, 0)
            # without the UUIRTSetUUIRTConfig call, the driver seems to need
            # much more time to close.
            self.SetConfig(*self.args)
            dll.UUIRTSetReceiveCallback(self.hDrvHandle, None, 0)
            dll.UUIRTClose(self.hDrvHandle)
            self.dll = None


    def OnComputerSuspend(self, suspendType):
        # The USB-UIRT driver seems to have a bug, that prevents the wake-up
        # from standby feature to work, if UUIRTSetRawReceiveCallback was used.
        # To workaround the problem, we re-open the device with
        # UUIRTSetReceiveCallback just before the system goes into standby and
        # later do the reverse once the system comes back from standby.
        dll = self.dll
        if dll is None:
            return
        dll.UUIRTClose(self.hDrvHandle)
        self.hDrvHandle = dll.UUIRTOpenEx(self.deviceStr, 0, 0, 0)
        dll.UUIRTSetReceiveCallback(self.hDrvHandle, None, 0)


    def OnComputerResume(self, suspendType):
        dll = self.dll
        if dll is None:
            return
        dll.UUIRTClose(self.hDrvHandle)
        self.hDrvHandle = dll.UUIRTOpenEx(self.deviceStr, 0, 0, 0)
        dll.UUIRTSetRawReceiveCallback(self.hDrvHandle, self.receiveProc, 0)
        self.SetConfig(*self.args)


    def OnDeviceRemoved(self, event):
        if event.payload[0].split("#")[1] == 'Vid_0403&Pid_f850':
            if self.dll:
                if not self.dll.UUIRTClose(self.hDrvHandle):
                    raise self.Exception("Error calling UUIRTClose")
                self.dll = None
            eg.Bind("System.DeviceAttached", self.OnDeviceAttached)


    def OnDeviceAttached(self, event):
        if event.payload[0].split("#")[1] == 'Vid_0403&Pid_f850':
            if self.enabled:
                self.__start__(*self.args)
            eg.Unbind("System.DeviceAttached", self.OnDeviceAttached)


    def SetConfig(self, ledRX, ledTX, legacyRX, repeatStopCodes=False):
        value = 0
        if ledRX:
            value |= UUIRTDRV_CFG_LEDRX
        if ledTX:
            value |= UUIRTDRV_CFG_LEDTX
        if legacyRX:
            value |= UUIRTDRV_CFG_LEGACYRX
        if repeatStopCodes:
            value |= 16
        if not self.dll.UUIRTSetUUIRTConfig(self.hDrvHandle, UINT32(value)):
            self.dll = None
            raise self.Exception("Error calling UUIRTSetUUIRTConfig")


    def ReceiveCallback(self, buf, length, userdata):
        # TODO: find a more efficient way to find the terminator
        data = []
        for i in range(2, 1024):
            value = buf[i]
            data.append(value)
            if value == 255:
                break
        self.irDecoder.Decode(data, len(data))
        return 0


    def Configure(
        self,
        ledRx=True,
        ledTx=True,
        legacyRx=None,
        repeatStopCodes=False,
    ):
        text = self.text
        if self.dll:
            protocolVersion = self.protocolVersion
            firmwareVersion = self.firmwareVersion
            firmwareDate = self.firmwareDate.strftime("%x")
        else:
            protocolVersion = text.notFound
            firmwareVersion = text.notFound
            firmwareDate = text.notFound

        panel = eg.ConfigPanel()
        ledRxCheckBox = panel.CheckBox(ledRx, text.blinkRx)
        ledTxCheckBox = panel.CheckBox(ledTx, text.blinkTx)
        legacyRxCheckBox = panel.CheckBox(legacyRx, text.legacyCodes)
        stopCodesRxCheckBox = panel.CheckBox(repeatStopCodes, text.stopCodes)

        infoGroupSizer = wx.StaticBoxSizer(
            wx.StaticBox(panel, -1, text.uuInfo),
            wx.VERTICAL
        )
        infoSizer = wx.FlexGridSizer(3, 2)
        infoSizer.AddMany([
            (panel.StaticText(text.uuProtocol), 0, wx.EXPAND),
            (panel.StaticText(protocolVersion), 0, wx.EXPAND),
            (panel.StaticText(text.uuFirmVersion), 0, wx.EXPAND),
            (panel.StaticText(firmwareVersion), 0, wx.EXPAND),
            (panel.StaticText(text.uuFirmDate), 0, wx.EXPAND),
            (panel.StaticText(firmwareDate), 0, wx.EXPAND),
        ])
        infoGroupSizer.Add(infoSizer, 0, wx.LEFT, 5)
        panel.sizer.Add(infoGroupSizer, 0, wx.EXPAND)

        panel.sizer.Add((15, 15))

        ledGroupSizer = wx.StaticBoxSizer(
            wx.StaticBox(panel, -1, text.redIndicator),
            wx.VERTICAL
        )
        ledGroupSizer.Add(ledRxCheckBox, 0, wx.ALL, 10)
        ledGroupSizer.Add(ledTxCheckBox, 0, wx.ALL, 10)
        panel.sizer.Add(ledGroupSizer, 0, wx.EXPAND)

        panel.sizer.Add((15, 15))
        receiveGroupSizer = wx.StaticBoxSizer(
            wx.StaticBox(panel, -1, text.irReception),
            wx.VERTICAL
        )
        receiveGroupSizer.Add(legacyRxCheckBox, 0, wx.ALL, 10)
        receiveGroupSizer.Add(stopCodesRxCheckBox, 0, wx.ALL, 10)
        panel.sizer.Add(receiveGroupSizer, 0, wx.EXPAND)

        while panel.Affirmed():
            panel.SetResult(
                ledRxCheckBox.GetValue(),
                ledTxCheckBox.GetValue(),
                legacyRxCheckBox.GetValue(),
                stopCodesRxCheckBox.GetValue(),
            )



class TransmitIR(eg.ActionClass):
    repeatCount = 4
    inactivityWaitTime = 0

    def __call__(self, code='', repeatCount=4, inactivityWaitTime=0):
        if not self.plugin.dll:
            raise self.Exceptions.DeviceNotReady
        if len(code) > 5:
            start = 0
            if code[0] == "Z":
                start = 2
            if code[start+3] == "R":
                codeFormat = UUIRTDRV_IRFMT_UUIRT
            elif code[start+4] == " ":
                codeFormat = UUIRTDRV_IRFMT_PRONTO
            else:
                codeFormat = UUIRTDRV_IRFMT_LEARN_FORCESTRUC
        else:
            repeatCount = 0
            codeFormat = UUIRTDRV_IRFMT_PRONTO
            code = ""
        if not self.plugin.dll.UUIRTTransmitIR(
            self.plugin.hDrvHandle,    # hHandle
            c_char_p(code),     # IRCode
            codeFormat,         # codeFormat
            repeatCount,        # repeatCount
            inactivityWaitTime, # inactivityWaitTime
            0,                  # hEvent
            0,                  # reserved1
            0                   # reserved2
        ):
            raise self.Exceptions.DeviceNotReady


    def GetLabel(self, code='', repeatCount=4, inactivityWaitTime=0):
        return self.name


    def Configure(self, code='', repeatCount=None, inactivityWaitTime=None):
        text = self.text
        panel = eg.ConfigPanel()
        if repeatCount is None:
            repeatCount = self.repeatCount
        if inactivityWaitTime is None:
            inactivityWaitTime = self.inactivityWaitTime
        if len(code) > 0:
            zone = 0
            if code[0] == "Z":
                zone = int(code[1])
                code = code[2:]
        else:
            zone = 0

        editCtrl = panel.TextCtrl(code, style=wx.TE_MULTILINE)
        font = editCtrl.GetFont()
        font.SetFaceName("Courier New")
        editCtrl.SetFont(font)
        editCtrl.SetMinSize((-1, 100))

        repeatCtrl = panel.SpinIntCtrl(value=repeatCount, min=1, max=127)
        repeatCtrl.SetInitialSize((50, -1))

        infiniteCtrl = wx.CheckBox(panel, -1, text.infinite)
        if repeatCount == 32767:
            repeatCtrl.SetValue(4)
            repeatCtrl.Enable(False)
            infiniteCtrl.SetValue(True)
        else:
            repeatCtrl.SetValue(repeatCount)

        def OnInfiniteCtrl(event):
            repeatCtrl.Enable(not infiniteCtrl.GetValue())
            event.Skip()
        infiniteCtrl.Bind(wx.EVT_CHECKBOX, OnInfiniteCtrl)

        waitCtrl = panel.SpinIntCtrl(inactivityWaitTime, 0, 500)
        waitCtrl.SetInitialSize((50, -1))

        zoneCtrl = panel.Choice(zone, text.zoneChoices)

        learnButton = panel.Button(text.learnButton)
        if self.plugin.dll is None:
            learnButton.Enable(False)

        panel.sizer.Add(panel.StaticText(text.irCode))
        panel.sizer.Add(editCtrl, 1, wx.EXPAND)
        panel.sizer.Add((5, 5))

        stFlags = wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT
        gridSizer = wx.GridBagSizer(5, 5)
        gridSizer.Add(panel.StaticText(text.repeatCount), (0, 0), flag=stFlags)

        tmpSizer = wx.BoxSizer(wx.HORIZONTAL)
        tmpSizer.Add(repeatCtrl)
        tmpSizer.Add((5, 5))
        tmpSizer.Add(infiniteCtrl, 0, wx.ALIGN_CENTER_VERTICAL)
        gridSizer.Add(tmpSizer, (0, 1))

        gridSizer.Add(panel.StaticText(text.wait1), (1, 0), flag=stFlags)

        tmpSizer = wx.BoxSizer(wx.HORIZONTAL)
        tmpSizer.Add(waitCtrl)
        tmpSizer.Add((5, 5))
        tmpSizer.Add(panel.StaticText(text.wait2), 0, wx.ALIGN_CENTER_VERTICAL)
        gridSizer.Add(tmpSizer, (1, 1))

        gridSizer.Add(panel.StaticText(text.zone), (2, 0), flag=stFlags)
        gridSizer.Add(zoneCtrl, (2, 1))
        gridSizer.AddGrowableCol(4, 1)
        gridSizer.Add(
            learnButton,
            (0,5),
            flag=wx.ALIGN_RIGHT|wx.EXPAND
        )

        panel.sizer.Add(gridSizer, 0, wx.EXPAND)

        def LearnIR(event):
            learnDialog = IRLearnDialog(
                None,
                self.plugin.dll,
                self.plugin.hDrvHandle,
                text.LearnDialog
            )
            learnDialog.ShowModal()
            if learnDialog.code:
                editCtrl.SetValue(learnDialog.code)
            learnDialog.AbortLearnThreadWait()
            learnDialog.Destroy()
        learnButton.Bind(wx.EVT_BUTTON, LearnIR)

        while panel.Affirmed():
            zone = zoneCtrl.GetValue()
            if zone > 0:
                code = "Z" + str(zone) + editCtrl.GetValue()
            else:
                code = editCtrl.GetValue()
            if infiniteCtrl.GetValue():
                self.__class__.repeatCount = 32767
            else:
                self.__class__.repeatCount = repeatCtrl.GetValue()
            self.__class__.inactivityWaitTime = waitCtrl.GetValue()
            panel.SetResult(
                code,
                self.repeatCount,
                self.inactivityWaitTime
            )



class IRLearnDialog(eg.Dialog):

    def __init__(self, parent, dll, hDrvHandle, text):
        self.dll = dll
        self.hDrvHandle = hDrvHandle
        self.code = None
        self.codeFormat = UUIRTDRV_IRFMT_PRONTO
        eg.Dialog.__init__(
            self,
            parent,
            -1,
            text.title,
            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER
        )

        staticText = wx.StaticText(self, -1, text.helpText)

        sb = wx.StaticBox(self, -1, text.progress)
        progressSizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
        progressCtrl = wx.Gauge(self, -1, 100, size=(250, 25))
        self.progressCtrl = progressCtrl
        progressSizer.Add(progressCtrl, 0, wx.ALL|wx.EXPAND, 5)

        sb = wx.StaticBox(self, -1, text.signalQuality)
        sigQualitySizer = wx.StaticBoxSizer(sb, wx.HORIZONTAL)
        sigQualityCtrl = wx.Gauge(
            self,
            -1,
            100,
            size=(25, 100),
            style=wx.GA_VERTICAL|wx.GA_SMOOTH
        )
        self.sigQualityCtrl = sigQualityCtrl
        sigQualitySizer.Add(sigQualityCtrl, 0, wx.ALL|wx.EXPAND, 5)

        sb = wx.StaticBox(self, -1, text.frequency)
        carrierFreqSizer = wx.StaticBoxSizer(sb, wx.HORIZONTAL)
        carrierFreqCtrl = wx.StaticText(self, -1, "-", style=wx.ALIGN_CENTER)
        self.carrierFreqCtrl = carrierFreqCtrl
        carrierFreqSizer.Add(carrierFreqCtrl, 1, wx.EXPAND|wx.ALL, 5)

        forceRawCtrl = wx.CheckBox(self, -1, text.forceRaw)
        forceRawCtrl.Bind(wx.EVT_CHECKBOX, self.OnRawBox)
        self.forceRawCtrl = forceRawCtrl

        cancelButton = wx.Button(self, wx.ID_CANCEL, eg.text.General.cancel)
        cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
        cancelButton.SetDefault()

        burstButton = wx.Button(self, wx.ID_CANCEL, text.acceptBurstButton)
        burstButton.Bind(wx.EVT_BUTTON, self.OnAcceptBurst)
        burstButton.Enable(False)
        self.burstButton = burstButton

        leftSizer = wx.BoxSizer(wx.VERTICAL)
        leftSizer.Add(staticText, 0, wx.EXPAND|wx.TOP, 5)
        leftSizer.Add((5, 5), 1)
        leftSizer.Add(forceRawCtrl)

        rightSizer = wx.BoxSizer(wx.VERTICAL)
        rightSizer.Add((15, 15))
        rightSizer.Add(burstButton, 0, wx.EXPAND|wx.ALIGN_RIGHT)
        rightSizer.Add((5, 5))
        rightSizer.Add(cancelButton, 0, wx.EXPAND|wx.ALIGN_RIGHT)
        rightSizer.Add((0, 0), 1)
        rightSizer.Add(carrierFreqSizer, 0, wx.EXPAND)

        upperRowSizer = wx.BoxSizer(wx.HORIZONTAL)
        upperRowSizer.Add(leftSizer, 1, wx.EXPAND)
        upperRowSizer.Add((5, 5))
        upperRowSizer.Add(sigQualitySizer, 0, wx.EXPAND)
        upperRowSizer.Add((5, 5))
        upperRowSizer.Add(rightSizer, 0, wx.EXPAND)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(upperRowSizer, 1, wx.EXPAND|wx.ALL, 5)
        sizer.Add(progressSizer, 0, wx.EXPAND|wx.ALL, 5)

        self.SetSizer(sizer)
        self.SetAutoLayout(True)
        sizer.Fit(self)
        self.SetMinSize(self.GetSize())
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.StartLearnIR()


    def SetRawMode(self, flag=True):
        if flag:
            self.codeFormat = UUIRTDRV_IRFMT_LEARN_FORCERAW
        else:
            self.codeFormat = UUIRTDRV_IRFMT_PRONTO


    def StartLearnIR(self):
        self.learnThreadAbortEvent = threading.Event()
        self.bAbortLearn = c_int(0)
        self.learnThread = threading.Thread(target=self.LearnThread)
        self.learnThread.start()


    def AbortLearnThread(self):
        self.bAbortLearn.value = True


    def AbortLearnThreadWait(self):
        self.bAbortLearn.value = True
        self.learnThreadAbortEvent.wait(10)


    def AcceptBurst(self):
        self.bAbortLearn.value = -1


    def LearnThread(self):
        learnBuffer = create_string_buffer('\000' * 2048)
        self.dll.UUIRTLearnIR(
            self.hDrvHandle,                       # hHandle
            self.codeFormat,                       # codeFormat
            learnBuffer,                           # IRCode buffer
            LEARNCALLBACKPROC(self.LearnCallback), # progressProc
            0x5a5a5a5a,                            # userData
            byref(self.bAbortLearn),               # *pAbort
            0,                                     # param1
            0,                                     # reserved0
            0                                      # reserved1
        )
        if self.bAbortLearn.value != 1:
            self.OnLearnSuccess(learnBuffer.value)
        self.learnThreadAbortEvent.set()


    def LearnCallback(self, progress, sigQuality, carrierFreq, userData):
        if progress > 0:
            self.burstButton.Enable(True)
        self.progressCtrl.SetValue(progress)
        self.sigQualityCtrl.SetValue(sigQuality)
        self.carrierFreqCtrl.SetLabel(
            "%d.%03d kHz" % (carrierFreq / 1000, carrierFreq % 1000)
        )
        return 0


    def OnLearnSuccess(self, code):
        self.code = code
        self.Close()


    def OnRawBox(self, event):
        self.AbortLearnThreadWait()
        self.SetRawMode(self.forceRawCtrl.GetValue())
        self.burstButton.Enable(False)
        self.progressCtrl.SetValue(0)
        self.sigQualityCtrl.SetValue(0)
        self.StartLearnIR()


    def OnAcceptBurst(self, event):
        self.AcceptBurst()


    def OnClose(self, event):
        self.AbortLearnThread()
        event.Skip()


    def OnCancel(self, event):
        self.Close()

