def is_true(value):
    return value == 'true' or value == True class Control(Component):
    def __init__(self, dialog, id, type, parent_id, properties, element):
        super().__init__(dialog, id, type, parent_id, properties, element)
        self.parent_id = parent_id
        self.read_only = properties.get('ro', False)
        self.tab_order = int(element.get('tabindex', '0'))
        self.tool_tip_text = element.get('title')

    @property
    def parent(self):
        return self.dialog.components[self.parent_id]

    def is_really_enabled(self):
        return super().is_really_enabled() and self.parent.is_really_enabled()

    def _ais_setReadOnly(self, value):
        self.read_only = is_true(value)
    def _ais_setTabOrder(self, value):
        self.tab_order = int(value)
    def _ais_setToolTipText(self, value):
        self.tool_tip_text = value

    def _ais_setX(self, value): pass
    def _ais_setY(self, value): pass
    def _ais_setWidth(self, value): pass
    def _ais_setHeight(self, value): pass
    def _ais_setForeground(self, value): pass
    def _ais_setBackground(self, value): pass
    def _ais_setComponentStyle(self, value): pass
    def _ais_setFontSize(self, value): pass
    def _ais_setFontStyle(self, value): pass

    def changed_properties(self):
        return ''

    def _build_changed_properties(self, **kwargs):
        # TODO: This could be shorter.
        result = []
        write = result.append

        write("<changedProperties>")
        write("<objName>" + self.id + "</objName>")
        write("<propertyValues>")

        for name, value in kwargs.items():
            is_xml = use_cdata = False
            if isinstance(value, tuple): is_xml, use_cdata, value = value
            if isinstance(value, bool): value = 'true' if value else 'false'

            if use_cdata: value = "<![CDATA[" + value + "]]>"
            write("<nameValue>")
            write("<name>" + name + "</name>")
            if is_xml is not None:
                write("<isXml>" + str(bool(is_xml)).lower() + "</isXml>")
            write("<value>" + value + "</value>")
            write("</nameValue>")

        write("</propertyValues>")
        write("<embObjChProps isNull='true'/>")
        write("</changedProperties>")

        return ''.join(result)

    def update_value_interactives(self, type):
        pass # TODO class ComboBox(Control):
    def __init__(self, dialog, id, type, parent_id, properties, element):
    def _parse_options(self, element):
    def selected_option(self):
    def select(self, index):
    def _fire_event(self):
    def changed_properties(self):
    def _ais_setDataView(self, id, body): def cell_edited_event(source, command, alias, row_index):
    source.dialog.try_interactive(source, 'CellEditedEvent')
    return Event(source, EVENT_ACTION,
        "<event class='avc.ui.event.AVCCellEditedEvent'>" +
        _tag("command", command, True) +
        _tag("alias", alias) + _tag("rowIndex", str(row_index)) +
        "</event>") def row_edited_event(source, command, row_index):
    source.dialog.try_interactive(source, 'RowEditedEvent')
    return Event(source, EVENT_ACTION,
        "<event class='avc.ui.event.AVCRowEditedEvent'>" +
        _tag("command", command, True) + _tag("rowIndex", str(rowIndex)) +
        "</event>") def action_event(source, command=None, original_source_name=None,
                 params=None, cancel=False):
    source.dialog.try_interactive(source, 'ActionEvent')
    # Note that <origSrcName> goes outside of <event>.
    return Event(source, EVENT_CANCEL if cancel else EVENT_ACTION,
        _tag("origSrcName", original_source_name, True) +
        "<event class='avc.ui.event.AVCActionEvent'>" +
        _tag("command", command, True) + _tag("params", params, True) +
        "</event>") def selection_event(source, index=-1, selected=True):
    source.dialog.try_interactive(source, 'SelectionEvent')
    return Event(source, EVENT_SELCHANGE,
        "<event class='avc.ui.event.AVCSelectionActionEvent'>" +
        _tag("command", "SELECT" if selected else "UNSELECT") +
        _tag("index", str(index)) +
        "</event>") def cursor_event(source, method, command=None):
    source.dialog.try_interactive(source, 'CursorEvent')
    return Event(source, CursorEventMethods[method],
        "<event class='avc.ui.event.AVCCursorEvent'>" +
        _tag("command", command, True) +
        "</event>") def table_header_event(
        source, column_alias, after_column_alias, modifiers, type):
    source.dialog.try_interactive(source, 'TableHeaderEvent')
    return Event(source, EVENT_HEADER,
        "<event class='avc.ui.table.AVCTableHeaderEvent'>" +
        _tag("type", type) + _tag("columnAlias", columnAlias) +
        _tag("afterColumnAlias", afterColumnAlias, True) +
        _tag("modifiers", modifiers, True) +
        "</event>") class AISParseError(AisiklError):
    '''We couldn't parse or properly process the response from AIS.

    This isn't just for syntax errors, but also for unknown or unsupported
    attribute values, method names and such. The error can either mean:

    - AIS does something we doesn't support yet, or
    - AIS changes their formats and our parser needs to be updated, or
    - the AIS server returned an error with status 200 (should never happen).
    ''' class AISBehaviorError(AisiklError):
    '''AIS did something we didn't expect.'''
import json
from bs4 import BeautifulSoup
from collections import namedtuple
from .component import is_true
from .control import Control
from .combobox import Option
from aisikl.events import (cell_edited_event, row_edited_event, action_event,
                           selection_event, cursor_event, table_header_event)
from aisikl.exceptions import AISParseError, AISBehaviorError
        # TODO remove me but document it

    def _update_data_position_properties(self, data_view):
        if not data_view: return
        element = data_view.find(id='dataPositionSpan')
        if not element: return
        self.is_end_of_data = element.get('isend', 'false') == 'true'
        self.truncated = element.get('truncated', 'false') == 'true'
        self.fixed_columns = int(element.get('fixedcolumns', '0'))

    def _update_data_tab_bodies_properties(self, data_view):
        if not data_view: return
        element = data_view.find(id='dataTabBodies')
        if not element: return
        self.buffer_size = int(element.get('buffersize'))   # I hope this won't break anything

    def _update_selection_model_properties(self, data_view):
        if not data_view: return
        element = data_view.find(id='selectionModel')
        if not element: return

        # TODO: loaded_rows chcem aby isiel po ID-ckach, to jest loaded_rows[0] je row ktory ma id=row_0 a nie nutne najvyssi
        # ledaze by nie??? vyzera ze AIS v tom ma tiez taky bordel ako my
        # TODO: velky audit celeho webui kedy sa pouziva row_ resp substring(4) a kedy nie

        was_multiple_selection = len(self.selected_row_indexes) > 1

        selection = [int(id) for id in element.get('selection', '').split(',') if id]
        if self.cell_selection_mode:
            selection = selection[0:1] if selection else [0]
        self.selected_row_indexes = selection

        if self.cell_selection_mode:
            self.selected_column_alias = element.get(
                'cellselectionalias', self.selected_column_alias)
        else:
            self.selected_column_alias = None

        self.row_count = int(element.get('rowcount'))
        self.declared_row_count = int(element.get('declaredrowcount'))
        self.no_data_loaded = element.get('nodataloaded', 'false') == 'true'

        if selection:
            self.cursor_row_index = int(element.get(
                'activeindex', selection[0]))
        elif self.row_count or self.declared_row_count:
            self.cursor_row_index = 0
        else:
            self.cursor_row_index = None

        self.inverted_selection = (not self.cell_selection_mode and
            element.get('invertedselection', 'false') == 'true')

        is_multiple_selection = len(self.selected_row_indexes) > 1
        if was_multiple_selection != is_multiple_selection:
            self.dialog.try_interactive(self, "switchMultiple")

        self.selection_changed = True

    def _ais_setSortable(self, value):
        self.sortable = is_true(value)
        # TODO: this.tableService.updateStatusControlSortButton
    def _ais_setAllwaysSelected(self, value):
        self.always_selected = is_true(value)
    def _ais_setFixedColumns(self, value):
        self.fixed_columns = int(value)
    def _ais_setMinNoFixedColumnsWidth(self, value):
        pass   # getMinNoFixedColumnsWidth is unused in Votr
    def _ais_setUpDownEnabled(self, value):
        self.up_down_enabled = is_true(value)
    def _ais_setUserAddRemoveRowsEnabled(self, value):
        self.user_add_remove_rows_enabled = is_true(value)
    def _ais_setSupportedEvents(self, value):
        self.supported_events = value
    def _ais_setMultipleSelection(self, value):
        self.multiple_selection = is_true(value)
    def _ais_setCellSelectionMode(self, value):
        self.cell_selection_mode = is_true(value)
    def _ais_setScrollBottomEnabled(self, value):
        self.scroll_bottom_enabled = is_true(value)
    def _ais_setSelectAllEnabled(self, value):
        self.select_all_enabled = is_true(value)
    def _ais_setRowNumbersVisible(self, value):
        self.row_numbers_visible = is_true(value)
    def _ais_setStatusVisible(self, value):
        self.status_visible = is_true(value)
    def _ais_setVisibleStatusButtons(self, value):
        self.visible_status_buttons = int(value)
    def _ais_setBufferSize(self, value):
        self.buffer_size = int(value)

    def switch_row_numbers_visible(self):
        self.log('action', 'Switching row numbers visibility in {}'.format(self.id))
        self.row_numbers_visible = not self.row_numbers_visible
        self.row_numbers_visible_changed = True
        self.dialog.component_changes(self, True)

    def switch_status_visible(self):
        self.log('action', 'Switching status bar visibility in {}'.format(self.id))
        self.status_visible = not self.status_visible
        self.status_visible_changed = True
        self.dialog.component_changes(self, True)

    def on_column_move(self, column_alias, after_column_alias): # TODO inline into actual column move fn
        self.log('action', 'Moving {} to be after {} in {}'.format(
            column_alias, after_column_alias, self.id))
        ev = table_header_event(
            self, column_alias, after_column_alias, None, 'MOVE_COLUMN')
        self.dialog.app.send_events(ev)

    def _on_edit_row(self, index):
        if not self.edited_cells: return
        self.dialog.component_changes(self, False)
        if 'EDITED_ROW|' in self.supported_events:
            self.used_listeners_mask |= 64
            ev = row_edited_event(self, 'EDITED_ROW', changed_index)
            self.dialog.app.send_events(ev)

    def sort(self, column_alias, modifiers):
        # TODO: find out if this should be a public function

        ev = table_header_event(