# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# XBMC Config Menu
# ------------------------------------------------------------

from __future__ import division
import sys, os, inspect, xbmcgui, xbmc
PY3 = False
if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
from builtins import range
from past.utils import old_div

from core import channeltools, servertools, scrapertools
from platformcode import config, logger, platformtools
from core.support import info, dbg, match


class SettingsWindow(xbmcgui.WindowXMLDialog):
    """
    Derived class that allows you to use custom configuration boxes.

    This class is derived from xbmcgui.WindowXMLDialog and allows you to create a dialog box with controls of the type:
    Radio Button (bool), Text Box (text), List (list) and Information Labels (label).
    We can also customize the box by adding a title (title).

    Construction method:
        SettingWindow(list_controls, dict_values, title, callback, item)
            Parameters:
                list_controls: (list) List of controls to include in the window, according to the following scheme:
                    (opcional)list_controls= [
                                {'id': "nameControl1",
                                  'type': "bool",                       # bool, text, list, label
                                  'label': "Control 1: type RadioButton",
                                  'color': '0xFFee66CC',                # text color in hexadecimal ARGB format
                                  'default': True,
                                  'enabled': True,
                                  'visible': True
                                },
                                {'id': "nameControl2",
                                  'type': "text",                       # bool, text, list, label
                                  'label': "Control 2: type text box",
                                  'color': '0xFFee66CC',
                                  'default': "Valor por defecto",
                                  'hidden': False,                      # only for type = text Indicates whether to hide the text (for passwords)
                                  'enabled': True,
                                  'visible': True
                                },
                                {'id': "nameControl3",
                                  'type': "list",                       # bool, text, list, label
                                  'label': "Control 3: type List",
                                  'color': '0xFFee66CC',
                                  'default': 0,                         # Default value index in lvalues
                                  'enabled': True,
                                  'visible': True,
                                  'lvalues':["item1", "item2", "item3", "item4"],  # only for type = list
                                },
                                {'id': "nameControl4",
                                  'type': "label",                       # bool, text, list, label
                                  'label': "Control 4: tipo Etiqueta",
                                  'color': '0xFFee66CC',
                                  'enabled': True,
                                  'visible': True
                                }]
                    If the controls list is not included, an attempt is made to obtain the json of the channel from which the call is made.

                    The format of the controls in the json is:
                        {
                            ...
                            ...
                            "settings": [
                                {
                                   "id": "name_control_1",
                                    "type": "bool",
                                    "label": "Control 1: type RadioButton",
                                    "default": false,
                                    "enabled": true,
                                    "visible": true,
                                    "color": "0xFFee66CC"
                                },
                                {
                                    "id": "name_control_2",
                                    "type": "text",
                                    "label": "Control 2: type text box",
                                    "default": "Default value",
                                    "hidden": true,
                                    "enabled": true,
                                    "visible": true,
                                    "color": "0xFFee66CC"
                                },
                                {
                                    "id": "name_control_3",
                                    "type": "list",
                                    "label": "Control 3: type List",
                                    "default": 0,
                                    "enabled": true,
                                    "visible": true,
                                    "color": "0xFFee66CC",
                                    "lvalues": [
                                        "item1",
                                        "item2",
                                        "item3",
                                        "item4"
                                    ]
                                },
                                {
                                    "id": "name_control_4",
                                    "type": "label",
                                    "label": "Control 4: tipo Etiqueta",
                                    "enabled": true,
                                    "visible": true,
                                    "color": "0xFFee66CC"
                                },
                            ]
                        }

                The fields 'label', 'default', 'enabled' and 'lvalues' can be a number preceded by '@'. In which case
                it will look for the literal in the string.xml file of the selected language.
                The 'enabled' and 'visible' fields support the comparators eq (), gt () and it () and their operation is
                described at: http://kodi.wiki/view/Add-on_settings#Different_types

                (opcional) dict_values: (dict) Dictionary representing the pair (id: value) of the controls in the list.
                If any control in the list is not included in this dictionary, it will be assigned the default value.
                        dict_values={"nameControl1": False,
                                     "nameControl2": "Esto es un ejemplo"}

                (opcional) caption: (str) Configuration window title. It can be located by a number preceded by '@'
                (opcional) callback (str) Name of the function, of the channel from which the call is made, which will be
                invoked when pressing the accept button in the window. This function will be passed as parameters the
                object 'item' and the 'dict_values' dictionary. If this parameter does not exist, the channel is searched for
                 function called 'cb_validate_config' and if it exists it is used as a callback.

            Retorno: If 'callback' is specified or the channel includes 'cb_validate_config' what that function returns will be returned. If not return none

    Usage examples:
        platformtools.show_channel_settings(): As such, without passing any argument, the window detects which channel the call has been made,
                                               and read the json settings and load the controls, when you click OK, save them again.

        return platformtools.show_channel_settings(list_controls=list_controls, dict_values=dict_values, callback='cb', item=item):
            This opens the window with the passed controls and the dict_values values, if dict_values is not passed, it loads the default values of the controls,
            when you accept, it calls the 'callback' function of the channel from where it was called, passing as parameters, item and dict_values
    """

    def start(self, list_controls=None, dict_values=None, caption="", callback=None, item=None, custom_button=None, channelpath=None):
        info()

        # Media Path
        self.mediapath = os.path.join(config.get_runtime_path(), 'resources', 'skins', 'Default', 'media')

        # Params
        self.list_controls = list_controls
        self.values = dict_values
        self.caption = caption
        self.callback = callback
        self.item = item

        if isinstance(custom_button, dict):
            self.custom_button = {}
            self.custom_button["label"] = custom_button.get("label", "")
            self.custom_button["function"] = custom_button.get("function", "")
            self.custom_button["visible"] = bool(custom_button.get("visible", True))
            self.custom_button["close"] = bool(custom_button.get("close", False))
        else:
            self.custom_button = None

        # Load Channel Settings
        if not channelpath:
            channelpath = inspect.currentframe().f_back.f_back.f_code.co_filename
        self.channel = os.path.basename(channelpath).replace(".py", "")
        self.ch_type = os.path.basename(os.path.dirname(channelpath))
        # If list_controls does not exist, it is removed from the channel json
        if not self.list_controls:

            # If the channel path is in the "channels" folder, we get the controls and values using chaneltools
            if os.path.join(config.get_runtime_path(), "channels") in channelpath or os.path.join(config.get_runtime_path(), "specials") in channelpath:

                # The call is made from a channel
                self.list_controls, default_values = channeltools.get_channel_controls_settings(self.channel)
                self.kwargs = {"channel": self.channel}
                self.channelName = channeltools.get_channel_json(self.channel)['name']

            # If the channel path is in the "servers" folder, we get the controls and values through servertools
            elif os.path.join(config.get_runtime_path(), "servers") in channelpath:

                # The call is made from a channel
                self.list_controls, default_values = servertools.get_server_controls_settings(self.channel)
                self.kwargs = {"server": self.channel}
                self.channelName = servertools.get_server_parameters(self.channel)['name']

            # Else Exit
            else:
                return None

        # If dict_values are not passed, create a blank dict
        if self.values is None:
            self.values = {}

        # Make title
        if self.caption == "":
            self.caption = str(config.get_localized_string(30100)) + ' - ' + self.channelName

        matches = match(self.caption, patron=r'@(\d+)').matches
        if matches:
            for m in matches:
                self.caption = self.caption.replace('@' + match, config.get_localized_string(int(m)))

        # Show Window
        self.return_value = None
        self.doModal()
        return self.return_value

    @staticmethod
    def set_enabled(c, val):
        if c["type"] == "list":
            c["control"].setEnabled(val)
            c["label"].setEnabled(val)
        else:
            c["control"].setEnabled(val)

    @staticmethod
    def set_visible(c, val):
        if c["type"] == "list":
            c["control"].setVisible(val)
            c["label"].setVisible(val)
        else:
            c["control"].setVisible(val)

    def evaluate_conditions(self):
        for c in self.list_controls:
            c["active"] = self.evaluate(self.list_controls.index(c), c["enabled"])
            self.set_enabled(c, c["active"])
            c["show"] = self.evaluate(self.list_controls.index(c), c["visible"])
            if not c["show"]:
                self.set_visible(c, c["show"])
        self.visible_controls = [c for c in self.list_controls if c["show"]]

    def evaluate(self, index, cond):
        import re

        ok = False

        # If the condition is True or False, there is nothing else to evaluate, that is the value
        if isinstance(cond, bool):
            return cond

        # Get the conditions
        # dbg()
        conditions = re.compile(r'''(!?eq|!?gt|!?lt)?\s*\(\s*([^, ]+)\s*,\s*["']?([^"'\)]+)["']?\)([+|])?''').findall(cond)
        for operator, id, value, next in conditions:
            matches = match(value, patron=r'@(\d+)').matches
            if matches:
                for m in matches:
                    value = value.replace('@' + m, config.get_localized_string(int(m)))
            try:
                id = int(id)
            except:
                return False

            # The control to evaluate on has to be within range, otherwise it returns False
            if index + id < 0 or index + id >= len(self.list_controls):
                return False

            else:
                # Obtain the value of the control on which it is compared
                c = self.list_controls[index + id]
                if c["type"] == "bool": control_value = bool(c["control"].isSelected())
                if c["type"] == "text": control_value = c["control"].getText()
                if c["type"] == "list": control_value = c["label"].getLabel()
                if c["type"] == "label": control_value = c["control"].getLabel()

            # Operations lt "less than" and gt "greater than" require comparisons to be numbers, otherwise it returns
            # False
            if operator in ["lt", "!lt", "gt", "!gt"]:
                try:
                    value = int(value)
                except ValueError:
                    return False

            # Operation eq "equal to"
            if operator in ["eq", "!eq"]:
                # int
                try:
                    value = int(value)
                except ValueError:
                    pass

                # bool
                if not isinstance(value, int) and value.lower() == "true":
                    value = True
                elif not isinstance(value, int) and value.lower() == "false":
                    value = False

            # Operation eq "equal to"
            if operator == "eq":
                if control_value == value:
                    ok = True
                else:
                    ok = False

            # Operation !eq "not equal to"
            if operator == "!eq":
                if not control_value == value:
                    ok = True
                else:
                    ok = False

            # operation "gt" "greater than"
            if operator == "gt":
                if control_value > value:
                    ok = True
                else:
                    ok = False

            # operation "!gt" "not greater than"
            if operator == "!gt":
                if not control_value > value:
                    ok = True
                else:
                    ok = False

            # operation "lt" "less than"
            if operator == "lt":
                if control_value < value:
                    ok = True
                else:
                    ok = False

            # operation "!lt" "not less than"
            if operator == "!lt":
                if not control_value < value:
                    ok = True
                else:
                    ok = False

            # Next operation, if it is "|" (or) and the result is True, there is no sense to follow, it is True
            if next == "|" and ok is True:
                break
            # Next operation, if it is "+" (and) and the result is False, there is no sense to follow, it is False
            if next == "+" and ok is False:
                break

        return ok


    def add_control_label(self, c):
        control = xbmcgui.ControlLabel(0, -100, self.controls_width + 20, 40, "", alignment=4, font=self.font, textColor=c["color"])

        self.addControl(control)

        control.setVisible(False)
        control.setLabel(c["label"])
        c["control"] = control


    def add_control_list(self, c):
        control = xbmcgui.ControlButton(0, -100, self.controls_width + 10, self.height_control, c["label"],
                                        os.path.join(self.mediapath, 'Controls', 'MenuItemFO.png'),
                                        os.path.join(self.mediapath, 'Controls', 'MenuItemNF.png'),
                                        10, textColor=c["color"], font=self.font)
        label = xbmcgui.ControlLabel(0, -100, self.controls_width, self.height_control,  "", font=self.font, textColor=c["color"], alignment= 1 | 4)

        self.addControl(control)
        self.addControl(label)

        control.setVisible(False)
        label.setVisible(False)
        label.setLabel(c["lvalues"][self.values[c["id"]]])

        c["control"] = control
        c["label"] = label


    def add_control_text(self, c):
        control = xbmcgui.ControlEdit(0, -100, self.controls_width, self.height_control,
                                    c["label"], self.font, c["color"], '', 1 | 4,
                                    focusTexture='',
                                    noFocusTexture='')

        image = xbmcgui.ControlImage(0, -100, self.controls_width + 10, self.height_control, os.path.join(self.mediapath, 'Controls', 'MenuItemFO.png'))

        self.addControl(image)
        self.addControl(control)
        image.setVisibleCondition('Control.HasFocus(%s)' % control.getId(), True) 

        control.setVisible(False)
        control.setLabel(c["label"])
        if c['hidden']: control.setType(xbmcgui.INPUT_TYPE_PASSWORD, c["label"])
        # frodo fix
        s = self.values[c["id"]]
        if s is None: s = c['default'] if 'default' in c else ''

        control.setText(s)
        control.setWidth(self.controls_width-10)
        control.setHeight(self.height_control)

        c["control"] = control
        c['image'] = image

    def add_control_bool(self, c):
        # Old versions do not support some textures
        if xbmcgui.__version__ in ["1.2", "2.0"]:
            control = xbmcgui.ControlRadioButton(0, -100, self.controls_width + 20, self.height_control,
                                                 label=c["label"], font=self.font, textColor=c["color"],
                                                 focusTexture=os.path.join(self.mediapath, 'Controls', 'MenuItemFO.png'),
                                                 noFocusTexture=os.path.join(self.mediapath, 'Controls', 'MenuItemNF.png'))
        else:
            control = xbmcgui.ControlRadioButton(0, -100, self.controls_width + 20,
                                                 self.height_control, label=c["label"], font=self.font,
                                                 textColor=c["color"],
                                                 focusTexture=os.path.join(self.mediapath, 'Controls', 'MenuItemFO.png'),
                                                 noFocusTexture=os.path.join(self.mediapath, 'Controls', 'MenuItemNF.png'),
                                                 focusOnTexture=os.path.join(self.mediapath, 'Controls', 'radiobutton-focus.png'),
                                                 noFocusOnTexture=os.path.join(self.mediapath, 'Controls', 'radiobutton-focus.png'),
                                                 focusOffTexture=os.path.join(self.mediapath, 'Controls', 'radiobutton-nofocus.png'),
                                                 noFocusOffTexture=os.path.join(self.mediapath, 'Controls', 'radiobutton-nofocus.png'))
       
        image = xbmcgui.ControlImage(0, -100, self.controls_width + 10, self.height_control, os.path.join(self.mediapath, 'Controls', 'MenuItemFO.png'))
        self.addControl(image)
        self.addControl(control)
        image.setVisibleCondition('Control.HasFocus(%s)' % control.getId(), True) 
        control.setVisible(False)
        control.setRadioDimension(x=self.controls_width - (self.height_control - 5), y=0, width=self.height_control - 5, height=self.height_control - 5)
        control.setSelected(self.values[c["id"]])

        c["control"] = control
        c['image'] = image

    def onInit(self):
        self.getControl(10004).setEnabled(False)
        self.getControl(10005).setEnabled(False)
        self.getControl(10006).setEnabled(False)
        self.ok_enabled = False
        self.default_enabled = False

        # Kodi 18 compatibility
        if config.get_platform(True)['num_version'] < 18:
            if xbmcgui.__version__ == "1.2":
                self.setCoordinateResolution(1)
            else:
                self.setCoordinateResolution(5)

        # Title
        self.getControl(10002).setLabel(self.caption)

        if self.custom_button is not None:
            if self.custom_button['visible']:
                self.getControl(10006).setLabel(self.custom_button['label'])
            else:
                self.getControl(10006).setVisible(False)

        # Control Area Dimensions
        self.controls_width = self.getControl(10007).getWidth() - 30
        self.controls_height = self.getControl(10007).getHeight() -100
        self.controls_pos_x = self.getControl(10007).getPosition()[0] + self.getControl(10001).getPosition()[0] + 10
        self.controls_pos_y = self.getControl(10007).getPosition()[1] + self.getControl(10001).getPosition()[1]
        self.height_control = 60
        self.font = "font16"

        # In old versions: we create 5 controls, from the contrary when clicking the second control,
        # automatically change third party label to "Short By: Name" I don't know why ...
        if xbmcgui.ControlEdit == ControlEdit:
            for x in range(5):
                control = xbmcgui.ControlRadioButton(-500, 0, 0, 0, "")
                self.addControl(control)

        for c in self.list_controls:
            # Skip controls that do not have the appropriate values
            if "type" not in c: continue
            if "label" not in c: continue
            if c["type"] != "label" and "id" not in c: continue
            if c["type"] == "list" and "lvalues" not in c: continue
            if c["type"] == "list" and not isinstance(c["lvalues"], list): continue
            if c["type"] == "list" and not len(c["lvalues"]) > 0: continue
            if c["type"] != "label" and len([control.get("id") for control in self.list_controls if c["id"] == control.get("id")]) > 1: continue

            # Translation label and lvalues
            if c['label'].startswith('@') and unicode(c['label'][1:]).isnumeric(): c['label'] = config.get_localized_string(int(c['label'][1:]))
            if c['type'] == 'list':
                lvalues = []
                for li in c['lvalues']:
                    if li.startswith('@') and unicode(li[1:]).isnumeric(): lvalues.append(config.get_localized_string(int(li[1:])))
                    else: lvalues.append(li)
                c['lvalues'] = lvalues

            # Default values in case the control does not have them
            if c["type"] == "bool": default = False
            elif c["type"] == "list": default = 0
            else: default = "" # label or text

            c["default"] = c.get("default", default)
            c["color"] = c.get("color", "0xFFFFFFFF")
            c["visible"] = c.get("visible", True)
            c["enabled"] = c.get("enabled", True)

            if c["type"] == "label" and "id" not in c: c["id"] = None
            if c["type"] == "text": c["hidden"] = c.get("hidden", False)

            # Decide whether to use the default value or the saved value
            if c["type"] in ["bool", "text", "list"]:
                if c["id"] not in self.values:
                    if not self.callback: self.values[c["id"]] = config.get_setting(c["id"], **self.kwargs)
                    else: self.values[c["id"]] = c["default"]

            if c["type"] == "bool": self.add_control_bool(c)
            elif c["type"] == 'text': self.add_control_text(c)
            elif c["type"] == 'list': self.add_control_list(c)
            elif c["type"] == 'label': self.add_control_label(c)
        self.list_controls = [c for c in self.list_controls if "control" in c]

        self.evaluate_conditions()
        self.index = -1
        self.dispose_controls(0)
        self.getControl(100010).setVisible(False)
        self.getControl(10004).setEnabled(True)
        self.getControl(10005).setEnabled(True)
        self.getControl(10006).setEnabled(True)
        self.ok_enabled = True
        self.default_enabled = True
        self.check_default()
        self.check_ok(self.values)

    def dispose_controls(self, index, focus=False, force=False):
        show_controls = old_div(self.controls_height, self.height_control) - 1

        visible_count = 0

        if focus:
            if not index >= self.index or not index <= self.index + show_controls:
                if index < self.index: new_index = index
                else: new_index = index - show_controls
            else:new_index = self.index
        else:
            if index + show_controls >= len(self.visible_controls): index = len(self.visible_controls) - show_controls - 1
            if index < 0: index = 0
            new_index = index

        if self.index != new_index or force:
            for x, c in enumerate(self.visible_controls):
                if x < new_index or visible_count > show_controls or not c["show"]:
                    self.set_visible(c, False)
                else:
                    c["y"] = self.controls_pos_y + visible_count * self.height_control
                    visible_count += 1

                    if c["type"] == "list":
                        c["control"].setPosition(self.controls_pos_x, c["y"])
                        if xbmcgui.__version__ == "1.2": c["label"].setPosition(self.controls_pos_x + self.controls_width - 30, c["y"])
                        else: c["label"].setPosition(self.controls_pos_x, c["y"])

                    else:
                        if c["type"] == "bool": c["control"].setPosition(self.controls_pos_x, c["y"])
                        elif c['type'] == 'text': c["control"].setPosition(self.controls_pos_x +10, c["y"])
                        else: c["control"].setPosition(self.controls_pos_x, c["y"])
                        if c['type'] in ['bool', 'text']:c['image'].setPosition(self.controls_pos_x, c["y"])

                    self.set_visible(c, True)

            # Calculate the position and size of the ScrollBar
            hidden_controls = len(self.visible_controls) - show_controls - 1
            if hidden_controls < 0: hidden_controls = 0

            scrollbar_height = self.getControl(10008).getHeight() - (hidden_controls * 3)
            scrollbar_y = self.getControl(10008).getPosition()[1] + (new_index * 3)
            self.getControl(10009).setPosition(self.getControl(10008).getPosition()[0], scrollbar_y)
            self.getControl(10009).setHeight(scrollbar_height)

        self.index = new_index

        if focus:
            self.setFocus(self.visible_controls[index]["control"])

    def check_ok(self, dict_values=None):
        if not self.callback:
            if dict_values:
                self.init_values = dict_values.copy()
                self.getControl(10004).setEnabled(False)
                self.ok_enabled = False

            else:
                if self.init_values == self.values:
                    self.getControl(10004).setEnabled(False)
                    self.ok_enabled = False
                else:
                    self.getControl(10004).setEnabled(True)
                    self.ok_enabled = True

    def check_default(self):
        if self.custom_button is None:
            def_values = dict([[c["id"], c.get("default")] for c in self.list_controls if not c["type"] == "label"])

            if def_values == self.values:
                self.getControl(10006).setEnabled(False)
                self.default_enabled = False
            else:
                self.getControl(10006).setEnabled(True)
                self.default_enabled = True

    def onClick(self, id):
        # Default values
        if id == 10006:
            if self.custom_button is not None:
                if self.custom_button["close"]:
                    self.close()

                if '.' in self.callback: package, self.callback = self.callback.rsplit('.', 1)
                else: package = '%s.%s' % (self.ch_type, self.channel)

                try: cb_channel = __import__(package, None, None, [package])
                except ImportError: logger.error('Imposible importar %s' % package)
                else:
                    self.return_value = getattr(cb_channel, self.custom_button['function'])(self.item, self.values)
                    if not self.custom_button["close"]:
                        if isinstance(self.return_value, dict) and "label" in self.return_value: self.getControl(10006).setLabel(self.return_value['label'])

                        for c in self.list_controls:
                            if c["type"] == "text": c["control"].setText(self.values[c["id"]])
                            if c["type"] == "bool": c["control"].setSelected(self.values[c["id"]])
                            if c["type"] == "list": c["label"].setLabel(c["lvalues"][self.values[c["id"]]])

                        self.evaluate_conditions()
                        self.dispose_controls(self.index, force=True)

            else:
                for c in self.list_controls:
                    if c["type"] == "text":
                        c["control"].setText(c["default"])
                        self.values[c["id"]] = c["default"]
                    if c["type"] == "bool":
                        c["control"].setSelected(c["default"])
                        self.values[c["id"]] = c["default"]
                    if c["type"] == "list":
                        c["label"].setLabel(c["lvalues"][c["default"]])
                        self.values[c["id"]] = c["default"]

                self.evaluate_conditions()
                self.dispose_controls(self.index, force=True)
                self.check_default()
                self.check_ok()

        # Cancel button [X]
        if id == 10003 or id == 10005:
            self.close()

        # OK button
        if id == 10004:
            self.close()
            if self.callback and '.' in self.callback: package, self.callback = self.callback.rsplit('.', 1)
            else: package = '%s.%s' % (self.ch_type, self.channel)

            cb_channel = None

            try: cb_channel = __import__(package, None, None, [package])
            except ImportError:logger.error('Impossible to import %s' % package)

            if self.callback:
                # If there is a callback function we invoke it...
                self.return_value = getattr(cb_channel, self.callback)(self.item, self.values)
            else:
                # if not, we test if there is a 'cb_validate_config' function in the channel...
                try:
                    self.return_value = getattr(cb_channel, 'cb_validate_config')(self.item, self.values)
                except AttributeError:
                    # if 'cb_validate_config' doesn't exist either ...
                    for v in self.values:
                        config.set_setting(v, self.values[v], **self.kwargs)

        # Adjustment controls, if the value of an adjustment is changed, we change the value saved in the value dictionary
        # Get the control that has been clicked
        # control = self.getControl(id)

        # We look it up in the list of controls
        for cont in self.list_controls:

            if cont['type'] == "list" and cont["control"].getId() == id:
                select = platformtools.dialog_select(config.get_localized_string(30041), cont["lvalues"], self.values[cont["id"]])
                if select >= 0:
                    cont["label"].setLabel(cont["lvalues"][select])
                    self.values[cont["id"]] = cont["lvalues"].index(cont["label"].getLabel())

            # If the control is a "bool", we save the new value True / False
            if cont["type"] == "bool" and cont["control"].getId() == id: self.values[cont["id"]] = bool(cont["control"].isSelected())

            # If the control is a "text", we save the new value
            if cont["type"] == "text" and cont["control"].getId() == id:
                # Older versions require opening the keyboard manually
                if xbmcgui.ControlEdit == ControlEdit:
                    keyboard = xbmc.Keyboard(cont["control"].getText(), cont["control"].getLabel(), cont["control"].isPassword)
                    keyboard.setHiddenInput(cont["control"].isPassword)
                    keyboard.doModal()
                    if keyboard.isConfirmed(): cont["control"].setText(keyboard.getText())

                self.values[cont["id"]] = cont["control"].getText()

        self.evaluate_conditions()
        self.dispose_controls(self.index, force=True)
        self.check_default()
        self.check_ok()

    # Older versions require this feature
    def onFocus(self, a):
        pass

    def onAction(self, raw_action):
        # Get Focus
        focus = self.getFocusId()

        action = raw_action.getId()
        # On Left
        if action == 1:
            # if Focus is on close button
            if focus == 10003:
                self.dispose_controls(0, True)
                self.setFocusId(3001)

            # if focus is on List
            else:
                if self.ok_enabled:
                    self.setFocusId(10004)
                else:
                    self.setFocusId(10005)

        # On Right
        elif action == 2:
            # if Focus is on button
            if focus in [10004, 10005, 10006]:
                self.dispose_controls(0, True)
                self.setFocusId(3001)

            # if focus is on List
            else:
                self.setFocusId(10003)

        # On Down
        elif action == 4:
            # if focus is on List
            if focus not in [10004, 10005, 10006]:
                try:
                    focus_control = [self.visible_controls.index(c) for c in self.visible_controls if c["control"].getId() == self.getFocus().getId()][0]
                    focus_control += 1

                except:
                    focus_control = 0

                while not focus_control == len(self.visible_controls) and (self.visible_controls[focus_control]["type"] == "label" or not self.visible_controls[focus_control]["active"]):
                    focus_control += 1

                if focus_control >= len(self.visible_controls):
                    focus_control = 0
                    self.setFocusId(3001)

                self.dispose_controls(focus_control, True)

            # Else navigate on main buttons
            elif focus in [10004]:
                self.setFocusId(10005)
            elif focus in [10005]:
                if self.default_enabled: self.setFocusId(10006)
                elif self.ok_enabled: self.setFocusId(10004)
            elif focus in [10006]:
                if self.ok_enabled: self.setFocusId(10004)
                else: self.setFocusId(10005)

        # On Up
        elif action == 3:
            # if focus is on List
            if focus not in [10003, 10004, 10005, 10006]:
                try:
                    focus_control = \
                        [self.visible_controls.index(c) for c in self.visible_controls if c["control"].getId() == self.getFocus().getId()][0]
                    focus_control -= 1

                    while not focus_control == -1 and (self.visible_controls[focus_control]["type"] == "label" or not
                    self.visible_controls[focus_control]["active"]):
                        focus_control -= 1

                    if focus_control < 0: 
                        focus_control = len(self.visible_controls) - 1

                except:
                    focus_control = 0

                self.dispose_controls(focus_control, True)

            # Else navigate on main buttons
            elif focus in [10004]:
                if self.default_enabled: self.setFocusId(10006)
                else: self.setFocusId(10005)
            elif focus in [10005]:
                if self.ok_enabled: self.setFocusId(10004)
                elif self.default_enabled: self.setFocusId(10006)
            elif focus in [10006]:
                self.setFocusId(10005)



        # Accion 104: Scroll Down
        elif action == 104:
            self.dispose_controls(self.index - 1)

        # Accion 105: Scroll Up
        elif action == 105:
            self.dispose_controls(self.index + 1)

        # ACTION_PREVIOUS_MENU 10
        # ACTION_NAV_BACK 92
        elif action in [10, 92]:
            self.close()

        elif action == 501:
            self.xx = int(raw_action.getAmount2())

        elif action == 504:

            if self.xx > raw_action.getAmount2():
                if old_div((self.xx - int(raw_action.getAmount2())), self.height_control):
                    self.xx -= self.height_control
                    self.dispose_controls(self.index + 1)
            else:
                if old_div((int(raw_action.getAmount2()) - self.xx), self.height_control):
                    self.xx += self.height_control
                self.dispose_controls(self.index - 1)
            return



class ControlEdit(xbmcgui.ControlButton):
    def __new__(cls, *args, **kwargs):
        del kwargs["isPassword"]
        del kwargs["window"]
        args = list(args)
        return xbmcgui.ControlButton.__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        self.isPassword = kwargs["isPassword"]
        self.window = kwargs["window"]
        self.label = ""
        self.text = ""
        self.textControl = xbmcgui.ControlLabel(self.getX(), self.getY(), self.getWidth(), self.getHeight(), self.text,
                                                font=kwargs["font"], textColor=kwargs["textColor"], alignment= 4 | 1)
        self.window.addControl(self.textControl)

    def setLabel(self, val):
        self.label = val
        xbmcgui.ControlButton.setLabel(self, val)

    def getX(self):
        return xbmcgui.ControlButton.getPosition(self)[0]

    def getY(self):
        return xbmcgui.ControlButton.getPosition(self)[1]

    def setEnabled(self, e):
        xbmcgui.ControlButton.setEnabled(self, e)
        self.textControl.setEnabled(e)

    def setWidth(self, w):
        xbmcgui.ControlButton.setWidth(self, w)
        self.textControl.setWidth(old_div(w, 2))

    def setHeight(self, w):
        xbmcgui.ControlButton.setHeight(self, w)
        self.textControl.setHeight(w)

    def setPosition(self, x, y):
        xbmcgui.ControlButton.setPosition(self, x, y)
        if xbmcgui.__version__ == "1.2":
            self.textControl.setPosition(x + self.getWidth(), y)
        else:
            self.textControl.setPosition(x + old_div(self.getWidth(), 2), y)

    def setText(self, text):
        self.text = text
        if self.isPassword:
            self.textControl.setLabel("*" * len(self.text))
        else:
            self.textControl.setLabel(self.text)

    def getText(self):
        return self.text


if not hasattr(xbmcgui, "ControlEdit"):
    xbmcgui.ControlEdit = ControlEdit
