# --------------------------------------------------------------------------
#<pycode(py_kernwin_askform)>
import sys

import ida_idaapi, _ida_idaapi
import ida_pro

#ICON WARNING|QUESTION|INFO|NONE
#AUTOHIDE NONE|DATABASE|REGISTRY|SESSION
#HIDECANCEL
#BUTTON YES|NO|CANCEL "Value|NONE"
#STARTITEM {id:ItemName}
#HELP / ENDHELP
try:
    import types
    import ctypes
    # On Windows, we use stdcall

    # Callback for buttons
    # typedef int (idaapi *buttoncb_t)(int button_code, form_actions_t &fa);

    _BUTTONCB_T = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_void_p)

    # Callback for form change
    # typedef int (idaapi *formchgcb_t)(int field_id, form_actions_t &fa);
    _FORMCHGCB_T = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_void_p)
except:
    try:
        _BUTTONCB_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_void_p)
        _FORMCHGCB_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_void_p)
    except:
        _BUTTONCB_T = _FORMCHGCB_T = None


# -----------------------------------------------------------------------
# textctrl_info_t clinked object
class textctrl_info_t(ida_idaapi.py_clinked_object_t):
    """Class representing textctrl_info_t"""

    # Some constants
    TXTF_AUTOINDENT = 0x0001
    """Auto-indent on new line"""
    TXTF_ACCEPTTABS = 0x0002
    """Tab key inserts 'tabsize' spaces"""
    TXTF_READONLY   = 0x0004
    """Text cannot be edited (but can be selected and copied)"""
    TXTF_SELECTED   = 0x0008
    """Shows the field with its text selected"""
    TXTF_MODIFIED   = 0x0010
    """Gets/sets the modified status"""
    TXTF_FIXEDFONT  = 0x0020
    """The control uses IDA's fixed font"""

    def __init__(self, text="", flags=0, tabsize=0):
        ida_idaapi.py_clinked_object_t.__init__(self)
        if text:
            self.text = text
        if flags:
            self.flags = flags
        if tabsize:
            self.tabsize = tabsize

    def _create_clink(self):
        return _ida_kernwin.textctrl_info_t_create()

    def _del_clink(self, lnk):
        return _ida_kernwin.textctrl_info_t_destroy(lnk)

    def _get_clink_ptr(self):
        return _ida_kernwin.textctrl_info_t_get_clink_ptr(self)

    def assign(self, other):
        """Copies the contents of 'other' to 'self'"""
        return _ida_kernwin.textctrl_info_t_assign(self, other)

    def __set_text(self, s):
        """Sets the text value"""
        return _ida_kernwin.textctrl_info_t_set_text(self, s)

    def __get_text(self):
        """Sets the text value"""
        return _ida_kernwin.textctrl_info_t_get_text(self)

    def __set_flags__(self, flags):
        """Sets the flags value"""
        return _ida_kernwin.textctrl_info_t_set_flags(self, flags)

    def __get_flags__(self):
        """Returns the flags value"""
        return _ida_kernwin.textctrl_info_t_get_flags(self)

    def __set_tabsize__(self, tabsize):
        """Sets the tabsize value"""
        return _ida_kernwin.textctrl_info_t_set_tabsize(self, tabsize)

    def __get_tabsize__(self):
        """Returns the tabsize value"""
        return _ida_kernwin.textctrl_info_t_get_tabsize(self)

    value   = property(__get_text, __set_text)
    """Alias for the text property"""
    text    = property(__get_text, __set_text)
    """Text value"""
    flags   = property(__get_flags__, __set_flags__)
    """Flags value"""
    tabsize = property(__get_tabsize__, __set_tabsize__)

# -----------------------------------------------------------------------
class Form(object):

    FT_ASCII = 'A'
    """Ascii string - char *"""
    FT_SEG = 'S'
    """Segment - sel_t *"""
    FT_HEX = 'N'
    """Hex number - uval_t *"""
    FT_SHEX = 'n'
    """Signed hex number - sval_t *"""
    FT_COLOR = 'K'
    """Color button - bgcolor_t *"""
    FT_ADDR = '$'
    """Address - ea_t *"""
    FT_UINT64 = 'L'
    """default base uint64 - uint64"""
    FT_INT64 = 'l'
    """default base int64 - int64"""
    FT_RAWHEX = 'M'
    """Hex number, no 0x prefix - uval_t *"""
    FT_FILE = 'f'
    """File browse - char * at least QMAXPATH"""
    FT_DEC = 'D'
    """Decimal number - sval_t *"""
    FT_OCT = 'O'
    """Octal number, C notation - sval_t *"""
    FT_BIN = 'Y'
    """Binary number, 0b prefix - sval_t *"""
    FT_CHAR = 'H'
    """Char value -- sval_t *"""
    FT_IDENT = 'I'
    """Identifier - char * at least MAXNAMELEN"""
    FT_BUTTON = 'B'
    """Button - def handler(code)"""
    FT_DIR = 'F'
    """Path to directory - char * at least QMAXPATH"""
    FT_TYPE = 'T'
    """Type declaration - char * at least MAXSTR"""
    _FT_USHORT = '_US'
    """Unsigned short"""
    FT_FORMCHG = '%/'
    """Form change callback - formchgcb_t"""
    FT_ECHOOSER = 'E'
    """Embedded chooser - idaapi.Choose"""
    FT_MULTI_LINE_TEXT = 't'
    """Multi text control - textctrl_info_t"""
    FT_DROPDOWN_LIST   = 'b'
    """Dropdown list control - Form.DropdownControl"""
    FT_HTML_LABEL = 'h'
    """HTML label to display (only for GUI version, and for dynamic labels; no input)"""

    FT_CHKGRP = 'C'
    FT_CHKGRP2= 'c'
    FT_RADGRP = 'R'
    FT_RADGRP2= 'r'

    @staticmethod
    def create_string_buffer(value, size=None):
        if value is None:
            assert(size is not None)
            return ctypes.create_string_buffer(size)
        else:
            if sys.version_info.major >= 3:
                return ctypes.create_string_buffer(value.encode("UTF-8"), size)
            else:
                return ctypes.create_string_buffer(value, size)

    @staticmethod
    def fieldtype_to_ctype(tp, i64 = False):
        """
        Factory method returning a ctype class corresponding to the field type string
        """
        if tp in (Form.FT_SEG, Form.FT_HEX, Form.FT_RAWHEX, Form.FT_ADDR):
            return ctypes.c_uint64 if i64 else ctypes.c_ulong
        elif tp in (Form.FT_SHEX, Form.FT_DEC, Form.FT_OCT, Form.FT_BIN, Form.FT_CHAR):
            return ctypes.c_int64 if i64 else ctypes.c_long
        elif tp == Form.FT_UINT64:
            return ctypes.c_uint64
        elif tp == Form.FT_INT64:
            return ctypes.c_int64
        elif tp == Form.FT_COLOR:
            return ctypes.c_ulong
        elif tp == Form._FT_USHORT:
            return ctypes.c_ushort
        elif tp in (Form.FT_FORMCHG, Form.FT_ECHOOSER):
            return ctypes.c_void_p
        else:
            return None


    #
    # Generic argument helper classes
    #
    class NumericArgument(object):
        """
        Argument representing various integer arguments (ushort, uint32, uint64, etc...)
        :param tp: One of Form.FT_XXX
        """
        DefI64 = False
        def __init__(self, tp, value, i64=None):
            cls = Form.fieldtype_to_ctype(tp, i64 if i64 is not None else self.DefI64)
            if cls is None:
                raise TypeError("Invalid numeric field type: %s" % tp)
            # Get a pointer type to the ctype type
            self.arg = ctypes.pointer(cls(value))

        def __set_value(self, v):
            self.arg.contents.value = v
        value = property(lambda self: self.arg.contents.value, __set_value)


    class StringArgument(object):
        """
        Argument representing a character buffer
        """
        def __init__(self, size=None, value=None):
            if size is None:
                raise SyntaxError("The string size must be passed")
            if isinstance(size, str):
                value, size = size, None
            self.size = size
            self.arg = Form.create_string_buffer(value, size)

        def __get_value(self):
            return self.arg.value.decode("UTF-8")

        def __set_value(self, v):
            self.arg.value = v.encode("UTF-8")
        value = property(__get_value, __set_value)


    #
    # Base control class
    #
    class Control(object):
        def __init__(self):
            self.id = 0
            """Automatically assigned control ID"""

            self.input_field_index = None
            """If this control is an input field, once Compile() returns this will hold its index. This is used only to compute the possible STARTITEM index"""

            self.arg = None
            """Control argument value. This could be one element or a list/tuple (for multiple args per control)"""

            self.form = None
            """Reference to the parent form. It is filled by Form.Add()"""

            self.form_hasattr = False

        def get_tag(self):
            """
            Control tag character. One of Form.FT_XXXX.
            The form class will expand the {} notation and replace them with the tags
            """
            pass

        def get_arg(self):
            """
            Control returns the parameter to be pushed on the stack
            (Of ask_form())
            """
            return self.arg

        def free(self):
            """
            Free the control
            """
            # Release the parent form reference
            self.form = None

        def is_input_field(self):
            """
            Return True if this field acts as an input
            """
            return False

    #
    # Label controls
    #
    class LabelControl(Control):
        """
        Base class for static label control
        """
        def __init__(self, tp):
            Form.Control.__init__(self)
            self.tp = tp

        def get_tag(self):
            return '%%%d%s' % (self.id, self.tp)


    class StringLabel(LabelControl):
        """
        String label control
        """
        def __init__(self, value, tp=None, size=ida_pro.MAXSTR):
            """
            Type field can be one of:
            A - ascii string
            T - type declaration
            I - ident
            F - folder
            f - file
            X - command
            """
            if tp is None:
                tp = Form.FT_ASCII
            Form.LabelControl.__init__(self, tp)
            self.size = size
            self.arg = Form.create_string_buffer(value, size)


    class NumericLabel(LabelControl, NumericArgument):
        """
        Numeric label control
        """
        def __init__(self, value, tp=None):
            if tp is None:
                tp = Form.FT_HEX
            Form.LabelControl.__init__(self, tp)
            Form.NumericArgument.__init__(self, tp, value)


    #
    # Group controls
    #
    class GroupItemControl(Control):
        """
        Base class for group control items
        """
        def __init__(self, tag, parent):
            Form.Control.__init__(self)
            self.tag = tag
            self.parent = parent
            # Item position (filled when form is compiled)
            self.pos = 0

        def assign_pos(self):
            self.pos = self.parent.next_child_pos()

        def get_tag(self):
            return "%s%d" % (self.tag, self.id)

        def is_input_field(self):
            return True


    class ChkGroupItemControl(GroupItemControl):
        """
        Checkbox group item control
        """
        def __init__(self, tag, parent):
            Form.GroupItemControl.__init__(self, tag, parent)

        def __get_value(self):
            return (self.parent.value & (1 << self.pos)) != 0

        def __set_value(self, v):
            pv = self.parent.value
            if v:
                pv = pv | (1 << self.pos)
            else:
                pv = pv & ~(1 << self.pos)

            self.parent.value = pv

        checked = property(__get_value, __set_value)
        """Get/Sets checkbox item check status"""


    class RadGroupItemControl(GroupItemControl):
        """
        Radiobox group item control
        """
        def __init__(self, tag, parent):
            Form.GroupItemControl.__init__(self, tag, parent)

        def __get_value(self):
            return self.parent.value == self.pos

        def __set_value(self, v):
            self.parent.value = self.pos

        selected = property(__get_value, __set_value)
        """Get/Sets radiobox item selection status"""


    class GroupControl(Control, NumericArgument):
        """
        Base class for group controls
        """
        def __init__(self, children_names, tag, value=0):
            Form.Control.__init__(self)
            self.children_names = children_names
            self.tag = tag
            self._reset()
            Form.NumericArgument.__init__(self, Form._FT_USHORT, value)

        def _reset(self):
            self.childpos = 0

        def next_child_pos(self):
            v = self.childpos
            self.childpos += 1
            return v

        def get_tag(self):
            return "%d" % self.id


    class ChkGroupControl(GroupControl):
        """
        Checkbox group control class.
        It holds a set of checkbox controls
        """
        ItemClass = None
        """
        Group control item factory class instance
        We need this because later we won't be treating ChkGroupControl or RadGroupControl
        individually, instead we will be working with GroupControl in general.
        """
        def __init__(self, children_names, value=0, secondary=False):
            # Assign group item factory class
            if Form.ChkGroupControl.ItemClass is None:
                Form.ChkGroupControl.ItemClass = Form.ChkGroupItemControl

            Form.GroupControl.__init__(
                self,
                children_names,
                Form.FT_CHKGRP2 if secondary else Form.FT_CHKGRP,
                value)


    class RadGroupControl(GroupControl):
        """
        Radiobox group control class.
        It holds a set of radiobox controls
        """
        ItemClass = None
        def __init__(self, children_names, value=0, secondary=False):
            """
            Creates a radiogroup control.
            :param children_names: A tuple containing group item names
            :param value: Initial selected radio item
            :param secondory: Allows rendering one the same line as the previous group control.
                              Use this if you have another group control on the same line.
            """
            # Assign group item factory class
            if Form.RadGroupControl.ItemClass is None:
                Form.RadGroupControl.ItemClass = Form.RadGroupItemControl

            Form.GroupControl.__init__(
                self,
                children_names,
                Form.FT_RADGRP2 if secondary else Form.FT_RADGRP,
                value)


    #
    # Input controls
    #
    class InputControl(Control):
        """
        Generic form input control.
        It could be numeric control, string control, directory/file browsing, etc...
        """
        def __init__(self,
                     tp,
                     width,
                     swidth,
                     hlp=None,
                     is_relative_offset=False):
            """
            :param width:  The maximum possible number of characters that
                           can be entered into the input field
            :param swidth: The width of visible part of the input field
            """
            Form.Control.__init__(self)
            self.tp = tp
            self.width = width
            self.swidth = swidth
            self.hlp = hlp
            self.is_relative_offset = is_relative_offset

        def get_tag(self):
            return "%s%d:%s%s:%s:%s" % (
                self.tp, self.id,
                "+" if self.is_relative_offset else "",
                self.width,
                self.swidth,
                ":" if self.hlp is None else self.hlp)

        def is_input_field(self):
            return True


    class NumericInput(InputControl, NumericArgument):
        """
        A composite class serving as a base numeric input control class
        """
        def __init__(self,
                     tp=None,
                     value=0,
                     width=50,
                     swidth=10,
                     hlp=None,
                     is_relative_offset=False,
                     i64=None):
            if tp is None:
                tp = Form.FT_HEX
            Form.InputControl.__init__(self,
                tp, width, swidth, hlp, is_relative_offset)
            Form.NumericArgument.__init__(self, self.tp, value, i64=i64)


    class ColorInput(NumericInput):
        """
        Color button input control
        """
        def __init__(self, value = 0):
            """
            :param value: Initial color value in RGB
            """
            Form.NumericInput.__init__(self, tp=Form.FT_COLOR, value=value, i64=False)


    class StringInput(InputControl, StringArgument):
        """
        Base string input control class.
        This class also constructs a StringArgument
        """
        def __init__(self,
                     tp=None,
                     width=ida_pro.MAXSTR,
                     swidth=40,
                     hlp=None,
                     value=None,
                     size=None):
            """
            :param width: String size. But in some cases it has special meaning. For example in FileInput control.
                          If you want to define the string buffer size then pass the 'size' argument
            :param swidth: Control width
            :param value: Initial value
            :param size: String size
            """
            if tp is None:
                tp = Form.FT_ASCII
            if not size:
                size = width
            Form.InputControl.__init__(self, tp, width, swidth, hlp)
            Form.StringArgument.__init__(self, size=size, value=value)


    class FileInput(StringInput):
        """
        File Open/Save input control
        """
        def __init__(self,
                     width=512,
                     swidth=80,
                     save=False, open=False,
                     hlp=None, value=None):

            if save == open:
                raise ValueError("Invalid mode. Choose either open or save")
            if width < 512:
                raise ValueError("Invalid width. Must be greater than 512.")

            # The width field is overloaded in this control and is used
            # to denote the type of the FileInput dialog (save or load)
            # On the other hand it is passed as is to the StringArgument part
            Form.StringInput.__init__(
                self,
                tp=Form.FT_FILE,
                width="1" if save else "0",
                swidth=swidth,
                hlp=hlp,
                size=width,
                value=value)


    class DirInput(StringInput):
        """
        Directory browsing control
        """
        def __init__(self,
                     width=512,
                     swidth=80,
                     hlp=None,
                     value=None):

            if width < 512:
                raise ValueError("Invalid width. Must be greater than 512.")

            Form.StringInput.__init__(
                self,
                tp=Form.FT_DIR,
                width=width,
                swidth=swidth,
                hlp=hlp,
                size=width,
                value=value)


    class ButtonInput(InputControl):
        """
        Button control.
        A handler along with a 'code' (numeric value) can be associated with the button.
        This way one handler can handle many buttons based on the button code (or in other terms id or tag)
        """
        def __init__(self, handler, code="", swidth="", hlp=None):
            """
            :param handler: Button handler. A callback taking one argument which is the code.
            :param code: A code associated with the button and that is later passed to the handler.
            """
            Form.InputControl.__init__(
                self,
                Form.FT_BUTTON,
                code,
                swidth,
                hlp)
            self.handler = handler
            self.arg = _BUTTONCB_T(self.helper_cb)

        def helper_cb(self, button_code, p_fa):
            # Remember the pointer to the forms_action in the parent form
            self.form.p_fa = p_fa

            # Call user's handler
            r = self.handler(button_code)
            return 0 if r is None else r

        def is_input_field(self):
            return False


    class FormChangeCb(Control):
        """
        Form change handler.
        This can be thought of like a dialog procedure.
        Everytime a form action occurs, this handler will be called along with the control id.
        The programmer can then call various form actions accordingly:
          - EnableField
          - ShowField
          - MoveField
          - GetFieldValue
          - etc...

        Special control IDs: -1 (The form is initialized) and -2 (Ok has been clicked)

        """
        def __init__(self, handler):
            """
            Constructs the handler.
            :param handler: The handler (preferrably a member function of a class derived from the Form class).
            """
            Form.Control.__init__(self)

            # Save the handler
            self.handler = handler

            # Create a callback stub
            # We use this mechanism to create an intermediate step
            # where we can create an 'fa' adapter for use by Python
            self.arg = _FORMCHGCB_T(self.helper_cb)

        def helper_cb(self, fid, p_fa):
            # Remember the pointer to the forms_action in the parent form
            self.form.p_fa = p_fa

            # Call user's handler
            r = self.handler(fid)
            return 0 if r is None else r

        def get_tag(self):
            return Form.FT_FORMCHG

        def free(self):
            Form.Control.free(self)
            # Remove reference to the handler
            # (Normally the handler is a member function in the parent form)
            self.handler = None


    class EmbeddedChooserControl(InputControl):
        """
        Embedded chooser control.
        This control links to a Chooser2 control created with the 'embedded=True'
        """
        def __init__(self,
                     chooser=None,
                     swidth=40,
                     hlp=None):
            """
            Embedded chooser control

            :param chooser: A chooser2 instance (must be constructed with 'embedded=True')
            """

            # !! Make sure a chooser instance is passed !!
            if chooser is None or not isinstance(chooser, Choose):
                raise ValueError("Invalid chooser passed.")

            # Create an embedded chooser structure from the Choose instance,
            # and retrieve the pointer to the chooser_base_t.
            emb = chooser.Embedded(create_chobj=True)
            # if chooser.Embedded() != 0:
            if emb is None:
                raise ValueError("Failed to create embedded chooser instance.")

            # Construct input control
            Form.InputControl.__init__(self, Form.FT_ECHOOSER, "", swidth)

            self.selobj = ida_pro.sizevec_t()

            # Get a pointer to the selection vector
            if sys.version_info.major >= 3:
                sel = self.selobj.this.__int__()
            else:
                sel = self.selobj.this.__long__()

            # Get a pointer to a c_void_p constructed from an address
            p_embedded = ctypes.pointer(ctypes.c_void_p.from_address(emb))
            p_sel      = ctypes.pointer(ctypes.c_void_p.from_address(sel))

            # - Create the embedded chooser info on control creation
            # - Do not free the embeded chooser because after we get the args
            #   via Compile() the user can still call Execute() which relies
            #   on the already computed args
            self.arg   = (p_embedded, p_sel)

            # Save chooser instance
            self.chooser = chooser

            # Add a bogus 'size' attribute
            self.size = 0


        value = property(lambda self: self.chooser)
        """Returns the embedded chooser instance"""

        def __get_selection__(self):
            if len(self.selobj):
                out = []
                for item in self.selobj:
                    out.append(int(item))
                return out
        selection = property(__get_selection__)
        """Returns the selection"""

        def free(self):
            """
            Frees the embedded chooser data
            """
            self.chooser.Close()
            self.chooser = None
            Form.Control.free(self)


    class DropdownListControl(InputControl, ida_pro._qstrvec_t):
        """
        Dropdown control
        This control allows manipulating a dropdown control
        """
        def __init__(self, items=[], readonly=True, selval=0, width=50, swidth=50, hlp = None):
            """
            :param items: A string list of items used to prepopulate the control
            :param readonly: Specifies whether the dropdown list is editable or not
            :param selval: The preselected item index (when readonly) or text value (when editable)
            :param width: the control width (n/a if the dropdown list is readonly)
            :param swidth: string width
            """

            # Ignore the width if readonly was set
            if readonly:
                width = 0

            # Init the input control base class
            Form.InputControl.__init__(
                self,
                Form.FT_DROPDOWN_LIST,
                width,
                swidth,
                hlp)

            # Init the associated qstrvec
            ida_pro._qstrvec_t.__init__(self, items)

            # Remember if readonly or not
            self.readonly = readonly

            if readonly:
                # Create a C integer and remember it
                self.__selval = ctypes.c_int(selval)
                val_addr      = ctypes.addressof(self.__selval)
            else:
                # Create an strvec with one qstring
                self.__selval = ida_pro._qstrvec_t([selval])
                # Get address of the first element
                val_addr      = self.__selval.addressof(0)

            # Two arguments:
            # - argument #1: a pointer to the qstrvec containing the items
            # - argument #2: an integer to hold the selection
            #         or
            #            #2: a qstring to hold the dropdown text control value
            self.arg = (
                ctypes.pointer(ctypes.c_void_p.from_address(self.clink_ptr)),
                ctypes.pointer(ctypes.c_void_p.from_address(val_addr))
            )


        def __set_selval(self, val):
            if self.readonly:
                self.__selval.value = val
            else:
                self.__selval[0] = val

        def __get_selval(self):
            # Return the selection index
            # or the entered text value
            return self.__selval.value if self.readonly else self.__selval[0]

        value  = property(__get_selval, __set_selval)
        selval = property(__get_selval, __set_selval)
        """
        Read/write the selection value.
        The value is used as an item index in readonly mode or text value in editable mode
        This value can be used only after the form has been closed.
        """

        def free(self):
            self._free()


        def set_items(self, items):
            """Sets the dropdown list items"""
            self.from_list(items)


    class MultiLineTextControl(InputControl, textctrl_info_t):
        """
        Multi line text control.
        This class inherits from textctrl_info_t. Thus the attributes are also inherited
        This control allows manipulating a multilinetext control
        """
        def __init__(self, text="", flags=0, tabsize=0, width=50, swidth=50, hlp = None):
            """
            :param text: Initial text value
            :param flags: One of textctrl_info_t.TXTF_.... values
            :param tabsize: Tab size
            :param width: Display width
            :param swidth: String width
            """
            # Init the input control base class
            Form.InputControl.__init__(self, Form.FT_MULTI_LINE_TEXT, width, swidth, hlp)

            # Init the associated textctrl_info base class
            textctrl_info_t.__init__(self, text=text, flags=flags, tabsize=tabsize)

            # Get the argument as a pointer from the embedded ti
            self.arg = ctypes.pointer(ctypes.c_void_p.from_address(self.clink_ptr))


        def free(self):
            self._free()


    #
    # Form class
    #
    def __init__(self, form, controls):
        """
        Contruct a Form class.
        This class wraps around ask_form() or open_form() and provides an easier / alternative syntax for describing forms.
        The form control names are wrapped inside the opening and closing curly braces and the control themselves are
        defined and instantiated via various form controls (subclasses of Form).

        :param form: The form string
        :param controls: A dictionary containing the control name as a _key_ and control object as _value_
        """
        self._reset()
        self.form = form
        """Form string"""
        self.controls = controls
        """Dictionary of controls"""
        self.__args = None

        self.title = None
        """The Form title. It will be filled when the form is compiled"""

        self.modal = True
        """By default, forms are modal"""

        self.openform_flags = 0
        """
        If non-modal, these flags will be passed to open_form.
        This is an OR'ed combination of the PluginForm.FORM_* values.
        """


    def Free(self):
        """
        Frees all resources associated with a compiled form.
        Make sure you call this function when you finish using the form.
        """

        # Free all the controls
        for name, ctrl in self.__controls.items():
            if ctrl.parent_hasattr:
                delattr(self, name)
                ctrl.parent_hasattr = False
            ctrl.free()

        # Reset the controls
        # (Note that we are not removing the form control attributes, no need)
        self._reset()

        # Unregister, so we don't try and free it again at closing-time.
        _ida_kernwin.py_unregister_compiled_form(self)


    def _reset(self):
        """
        Resets the Form class state variables
        """
        self.__controls = {}
        self.__ctrl_id = 1


    def __getitem__(self, name):
        """Returns a control object by name"""
        return self.__controls[name]


    def Add(self, name, ctrl, mkattr = True):
        """
        Low level function. Prefer AddControls() to this function.
        This function adds one control to the form.

        :param name: Control name
        :param ctrl: Control object
        :param mkattr: Create control name / control object as a form attribute
        """
        # Assign a unique ID
        ctrl.id = self.__ctrl_id
        self.__ctrl_id += 1

        # Create attribute with control name
        if mkattr:
            setattr(self, name, ctrl)
            ctrl.parent_hasattr = True

        # Remember the control
        self.__controls[name] = ctrl

        # Link the form to the control via its form attribute
        ctrl.form = self

        # Is it a group? Add each child
        if isinstance(ctrl, Form.GroupControl):
            self._AddGroup(ctrl, mkattr)


    def FindControlById(self, id):
        """
        Finds a control instance given its id
        """
        for ctrl in self.__controls.values():
            if ctrl.id == id:
                return ctrl
        return None


    @staticmethod
    def _ParseFormTitle(form):
        """
        Parses the form's title from the form text
        """
        help_state = 0
        for i, line in enumerate(form.split("\n")):
            if line.startswith("STARTITEM ") or line.startswith("BUTTON "):
                continue
            # Skip "HELP" and remember state
            elif help_state == 0 and line == "HELP":
                help_state = 1 # Mark inside HELP
                continue
            elif help_state == 1 and line == "ENDHELP":
                help_state = 2 # Mark end of HELP
                continue
            return line.strip()

        return None


    def _AddGroup(self, Group, mkattr=True):
        """
        Internal function.
        This function expands the group item names and creates individual group item controls

        :param Group: The group class (checkbox or radio group class)
        """

        # Create group item controls for each child
        for child_name in sorted(Group.children_names):
            self.Add(
                child_name,
                # Use the class factory
                Group.ItemClass(Group.tag, Group),
                mkattr)


    def AddControls(self, controls, mkattr=True):
        """
        Adds controls from a dictionary.
        The dictionary key is the control name and the value is a Form.Control object
        :param controls: The control dictionary
        """
        for name in sorted(controls.keys()):
            # Add the control
            self.Add(name, controls[name], mkattr)


    def CompileEx(self, form):
        """
        Low level function.
        Compiles (parses the form syntax and adds the control) the form string and
        returns the argument list to be passed the argument list to ask_form().

        The form controls are wrapped inside curly braces: {ControlName}.

        A special operator can be used to return the index of a given control by its name: {id:ControlName}.
        This is useful when you use the STARTITEM form keyword to set the initially focused control.
        (note that, technically, the index is not the same as the ID; that's because STARTITEM
        uses raw, 0-based indexes rather than control IDs to determine the focused widget.)

        :param form: Compiles the form and returns the arguments needed to be passed to ask_form()
        """
        # First argument is the form string
        args = [None]

        # Second argument, if form is not modal, is the set of flags
        if not self.modal:
            args.append(self.openform_flags | 0x80) # Add FORM_QWIDGET

        ctrlcnt = 1

        # Reset all group control internal flags
        for ctrl in self.__controls.values():
            if isinstance(ctrl, Form.GroupControl):
                ctrl._reset()

        def next_control(form, p, first_pass):
            i1 = form.find("{", p)
            if i1 < 0:
                return form, None, None, None
            if form[i1 - 1] == '\\' and i1 > 0:
                if first_pass:
                    return next_control(form, i1 + 1, first_pass)
                else:
                    # Remove escape sequence and restart search
                    form = form[:i1 - 1] + form[i1:]
                    return next_control(form, i1, first_pass)
            i2 = form.find("}", i1)
            if i2 < 0:
                raise SyntaxError("No matching closing brace '}'")
            ctrlname = form[i1 + 1:i2]
            if not ctrlname:
                raise ValueError("Control %d has an invalid name!" % ctrlcnt)
            return form, i1, i2, ctrlname


        control_count = 0
        last_input_field_index = 0
        # First pass: assign input_field_index values to controls
        p = 0
        while True:
            form, i1, i2, ctrlname = next_control(form, p, first_pass=True)
            if ctrlname is None:
                break
            p = i2

            if ctrlname.startswith("id:"):
                continue

            ctrl = self.__controls.get(ctrlname, None)
            if ctrl is None:
                raise ValueError("No matching control '%s'" % ctrlname)

            if isinstance(ctrl, Form.FormChangeCb) and control_count > 0:
                raise SyntaxError("Control '%s' should be the first control in the form" % ctrlname)

            # If this control is an input, assign its index
            if ctrl.is_input_field():
                ctrl.input_field_index = last_input_field_index
                last_input_field_index += 1

            control_count += 1


        p = 0
        while True:
            form, i1, i2, ctrlname = next_control(form, p, first_pass=False)
            if ctrlname is None:
                break

            # Is it the IDOF operator?
            if ctrlname.startswith("id:"):
                idfunc = True
                # Take actual ctrlname
                ctrlname = ctrlname[3:]
            else:
                idfunc = False

            # Find the control
            ctrl = self.__controls.get(ctrlname, None)
            if ctrl is None:
                raise ValueError("No matching control '%s'" % ctrlname)

            # Replace control name by tag
            if idfunc:
                tag = str(ctrl.input_field_index if ctrl.input_field_index is not None else ctrl.id)
            else:
                tag = ctrl.get_tag()
            taglen = len(tag)
            form = form[:i1] + tag + form[i2+1:]

            # Set new position
            p = i1 + taglen

            # Was it an IDOF() ? No need to push parameters
            # Just ID substitution is fine
            if idfunc:
                continue


            # For GroupItem controls, there are no individual arguments
            # The argument is assigned for the group itself
            if isinstance(ctrl, Form.GroupItemControl):
                # GroupItem controls will have their position dynamically set
                ctrl.assign_pos()
            else:
                # Push argument(s)
                # (Some controls need more than one argument)
                arg = ctrl.get_arg()
                if isinstance(arg, (list, tuple)):
                    # Push all args
                    args.extend(arg)
                else:
                    # Push one arg
                    args.append(arg)

            ctrlcnt += 1

        # If no FormChangeCb instance was passed, and thus there's no '%/'
        # in the resulting form string, let's provide a minimal one, so that
        # we will retrieve 'p_fa', and thus actions that rely on it will work.
        if form.find(Form.FT_FORMCHG) < 0:
            form = form + Form.FT_FORMCHG
            fccb = Form.FormChangeCb(lambda *args: 1)
            self.Add("___dummyfchgcb", fccb)
            # Regardless of the actual position of '%/' in the form
            # string, a formchange callback _must_ be right after
            # the form string.
            if self.modal:
                inspos = 1
            else:
                inspos = 2
            args.insert(inspos, fccb.get_arg())

        # Patch in the final form string

        if sys.version_info.major >= 3:
            args[0] = form.encode("UTF-8")
        else:
            args[0] = form

        self.title = self._ParseFormTitle(form)
        return args


    def Compile(self):
        """
        Compiles a form and returns the form object (self) and the argument list.
        The form object will contain object names corresponding to the form elements

        :returns: It will raise an exception on failure. Otherwise the return value is ignored
        """

        # Reset controls
        self._reset()

        # Insert controls
        self.AddControls(self.controls)

        # Compile form and get args
        self.__args = self.CompileEx(self.form)

        # Register this form, to make sure it will be freed at closing-time.
        _ida_kernwin.py_register_compiled_form(self)

        return (self, self.__args)


    def Compiled(self):
        """
        Checks if the form has already been compiled

        :returns: Boolean
        """
        return self.__args is not None


    def _ChkCompiled(self):
        if not self.Compiled():
            raise SyntaxError("Form is not compiled")


    def Execute(self):
        """
        Displays a modal dialog containing the compiled form.
        :returns: 1 - ok ; 0 - cancel
        """
        self._ChkCompiled()
        if not self.modal:
            raise SyntaxError("Form is not modal. Open() should be instead")

        return ask_form(*self.__args)


    def Open(self):
        """
        Opens a widget containing the compiled form.
        """
        self._ChkCompiled()
        if self.modal:
            raise SyntaxError("Form is modal. Execute() should be instead")

        open_form(*self.__args)


    def EnableField(self, ctrl, enable):
        """
        Enable or disable an input field
        :returns: False - no such control
        """
        return _ida_kernwin.formchgcbfa_enable_field(self.p_fa, ctrl.id, enable)


    def ShowField(self, ctrl, show):
        """
        Show or hide an input field
        :returns: False - no such control
        """
        return _ida_kernwin.formchgcbfa_show_field(self.p_fa, ctrl.id, show)


    def MoveField(self, ctrl, x, y, w, h):
        """
        Move/resize an input field

        :returns: False - no such fiel
        """
        return _ida_kernwin.formchgcbfa_move_field(self.p_fa, ctrl.id, x, y, w, h)


    def GetFocusedField(self):
        """
        Get currently focused input field.
        :returns: None if no field is selected otherwise the control ID
        """
        id = _ida_kernwin.formchgcbfa_get_focused_field(self.p_fa)
        return self.FindControlById(id)


    def SetFocusedField(self, ctrl):
        """
        Set currently focused input field
        :returns: False - no such control
        """
        return _ida_kernwin.formchgcbfa_set_focused_field(self.p_fa, ctrl.id)


    def RefreshField(self, ctrl):
        """
        Refresh a field
        :returns: False - no such control
        """
        return _ida_kernwin.formchgcbfa_refresh_field(self.p_fa, ctrl.id)


    def Close(self, close_normally):
        """
        Close the form
        :param close_normally: 1: form is closed normally as if the user pressed Enter. 0: form is closed abnormally as if the user pressed Esc
        :returns: None
        """
        return _ida_kernwin.formchgcbfa_close(self.p_fa, close_normally)


    def GetControlValue(self, ctrl):
        """
        Returns the control's value depending on its type
        :param ctrl: Form control instance
        :returns: color button, radio controls: integer
        :returns: file/dir input, string input and string label: string
        :returns: embedded chooser control (0-based indices of selected items): integer list
        :returns: for multilinetext control: textctrl_info_t
        :returns: dropdown list controls: string (when editable) or index (when readonly)
        :returns: None: on failure
        """
        tid, size = self.ControlToFieldTypeIdAndSize(ctrl)
        r = _ida_kernwin.formchgcbfa_get_field_value(
                    self.p_fa,
                    ctrl.id,
                    tid,
                    size)
        # Multilinetext? Unpack the tuple into a new textctrl_info_t instance
        if r is not None and tid == 7:
            return textctrl_info_t(text=r[0], flags=r[1], tabsize=r[2])
        else:
            return r


    def SetControlValue(self, ctrl, value):
        """
        Set the control's value depending on its type
        :param ctrl: Form control instance
        :param value: embedded chooser: a 0-base indices list to select embedded chooser items
        :param value: multilinetext: a textctrl_info_t
        :param value: dropdown list: an integer designating the selection index if readonly
                                     a string designating the edit control value if not readonly
        :returns: Boolean true on success
        """
        tid, _ = self.ControlToFieldTypeIdAndSize(ctrl)
        return _ida_kernwin.formchgcbfa_set_field_value(
                    self.p_fa,
                    ctrl.id,
                    tid,
                    value)


    @staticmethod
    def ControlToFieldTypeIdAndSize(ctrl):
        """
        Converts a control object to a tuple containing the field id
        and the associated buffer size
        """
        # Input control depend on the associated buffer size (supplied by the user)

        # Make sure you check instances types taking into account inheritance
        if isinstance(ctrl, Form.DropdownListControl):
            return (8, 1 if ctrl.readonly else 0)
        elif isinstance(ctrl, Form.MultiLineTextControl):
            return (7, 0)
        elif isinstance(ctrl, Form.EmbeddedChooserControl):
            return (5, 0)
        # Group items or controls
        elif isinstance(ctrl, (Form.GroupItemControl, Form.GroupControl)):
            return (2, 0)
        elif isinstance(ctrl, Form.StringLabel):
            return (3, min(ida_pro.MAXSTR, ctrl.size))
        elif isinstance(ctrl, Form.ColorInput):
            return (4, 0)
        elif isinstance(ctrl, Form.NumericInput):
            # Pass the numeric control type
            return (6, ord(ctrl.tp[0]))
        elif isinstance(ctrl, Form.InputControl):
            return (1, ctrl.size)
        else:
            raise NotImplementedError("Not yet implemented")

# --------------------------------------------------------------------------
# Instantiate ask_form/open_form function pointers
try:
    import ctypes
# Setup the numeric argument size
    Form.NumericArgument.DefI64 = _ida_idaapi.BADADDR == 0xFFFFFFFFFFFFFFFF
# int ask_form(const char *form, ...)
    __ask_form_callable = ctypes.CFUNCTYPE(ctypes.c_int)(_ida_kernwin.py_get_ask_form())
# specify types of the fixed arguments explicitly so that varargs are passed correctly on arm macOS
# https://bugs.python.org/issue42880
    __ask_form_callable.argtypes = [ ctypes.c_char_p ]
#  TWidget *open_form(const char *form, uint32 flags, ...)
    __open_form_callable = ctypes.CFUNCTYPE(ctypes.c_void_p )(_ida_kernwin.py_get_open_form())
    __open_form_callable.argtypes = [ ctypes.c_char_p, ctypes.c_uint32 ]
except:
    def __ask_form_callable(*args):
        warning("ask_form() needs ctypes library in order to work")
        return 0
    def __open_form_callable(*args):
        warning("open_form() needs ctypes library in order to work")

def __call_form_callable(call, *args):
    assert(len(args))
    with disabled_script_timeout_t():
        if sys.version_info.major >= 3 and isinstance(args[0], str):
            largs = list(args)
            largs[0] = largs[0].encode("UTF-8")
            args = tuple(largs)
        r = call(*args)
    return r

def ask_form(*args):
    return __call_form_callable(__ask_form_callable, *args)

def open_form(*args):
    if len(args) == 1:
        args = (args[0], 0) # add default flags
    return __call_form_callable(__open_form_callable, *args)

#</pycode(py_kernwin_askform)>
