/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */
package com.queplix.core.client.controls.form;


import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.EventPreview;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FocusListenerAdapter;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasFocus;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.KeyboardListenerCollection;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.queplix.core.client.app.Application;
import com.queplix.core.client.app.vo.BaseFieldMeta;
import com.queplix.core.client.app.vo.ContextMenuMeta;
import com.queplix.core.client.app.vo.FieldData;
import com.queplix.core.client.app.vo.FieldDataRequest;
import com.queplix.core.client.app.vo.FieldMeta;
import com.queplix.core.client.app.vo.FieldOnDemandData;
import com.queplix.core.client.app.vo.FormButtonMeta;
import com.queplix.core.client.app.vo.FormHtmlElementMeta;
import com.queplix.core.client.app.vo.FormLayoutMeta;
import com.queplix.core.client.app.vo.FormLinkMeta;
import com.queplix.core.client.app.vo.FormMeta;
import com.queplix.core.client.app.vo.MenuItemMeta;
import com.queplix.core.client.app.vo.chart.ChartMeta;
import com.queplix.core.client.app.vo.chart.ChartModel;
import com.queplix.core.client.app.vo.uisettings.FormUISettings;
import com.queplix.core.client.common.StringUtil;
import com.queplix.core.client.common.event.Event;
import com.queplix.core.client.common.event.EventListener;
import com.queplix.core.client.common.event.EventSource;
import com.queplix.core.client.common.ui.ButtonData;
import com.queplix.core.client.common.ui.CommonActionButtonSet;
import com.queplix.core.client.common.ui.DialogHelper;
import com.queplix.core.client.common.ui.Icon;
import com.queplix.core.client.common.ui.IconButton;
import com.queplix.core.client.common.ui.IconData;
import com.queplix.core.client.common.ui.MenuPopup;
import com.queplix.core.client.controls.DataCloneException;
import com.queplix.core.client.controls.FormControlListener;
import com.queplix.core.client.controls.QButton;
import com.queplix.core.client.controls.QElementsCoupler;
import com.queplix.core.client.controls.QFormComponentsFactory;
import com.queplix.core.client.controls.QFormElement;
import com.queplix.core.client.controls.QFormElementModel;
import com.queplix.core.client.controls.QFormElementModelImpl;
import com.queplix.core.client.controls.QFormElementView;
import com.queplix.core.client.controls.QFormHtmlElement;
import com.queplix.core.client.controls.QFormLayoutElement;
import com.queplix.core.client.controls.chart.QChart;
import com.queplix.core.client.controls.chart.QChartFactory;
import com.queplix.core.client.frames.mainframe.FormState;
import com.queplix.core.client.frames.mainframe.ViewPropertiesProvider;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Implementation of the view for the QueWeb QForm.
 *
 * @author Sergey Kozmin
 * @since 21 Sep 2006
 */
class QFormViewImpl extends QFormView
        implements ClickListener, QFormDataListener, FormControlListener {

    private static final String WHOLE_FORM_STYLE = QForm.FORM_STYLE_NAME_PREFIX
            + "wholeForm";

    public static final String SEARCH_BUTTON_CAPTION = "Search";
    public static final String CLEAR_BUTTON_CLEAR_CAPTION = "Clear";
    public static final String CLEAR_BUTTON_CANCEL_CAPTION = "Cancel";
    public static final String NEW_BUTTON_CAPTION = "New";
    public static final String CHANGE_BUTTON_CAPTION = "Change";
    public static final String UPDATE_BUTTON_CAPTION = "Save";

    public static final Character SEARCH_BUTTON_KEYCODE = new Character('c');
    public static final Character CLEAR_BUTTON_CLEAR_KEYCODE = new Character(
            'r');
    public static final Character CLEAR_BUTTON_CANCEL_KEYCODE = new Character(
            'l');
    public static final Character NEW_BUTTON_KEYCODE = new Character('n');
    public static final Character CHANGE_BUTTON_KEYCODE = new Character('g');
    public static final Character UPDATE_BUTTON_KEYCODE = new Character('s');

    private static final int DEFAULT_MODIFIER_KEY
            = KeyboardListener.MODIFIER_ALT;
    private static final String FORM_KEYCODE_PREFIX = "Alt + ";

    private static final IconData ICON_STATE_SEARCH = new IconData(
            "form/state_search.gif", "Form is in Search state");
    private static final IconData ICON_STATE_SELECTED = new IconData(
            "form/state_selected.gif", "Form is in Selected state");
    private static final IconData ICON_STATE_NEW = new IconData(
            "form/state_new.gif", "Form is in New state");
    private static final IconData ICON_STATE_EDIT = new IconData(
            "form/state_edit.gif", "Form is in Edit state");
    private static final IconData ICON_REPORT_DESIGN = new IconData(
            "form/state_report_design.gif", "Form is in Report Design state");

    private static final ButtonData MENU_BUTTON = new ButtonData(null, null,
            "form_actionButton", null, "form_actionButton_hi", true);
    private static final ButtonData MINIMIZE_BUTTON_MIN_STATE = new ButtonData(
            null, "Minimize form", "form_minButton", null, "form_minButton_hi",
            true);
    private static final ButtonData MINIMIZE_BUTTON_MAX_STATE = new ButtonData(
            null, "Maximize form", "form_maxButton", null, "form_maxButton_hi",
            true);

    private QFormModelImpl model;

    private Map controls = new HashMap();
    private Map customButtons = new HashMap();
    private Map keyCodes = new HashMap();
    private Set presentButtons = new HashSet();

    private int formLayout;
    private int gridWidth;
    private int gridHeight;
    private int lastXPosition;
    private int lastYPosition;

    private String actualHeight;

    private Grid mainPanel;
    private FocusPanel container;

    private boolean textAreaHasFocus;
    private boolean menuPopupShowRequest = false;

    private FormUISettings uiSettings;
    
    private boolean hasAdditionalActions;
    private boolean minimizable;
    private ViewPropertiesProvider viewPropertiesProvider;

    private InternalFlexTable elementsGrid;
    private DockPanel titleBar;
    private DockPanel elementsPanel;
    private VerticalPanel elementsAndSizeKeeper;
    private SimplePanel sizeKeeper;
    private boolean minimized;

    private Label rightSide;
    private Label leftSide;
    private Label bottomSide;

    private CommonActionButtonSet commonActionPanel;

    private QButton searchButton;
    private QButton changeOrUpdateButton;
    private QButton clearButton;
    private QButton newButton;

    private EventPreview keyboardEventPreview;
    private FormControlListener formControlListener;

    private MenuPopup contextMenu;

    private Icon stateIcon;
    private IconButton minimizeButton;
    private IconButton menuButton;
    private int labelsOrientation;
    private FormLayoutMeta layoutMeta;
    private final List buttonsMeta = new ArrayList();
    private FormLinkMeta[] links;
    private final Map htmlElements = new HashMap();
    private final List chartModels = new ArrayList();

    private boolean selected;
    private boolean isAlreadyLoaded = false;
    
    private boolean loadMinimized;
    private boolean cancelBubbling = false;

    public QFormViewImpl(QFormModelImpl model,
                         int formLayout, int width, int height,
                         boolean minimizable, boolean hasAdditionalActions) {
        initParams(model);

        this.minimizable = minimizable;
        this.hasAdditionalActions = hasAdditionalActions;

        this.formLayout = formLayout;
        this.gridWidth = width;
        this.gridHeight = height;

        doInit();
    }

    private void initParams(QFormModelImpl model) {
        this.model = model;
        FormMeta formMeta = model.getFormMeta();
        layoutMeta = formMeta.getLayoutMeta();
        FormButtonMeta[] buttonsMeta = formMeta.getButtons();
        for(int i = 0; i < buttonsMeta.length; i++) {
            this.buttonsMeta.add(buttonsMeta[i]);
        }
        FormHtmlElementMeta[] htmlElements = formMeta.getHtmlElements();
        for(int i = 0; i < htmlElements.length; i++) {
            this.htmlElements.put(htmlElements[i].getId(),
                    htmlElements[i].getHtml());
        }
        ChartModel[] chartsModels = formMeta.getCharts();
        if(chartsModels != null) {
            for(int i = 0; i < chartsModels.length; i++) {
                this.chartModels.add(chartsModels[i]);
            }
        }
        this.links = formMeta.getLinks();
        this.labelsOrientation = formMeta.getLabelsOrientation();
    }

    private void doInit() {
        model.addDataListener(this);
        initializeWrappingUI();
        if(hasAdditionalActions) {
            createMenu();
        }
        subscribeEvents();
        initKeyboardHandling();

        if(model.getFormMeta().getUISettings() != null && model.getFormMeta()
                .getUISettings().isMinimized()) {
            loadMinimized = true;
            setCaption(model.getFormTitle());
            processMinimizeEvent(true);
        } else {
            initFormLayoutUI();
        }
    }
    
    private void initializeWrappingUI() {
        container = new FocusPanel();
        mainPanel = new Grid(3, 3);

        elementsGrid = new InternalFlexTable();
        elementsGrid.setCellPadding(0);
        elementsGrid.setCellSpacing(0);

        titleBar = new DockPanel();

        stateIcon = new Icon(ICON_STATE_SEARCH);
        stateIcon.setStyleName("form_topIcon");

        titleBar.add(stateIcon, DockPanel.WEST);
        titleBar.add(captionLabel, DockPanel.WEST);
        captionLabel.setStyleName("form_topSide");
        captionLabel.setWordWrap(false);
        titleBar.setCellWidth(captionLabel, "100%");
        if(hasAdditionalActions){
            menuButton = new IconButton(MENU_BUTTON);
            titleBar.add(menuButton, DockPanel.EAST);
            titleBar.setCellWidth(menuButton, "1px");
        }
        if(minimizable){
            minimizeButton = new IconButton(MINIMIZE_BUTTON_MIN_STATE);
            titleBar.add(minimizeButton, DockPanel.EAST);
            titleBar.setCellWidth(minimizeButton, "1px");
        }

        elementsPanel = new DockPanel();

        commonActionPanel = new CommonActionButtonSet();
        commonActionPanel.getEventSource().addEventListener(el);
        sizeKeeper = new SimplePanel();
        sizeKeeper.setStyleName("form_sizeKeeper");
        sizeKeeper.setHeight("1px");

        elementsPanel.add(elementsGrid, DockPanel.CENTER);
        elementsPanel.add(commonActionPanel, DockPanel.EAST);

        elementsAndSizeKeeper = new VerticalPanel();
        elementsAndSizeKeeper.add(elementsPanel);
        elementsAndSizeKeeper.add(sizeKeeper);
        
        Label leftTop = new Label(" ");
        leftTop.setStyleName("form_leftTop");

        leftSide = new Label(" ");
        leftSide.setStyleName("form_leftSide");

        Label leftBottom = new Label(" ");
        leftBottom.setStyleName("form_leftBottom");

        Label topSide = new Label(" ");
        topSide.setStyleName("form_topSide");

        bottomSide = new Label(" ");
        bottomSide.setStyleName("form_bottomSide");

        Label rightTop = new Label(" ");
        rightTop.setStyleName("form_rightTop");

        rightSide = new Label(" ");
        rightSide.setStyleName("form_rightSide");

        Label rightBottom = new Label(" ");
        rightBottom.setStyleName("form_rightBottom");


        mainPanel.setWidget(0, 0, leftTop);
        mainPanel.setWidget(0, 1, titleBar);
        mainPanel.setWidget(0, 2, rightTop);
        mainPanel.setWidget(1, 0, leftSide);
        mainPanel.setWidget(1, 1, elementsAndSizeKeeper);
        mainPanel.setWidget(1, 2, rightSide);
        mainPanel.setWidget(2, 0, leftBottom);
        mainPanel.setWidget(2, 1, bottomSide);
        mainPanel.setWidget(2, 2, rightBottom);

        mainPanel.setCellPadding(0);
        mainPanel.setCellSpacing(0);

        if(minimizable){
            minimizeButton.addClickListener(this);
        }
        mainPanel.setStyleName(WHOLE_FORM_STYLE);

        container.setWidget(mainPanel);
        initWidget(container);
    }

    public boolean hasAdditionalActions() {
        return hasAdditionalActions;
    }
    
    private void createMenu() {
        MenuItemMeta[] menuItems;
        ContextMenuMeta menuMeta;
        contextMenu = new MenuPopup(menuButton);
        menuMeta = model.getContextMenuMeta();
        if(menuMeta != null) {
            menuItems = menuMeta.getMenuItems();
            if(menuItems != null) {
                for(int i = 0; i < menuItems.length; i++) {
                    Event event = new Event(menuItems[i].getMenuItemID());
                    ButtonData data = new ButtonData(menuItems[i].getCaption());
                    contextMenu.addButton(event, data);
                }
            }
        }
        // Doing this to eliminate the bug when context menus stick to the window
        contextMenu.setAbleToShow(false);
        contextMenu.getEventSource().addEventListener(new EventListener() {
            public void onEvent(Event event, Widget sender) {
                if(event == MenuPopup.Events.MENUPOPUP_SHOW_REQUEST) {
                    menuPopupShowRequest = true;
                }
            }
        });
    }

    public void onClick(Widget sender) {
        if(sender == minimizeButton) {
            minimized = !minimized;
            processMinimizeEvent(minimized);
            if(loadMinimized) {
                loadMinimized = false;
                initFormLayoutUI();
                isAlreadyLoaded = false;
                onAttach();
            }
            updateUISettings();
        }
    }

    private void processMinimizeEvent(boolean minimized) {
        this.minimized = minimized;
        minimizeButton.setButtonState(this.minimized ? MINIMIZE_BUTTON_MAX_STATE
                :MINIMIZE_BUTTON_MIN_STATE);
        if(Application.isInternetExplorer()) {
            leftSide.setHeight(this.minimized ? "1px":actualHeight);
            rightSide.setHeight(this.minimized ? "1px":actualHeight);
        }
        elementsPanel.setVisible(!this.minimized);
        if(getParent() != null) {
            getParent().setHeight("0px"); // collapse freed space in FireFox
        }
    }

    protected void addWidget(int xPosition, int yPosition, int width,
                             int height, QFormLayoutElement controlView) {
        addWidget(xPosition, yPosition, width, height, controlView,
                elementsGrid);
    }

    protected void addWidget(int xPosition, int yPosition, int width,
                             int height, QFormLayoutElement controlView,
                             FlexTable grid) {
        controlView.setColSpan(width);
        controlView.setRowSpan(height);
        grid.setWidget(yPosition, xPosition, controlView);
        grid.getFlexCellFormatter().setColSpan(yPosition, xPosition, width);
        grid.getFlexCellFormatter().setRowSpan(yPosition, xPosition, height);
    }

    public void onInit() {
        Set ids = controls.keySet();
        for(Iterator iterator = ids.iterator(); iterator.hasNext();) {
            ((QFormElement) controls.get(iterator.next())).getBaseController()
                    .onInit();
        }
    }

    public void setEnabledForSearch() {
        Set ids = controls.keySet();
        for(Iterator iterator = ids.iterator(); iterator.hasNext();) {
            ((QFormElement) controls.get(iterator.next())).getBaseView()
                    .setEnabledForSearch();
        }
    }

    public void setEnabledForEdit() {
        Set ids = controls.keySet();
        for(Iterator iterator = ids.iterator(); iterator.hasNext();) {
            ((QFormElement) controls.get(iterator.next())).getBaseView()
                    .setEnabledForEdit();
        }
    }

    public void setEnabledForNew() {
        Set ids = controls.keySet();
        for(Iterator iterator = ids.iterator(); iterator.hasNext();) {
            ((QFormElement) controls.get(iterator.next())).getBaseView()
                    .setEnabledForNew();
        }
    }

    public void setEnabledForReportDesign() {
        Set ids = controls.keySet();
        for(Iterator iterator = ids.iterator(); iterator.hasNext();) {
            ((QFormElement) controls.get(iterator.next())).getBaseView()
                    .setModeRepordDesign();
        }
    }

    public void disable() {
        Set ids = controls.keySet();
        for(Iterator iterator = ids.iterator(); iterator.hasNext();) {
            ((QFormElement) controls.get(iterator.next())).getBaseView()
                    .disable();
        }
    }

    protected void setCommonActionEnabled(Event[] commonFormsActions,
                                          boolean isEnabled) {
        for(int i = 0; i < commonFormsActions.length; i++) {
            setCommonActionEnabled(commonFormsActions[i], isEnabled);
        }
    }

    protected void setCommonActionEnabled(Event action, boolean isEnabled) {
        if(QFormController.Events.FORM_NEW_BUTTON_EVENT.equals(action)
                && newButton != null) {
            newButton.setEnabled(isEnabled);
        } else if((QFormController.Events.FORM_CHANGE_BUTTON_EVENT.equals(
                action) || QFormController.Events
                .FORM_UPDATE_BUTTON_EVENT
                .equals(action)) && changeOrUpdateButton != null) {
            changeOrUpdateButton.setEnabled(isEnabled);
        } else if(QFormController.Events.FORM_CLEAR_BUTTON_EVENT.equals(action)
                && clearButton != null) {
            clearButton.setEnabled(isEnabled);
        } else if(QFormController.Events.FORM_SEARCH_BUTTON_EVENT.equals(action)
                && searchButton != null) {
            searchButton.setEnabled(isEnabled);
        }
    }

    private final static boolean isASystemButton(String fieldId) {
        return fieldId.equalsIgnoreCase(QFormController.FORM_NEW_BUTTON) ||
                fieldId.equalsIgnoreCase(QFormController.FORM_SEARCH_BUTTON) ||
                fieldId.equalsIgnoreCase(QFormController.FORM_CHANGE_BUTTON) ||
                fieldId.equalsIgnoreCase(QFormController.FORM_UPDATE_BUTTON) ||
                fieldId.equalsIgnoreCase(
                        QFormController.FORM_CHANGE_OR_UPDATE_BUTTON) ||
                fieldId.equalsIgnoreCase(QFormController.FORM_CLEAR_BUTTON);
    }


    private QButton createButton(FormButtonMeta buttonMeta) {
        String buttonCaption = buttonMeta.getCaption();
        if(buttonCaption.length() != 0) {
            presentButtons.add(buttonCaption);
        }
        String buttonId = buttonMeta.getId();
        Event event;
        QButton result;
        boolean isWidthFixed = buttonCaption.length() < 8;
        if(buttonId.equals(QFormController.FORM_NEW_BUTTON)) {
            commonActionPanel.removeButton(newButton);
            if(buttonCaption.length() != 0) {
                newButton = new QButton(getUnderlined(buttonCaption),
                        getKeyCodeCaption(buttonCaption),
                        getFormLabelsOrientation(), isWidthFixed);
            } else {
                newButton = new QButton(getUnderlined(NEW_BUTTON_CAPTION),
                        getKeyCodeCaption(NEW_BUTTON_CAPTION),
                        getFormLabelsOrientation(), isWidthFixed);
                presentButtons.add(NEW_BUTTON_CAPTION);
            }
            event = QFormController.Events.FORM_NEW_BUTTON_EVENT;
            result = newButton;
        } else if((buttonId.equals(QFormController.FORM_CHANGE_BUTTON)) ||
                (buttonId.equals(QFormController.FORM_UPDATE_BUTTON)) ||
                (buttonId.equals(
                        QFormControllerImpl.FORM_CHANGE_OR_UPDATE_BUTTON))) {
            commonActionPanel.removeButton(changeOrUpdateButton);
            if(buttonCaption.length() != 0) {
                changeOrUpdateButton = new QButton(getUnderlined(buttonCaption),
                        getKeyCodeCaption(buttonCaption),
                        getFormLabelsOrientation(), isWidthFixed);
            } else {
                changeOrUpdateButton = new QButton(getUnderlined(
                        CHANGE_BUTTON_CAPTION), getKeyCodeCaption(
                        CHANGE_BUTTON_CAPTION), getFormLabelsOrientation(),
                        isWidthFixed);
                presentButtons.add(CHANGE_BUTTON_CAPTION);
            }
            event = QFormController.Events.FORM_CHANGE_BUTTON_EVENT;
            result = changeOrUpdateButton;
        } else if(buttonId.equals(QFormController.FORM_CLEAR_BUTTON)) {
            commonActionPanel.removeButton(clearButton);
            if(buttonCaption.length() != 0) {
                clearButton = new QButton(getUnderlined(buttonCaption),
                        getKeyCodeCaption(buttonCaption),
                        getFormLabelsOrientation(), isWidthFixed);
            } else {
                clearButton = new QButton(getUnderlined(
                        CLEAR_BUTTON_CLEAR_CAPTION), getKeyCodeCaption(
                        CLEAR_BUTTON_CLEAR_CAPTION), getFormLabelsOrientation(),
                        isWidthFixed);
                presentButtons.add(CLEAR_BUTTON_CLEAR_CAPTION);
            }
            event = QFormController.Events.FORM_CLEAR_BUTTON_EVENT;
            result = clearButton;
        } else if(buttonId.equals(QFormController.FORM_SEARCH_BUTTON)) {
            commonActionPanel.removeButton(searchButton);
            if(buttonCaption.length() != 0) {
                searchButton = new QButton(getUnderlined(buttonCaption),
                        getKeyCodeCaption(buttonCaption),
                        getFormLabelsOrientation(), isWidthFixed);
            } else {
                searchButton = new QButton(getUnderlined(SEARCH_BUTTON_CAPTION),
                        getKeyCodeCaption(SEARCH_BUTTON_CAPTION),
                        getFormLabelsOrientation(), isWidthFixed);
                presentButtons.add(SEARCH_BUTTON_CAPTION);
            }
            event = QFormController.Events.FORM_SEARCH_BUTTON_EVENT;
            result = searchButton;
        } else {
            result = new QButton(buttonCaption, getFormLabelsOrientation(),
                    buttonMeta.getButtonType(), buttonMeta.getIcon(),
                    buttonMeta.getCaptionStyle(), isWidthFixed);
            event = new Event(new QButton.CustomButtonEventData(buttonId));
            customButtons.put(buttonId, result);
        }
        commonActionPanel.addButton(event, result);
        return result;
    }

    private ButtonLayoutInfo findButtonInLayout(String buttonId) {
        for(int rowIdx = 0; rowIdx < getLayoutMeta().getRows().length; rowIdx++)
        {
            FormLayoutMeta.Row row = getLayoutMeta().getRows()[rowIdx];
            for(int colIdx = 0; colIdx < row.getColsCount(); colIdx++) {
                FormLayoutMeta.Col col = row.getCols()[colIdx];
                if(col.getFieldID().equals(buttonId)) {
                    return new ButtonLayoutInfo(rowIdx, colIdx,
                            col.getColspan(), col.getRowspan());
                }
            }
        }
        return null;
    }

    private void createButtonsMap(Map buttonsForPanel, Map buttonsForGrid) {
        if(getLayoutMeta() != null) {
            int row = 0;
            int col = 0;
            for(Iterator i = buttonsMeta.iterator(); i.hasNext();) {
                FormButtonMeta buttonMeta = (FormButtonMeta) i.next();
                QButton button = createButton(buttonMeta);
                ButtonLayoutInfo buttonLayout = findButtonInLayout(
                        buttonMeta.getId());
                if(buttonLayout != null) {
                    buttonsForGrid.put(button, buttonLayout);
                } else {
                    buttonsForPanel.put(button, new ButtonLayoutInfo(row,
                            col++));
                    if(col == 2) {
                        col = 0;
                        row++;
                    }
                }
            }
        } else {
            int row = 0;
            int col = 0;
            for(Iterator i = buttonsMeta.iterator(); i.hasNext();) {
                FormButtonMeta buttonMeta = (FormButtonMeta) i.next();
                QButton button = createButton(buttonMeta);
                buttonsForPanel.put(button, new ButtonLayoutInfo(row, col++));
                if(col == 2) {
                    col = 0;
                    row++;
                }
            }
        }
    }

    public void dataStructureChanged() {
        initFormLayoutUI();
    }

    private void initFormLayoutUI() {
        controls.clear();
        customButtons.clear();
        presentButtons.clear();
        setCaption(model.getFormTitle());
        Map buttonsForPanel = new HashMap();
        Map buttonsForGrid = new HashMap();
        createButtonsMap(buttonsForPanel, buttonsForGrid);
        if(getLayoutMeta() == null) {
            fillFormFromModel();
        } else {
            fillFormFromLayoutMeta();
        }
        setupButtons(buttonsForPanel, commonActionPanel.getInternalGrid());
        setupButtons(buttonsForGrid, elementsGrid);
        activateLinks();
    }

    private void setupButtons(Map buttons, FlexTable grid) {
        for(Iterator it = buttons.keySet().iterator(); it.hasNext();) {
            QButton button = (QButton) it.next();
            ButtonLayoutInfo layoutInfo = (ButtonLayoutInfo) buttons.get(
                    button);
            addWidget(layoutInfo.col, layoutInfo.row, layoutInfo.colspan,
                    layoutInfo.rowspan, button, grid);
        }
    }

    /**
     * Creates form using specified FormLayoutMeta instance.
     */
    private void fillFormFromLayoutMeta() {
        Set createdHiddenControls = new HashSet();
        for(int rowIdx = 0; rowIdx < getLayoutMeta().getRows().length; rowIdx++)
        {
            FormLayoutMeta.Row row = getLayoutMeta().getRows()[rowIdx];
            for(int colIdx = 0; colIdx < row.getColsCount(); colIdx++) {
                FormLayoutMeta.Col col = row.getCols()[colIdx];
                QFormLayoutElement control;
                if(col.getFieldID().equals("")) {
                    control = new QErrorLabel(getFormLabelsOrientation(), "");
                } else {
                    if(model.getElementMeta(col.getFieldID()) != null) {
                        //draw field if it exists
                        control = linkControlFromModel(col.getFieldID())
                                .getBaseView();
                    } else if(findButtonMeta(col.getFieldID())
                            != null) {//skip buttons
                        continue;
                    } else if(hasChartModel(col.getFieldID())) {
                        control = createChart(col.getFieldID());
                    } else if(htmlElements.containsKey(col.getFieldID())) {
                        control = new QFormHtmlElement(
                                (String) htmlElements.get(col.getFieldID()));
                    } else if(isASystemButton(col.getFieldID())) {
                        control = new QErrorLabel(getFormLabelsOrientation(),
                                "");
                    } else {
                        control = new QErrorLabel(getFormLabelsOrientation(),
                                "!!!UNKNOWN_ELEMENT <" + col.getFieldID()
                                        + ">!!!");
                    }
                }
                if(getLayoutMeta().isControlHidden(col.getFieldID())) {
                    //if control is hidden - add space into grid
                    control = new QErrorLabel(getFormLabelsOrientation(), "");
                    createdHiddenControls.add(col.getFieldID());
                }
                addWidget(colIdx, rowIdx, col.getColspan(), col.getRowspan(),
                        control);
            }
        }
        //and now creating all residuary hidden controls
        String[] hiddenControls = getLayoutMeta().getHiddenControls();
        for(int i = 0; i < hiddenControls.length; i++) {
            String hiddenControl = hiddenControls[i];
            if(!createdHiddenControls.contains(hiddenControl)) {
                linkControlFromModel(hiddenControl);
            }
        }
    }

    private void fillFormFromModel() {
        //add controls
        ArrayList ids = model.elementsKeys();
        for(int i = 0, n = ids.size(); i < n; i++) {
            String key = (String) ids.get(i);
            QFormElement control = linkControlFromModel(key);
            if(control.getBaseModel().isLinkable() &&
                    control.getBaseModel().getBaseMeta().getLinkedForm()
                            != null) {
                control.getBaseView().addLinkedFormStyle();
            }
            putControl(control, null);
        }
    }

    private FormButtonMeta findButtonMeta(String buttonId) {
        int index = buttonsMeta.indexOf(new FormButtonMeta(buttonId, ""));
        if(index == -1) {
            return null;
        } else {
            return (FormButtonMeta) buttonsMeta.get(index);
        }
    }

    private boolean hasChartModel(String chartId) {
        return getChartModel(chartId) != null;
    }

    private ChartModel getChartModel(String chartId) {
        for(Iterator it = chartModels.iterator(); it.hasNext();) {
            ChartModel model = (ChartModel) it.next();
            ChartMeta meta = model.getMeta();
            if(meta != null && chartId.equalsIgnoreCase(
                    meta.getID().toString())) {
                return model;
            }
        }
        return null;
    }

    private QFormLayoutElement createChart(String chartId) {
        ChartModel chartModel = getChartModel(chartId);
        if(chartModel == null) {
            return null;
        }

        QChart chart = QChartFactory.getInstance().createChart(chartModel);
        chart.getController().getEventSource().addEventListener(el);

        return (QFormLayoutElement) chart.getView().getWidget();
    }

    private QFormElement linkControlFromModel(String key) {
        FieldMeta elementMeta = model.getElementMeta(key);
        QFormElement control = QFormComponentsFactory.createWidgetForElement(
                elementMeta, this, getFormLabelsOrientation());
        controls.put(key, control);//need for fast searching
        control.getBaseController().getEventSource().addEventListener(el);
        return control;
    }

    public void needMoreData(FieldDataRequest request) {
        formControlListener.needMoreData(request);
    }

    public void controlDataChanged(String controlId) {
        formControlListener.controlDataChanged(controlId);
    }

    EventListener el = new EventListener() {
        public void onEvent(Event event, Widget sender) {
            if(event == QFormElementView.Events.TEXTAREA_FOCUSED) {
                textAreaHasFocus = true;
            } else if(event == QFormElementView.Events.TEXTAREA_FOCUS_LOST) {
                textAreaHasFocus = false;
            } else {
                formElementsEventSource.fireEvent(event);
            }
        }
    };

    public QFormElement getControl(Object key) {
        return (QFormElement) controls.get(key);
    }

    private void putControl(QFormElement control,
                            QFormPlaceConstraint elementConstraint) {
        addWidget(lastXPosition, lastYPosition, 1, 1, control.getBaseView());
        switch(formLayout) {
            case QFormLayout.HORIZONTAL_FLOW_LAYOUT: {
                lastXPosition++;
                if(lastXPosition >= gridWidth) {
                    lastXPosition = 0;
                    lastYPosition++;
                }
                if(lastYPosition >= gridHeight) {
                    lastYPosition = 0;
                }
                break;
            }
            case QFormLayout.VERTICAL_FLOW_LAYOUT: {
                lastYPosition++;
                if(lastYPosition >= gridHeight) {
                    lastYPosition = 0;
                    lastXPosition++;
                }
                if(lastXPosition >= gridWidth) {
                    lastXPosition = 0;
                }
                break;
            }
            case QFormLayout.XY_LAYOUT: {
                if(elementConstraint != null) {
                    if(elementConstraint.getConstraintForLayoutType()
                            == QFormLayout.XY_LAYOUT) {
                        lastXPosition = ((XYConstraint) elementConstraint)
                                .getX();
                        lastYPosition = ((XYConstraint) elementConstraint)
                                .getY();
                    }
                }
                break;
            }
            default: {//simply place elements by diagonal
                lastYPosition++;
                lastYPosition++;
                break;
            }
        }
    }

    public void dataChanged() {
        setCaption(model.getFormTitle());
        ArrayList keys = model.elementsKeys();
        for(int i = 0; i < keys.size(); i++) {
            String key = (String) keys.get(i);
            dataChanged(key);
        }
    }

    public void dataChanged(String elementId) {
        if(controls.containsKey(elementId)) {
            QFormElement control = getElement(elementId);
            FieldData fieldData = model.getElementData(elementId);
            if(fieldData != null) {
                control.getBaseModel().setBaseData(fieldData);
            }
        }
    }

    public void dataOnDemandCome(FieldOnDemandData data) {
        if(data != null) {
            QFormElement qFormElement = getControl(data.getFieldID());
            if(qFormElement != null) {
                qFormElement.getBaseModel().setFieldOnDemandData(data);
            }
        }
    }

    public void setDataRequirementsListener(FormControlListener listener) {
        formControlListener = listener;
    }

    public void disableCommonChangeButton() {
        setCommonActionEnabled(QFormController.Events.FORM_CHANGE_BUTTON_EVENT,
                false);
    }

    public void allowModificationsForSearch() {
        setEnabledForSearch();
    }

    public void allowModificationsForEdit() {
        setEnabledForEdit();
    }

    public void allowModificationsForReportDesign() {
        setEnabledForReportDesign();
    }

    public void denyModifications() {
        disable();
    }

    public void disableCommonSearchButton() {
        setCommonActionEnabled(QFormController.Events.FORM_SEARCH_BUTTON_EVENT,
                false);
    }

    public void disableCommonClearButton() {
        setCommonActionEnabled(QFormController.Events.FORM_CLEAR_BUTTON_EVENT,
                false);
    }

    public void disableCommonNewButton() {
        setCommonActionEnabled(QFormController.Events.FORM_NEW_BUTTON_EVENT,
                false);
    }

    public void enableAllCommonButtons() {
        setCommonActionEnabled(QForm.DEFAULT_BUTTON_EVENTS, true);
    }

    public void populateForm() {
        dataChanged();
    }

    public void showChangeButton() {
        if(changeOrUpdateButton != null) {
            commonActionPanel.removeButton(changeOrUpdateButton);
            commonActionPanel.addButton(
                    QFormController.Events.FORM_CHANGE_BUTTON_EVENT,
                    changeOrUpdateButton);
            changeOrUpdateButton.setCaption(getUnderlined(
                    CHANGE_BUTTON_CAPTION));
            changeOrUpdateButton.setTooltip(getKeyCodeCaption(
                    CHANGE_BUTTON_CAPTION));

            presentButtons.remove(UPDATE_BUTTON_CAPTION);
            presentButtons.add(CHANGE_BUTTON_CAPTION);
        }
    }

    public void showUpdateButton() {
        if(changeOrUpdateButton != null) {
            commonActionPanel.removeButton(changeOrUpdateButton);
            commonActionPanel.addButton(
                    QFormController.Events.FORM_UPDATE_BUTTON_EVENT,
                    changeOrUpdateButton);
            changeOrUpdateButton.setCaption(getUnderlined(
                    UPDATE_BUTTON_CAPTION));
            changeOrUpdateButton.setTooltip(getKeyCodeCaption(
                    UPDATE_BUTTON_CAPTION));

            presentButtons.remove(CHANGE_BUTTON_CAPTION);
            presentButtons.add(UPDATE_BUTTON_CAPTION);
        }
    }

    private boolean shallColspannedControlUseHeaders(QFormLayoutElement control,
                                                     int colIdx,
                                                     int[] colsWidth) {
        int result = 0;
        for(int i = colIdx;
            i < (colIdx + control.getColSpan()) && i < colsWidth.length; i++) {
            result += colsWidth[i];
        }
        return result == 0 && getLayoutMeta().containHeaders()
                && control.getClientWidth() > 0;
    }

    private Set enlargeSpannedControlsUseHeaders(List[] rows, int[] colsWidth) {
        Set controlsForEnlarging = new HashSet();
        //enlarge all spanned controls
        //enlarge controls which are placed in columns without any single input controls
        for(int rowIdx = 0; rowIdx < rows.length; rowIdx++) {
            List row = rows[rowIdx];
            for(int colIdx = 0; colIdx < row.size(); colIdx++) {
                QFormLayoutElement control = (QFormLayoutElement) row.get(
                        colIdx);
                if(control != null && control.getColSpan() > 1) {
                    //let's check - if this control is placed in columns with no-client-width
                    //elements - set its size according headers, otherwise - use default span
                    if(shallColspannedControlUseHeaders(control, colIdx,
                            colsWidth)) {
                        int clientWidth = 0;
                        for(int i = colIdx; i < colIdx + control.getColSpan();
                            i++) {
                            FormLayoutMeta.ColumnHeader header = getLayoutMeta()
                                    .getHeader(i);
                            if(header != null) {
                                clientWidth += header.getClientWidth();
                            }
                        }
                        if(clientWidth > 0) {
                            control.setClientWidth(clientWidth);
                            control.setWidth(StringUtil.pixelToSize(
                                    clientWidth + control.getFilledWidth()));
                        }
                    } else {
                        controlsForEnlarging.add(control);
                    }
                }
            }
        }
        return controlsForEnlarging;
    }

    /**
     * Traverses all spanned controls which are placed in columns with client-side controls
     * and sets their width to required size.
     * Attention: if colspanned control is placed in columns with no-client-resizing
     * controls (e.g. buttons, checkboxes etc) - it shall be resized according headers.
     *
     * @param controlsForEnlarging set of {@link com.queplix.core.client.controls.QFormLayoutElement}
     */
    private void enlargeTheRestOfSpannedControls(Set controlsForEnlarging) {
        //enlarge the rest of spanned controls
        for(Iterator it = controlsForEnlarging.iterator(); it.hasNext();) {
            QFormLayoutElement control = (QFormLayoutElement) it.next();
            if(control.allowColspanning()) {
                control.setWidth("100%");
                control.setClientWidth(control.getOffsetWidth()
                        - control.getFilledWidth() - 1);
            }
        }
    }

    /**
     * Creates and fills the matrix which contain real positions of all controls
     * in the form. Positions are calculated according colspans.
     *
     * @return matrix of real control positions.
     */
    private List[] calculateRealControlMatrix() {
        //create matrix of controls real position (using colspans)
        List[] rows = new ArrayList[elementsGrid.getRowCount()];
        for(int i = 0; i < rows.length; i++) {
            rows[i] = new ArrayList();
        }
        //fill matrix using colspans
        for(int i = 0, n = elementsGrid.getRowCount(); i < n; i++) {
            QFormLayoutElement prevElement = null;
            for(int j = 0, m = elementsGrid.getCellCount(i); j < m; j++) {
                QFormLayoutElement c = (QFormLayoutElement) elementsGrid
                        .getWidget(i, j);
                if(j > 0 && prevElement != null) {
                    QElementsCoupler.getInstance().coupleElements(prevElement,
                            c);
                }
                prevElement = c;
                rows[i].add(c);
                //if element has a colspan - add nulls into matrix row
                if(c.getColSpan() > 1) {
                    int colspan = c.getColSpan();
                    while(colspan-- > 1) {
                        rows[i].add(null);
                    }
                }
            }
        }
        //and correct matrix using rowspans
        for(int i = 0; i < rows.length; i++) {
            List row = rows[i];
            int colIdx = 0;
            for(Iterator it = row.iterator(); it.hasNext();) {
                QFormLayoutElement control = (QFormLayoutElement) it.next();
                if(control != null && control.getRowSpan() > 1) {
                    for(int rowIdx = i + 1;
                        rowIdx < rows.length && rowIdx < i + control
                                .getRowSpan(); rowIdx++) {
                        List bottomRow = rows[rowIdx];
                        if(colIdx < bottomRow.size()) {
                            int colspanCounter = control.getColSpan();
                            while(colspanCounter-- > 0) {
                                bottomRow.add(colIdx, null);
                            }
                        }
                    }
                }
                colIdx++;
            }
        }
        return rows;
    }

    /**
     * Calculates max caption with for each column and sets this max width for
     * all captions in processed column.
     *
     * @param rows array of a controls description.
     *             It looks like:<br/>
     *             <table border="1">
     *             <tr>
     *             <td>[controlA]</td>
     *             <td>[null]</td>
     *             <td>[controlB]</td>
     *             </tr>
     *             <tr>
     *             <td>[controlC]</td>
     *             <td>[controlD]</td>
     *             <td>[null]</td>
     *             <td>[controlE]</td>
     *             </tr>
     *             </table>
     */
    private void setCaptionWidthForAllControls(final List[] rows) {
        int width = getFormWidth(rows);
        //calculate and set labels width
        for(int colIdx = 0; colIdx < width; colIdx++) {
            //getting maximum caption width in the current column
            int maxCaptionWidth = 0;
            for(int rowIdx = 0; rowIdx < rows.length; rowIdx++) {
                if(rows[rowIdx].size() > colIdx) {
                    QFormLayoutElement element
                            = (QFormLayoutElement) rows[rowIdx].get(colIdx);
                    if(element != null && element.alignableAsHorizontal() &&
                            element.getCaptionOffsetWidth() > maxCaptionWidth) {
                        maxCaptionWidth = element.getCaptionOffsetWidth();
                    }
                }
            }
            //and assign it to all captions
            for(int rowIdx = 0; rowIdx < rows.length; rowIdx++) {
                if(rows[rowIdx].size() > colIdx) {
                    QFormLayoutElement element
                            = (QFormLayoutElement) rows[rowIdx].get(colIdx);
                    if(element != null && element.alignableAsHorizontal()) {
                        element.setCaptionOffsetWidth(maxCaptionWidth);
                    }
                }
            }
        }
    }

    /**
     * Returns width of the form. Actually it is a size of the widgest row in
     * the form layout.
     *
     * @param rows Matrix of controls built according colspans/rowspans.
     * @return form width.
     */
    private int getFormWidth(final List[] rows) {
        //calculate max cols count
        int width = 0;
        for(int i = 0; i < rows.length; i++) {
            if(width < rows[i].size()) {
                width = rows[i].size();
            }
        }
        return width;
    }

    /**
     * Sets client widths for all controls accroding headers.
     *
     * @param rows array of a controls description.
     * @return width of the columns excepting colspanned elements.
     */
    private int[] setClientWidth(List[] rows) {
        int[] colsWidth = new int[getFormWidth(rows)];
        if(getLayoutMeta() != null && getLayoutMeta().containHeaders()) {
            for(int rowIdx = 0; rowIdx < rows.length; rowIdx++) {
                List row = rows[rowIdx];
                for(int colIdx = 0; colIdx < row.size(); colIdx++) {
                    QFormLayoutElement control = (QFormLayoutElement) row.get(
                            colIdx);
                    if(control != null && control.getColSpan() < 2) {
                        FormLayoutMeta.ColumnHeader header = getLayoutMeta()
                                .getHeader(colIdx);
                        if(header != null) {
                            int clientWidth = header.getClientWidth();
                            if(clientWidth != FormLayoutMeta
                                    .CLIENT_WIDTH_NOT_SPECIFIED) {
                                control.setClientWidth(clientWidth);
                            }
                        }
                        if(control.getClientWidth() > colsWidth[colIdx]) {
                            colsWidth[colIdx] = control.getClientWidth();
                        }
                    }
                }
            }
        }
        return colsWidth;
    }

    private void activateLinks() {
        if(links == null) {
            return;
        }
        for(int i = 0; i < links.length; i++) {
            FormLinkMeta link = links[i];
            QFormElement element = getControl(link.getFieldId());
            if(element != null) {
                element.getBaseView().addLinkedFormStyle();
            }
        }
    }

    public void onAttach() {
        super.onAttach();
        if(!isAlreadyLoaded) {
            List[] rows = calculateRealControlMatrix();
            int[] colsWidth = setClientWidth(rows);
            Set controlsForEnlarging = enlargeSpannedControlsUseHeaders(rows,
                    colsWidth);
            setCaptionWidthForAllControls(rows);
            enlargeTheRestOfSpannedControls(controlsForEnlarging);
            isAlreadyLoaded = true;
        }

        if(Application.isInternetExplorer()) {
            actualHeight = StringUtil.pixelToSize(
                    elementsAndSizeKeeper.getOffsetHeight());
            leftSide.setHeight(actualHeight);
            rightSide.setHeight(actualHeight);
        }
        sizeKeeper.setWidth(StringUtil.pixelToSize(
                elementsPanel.getOffsetWidth()));
    }

    public void setSelected(boolean selected) {
        this.selected = selected;

        mainPanel.removeStyleName(
                selected ? "form_wholeForm":"form_wholeFormSelected");
        mainPanel.addStyleName(
                selected ? "form_wholeFormSelected":"form_wholeForm");

        container.setFocus(this.selected);
        if(this.selected) {
            setFocusOnFirstAvailableField();
        }

        if(selected) {
            DOM.addEventPreview(keyboardEventPreview);
        } else {
            DOM.removeEventPreview(keyboardEventPreview);
        }
        // we can show a context menu only on selected form
        // in other case, a bug occurs: the pop-ups stick to the window
        if(menuPopupShowRequest && isSelected()) {
            contextMenu.show();
            menuPopupShowRequest = false;
        }
        if(hasAdditionalActions){
            contextMenu.setAbleToShow(selected);
        }
    }

    private void setFocusOnFirstAvailableField() {
        boolean foundField = false;
        for(int i = 0, n = elementsGrid.getRowCount(); i < n; i++) {
            for(int j = 0, m = elementsGrid.getCellCount(i); j < m; j++) {
                final QFormLayoutElement c = (QFormLayoutElement) elementsGrid
                        .getWidget(i, j);
                final HasFocus w = c.getFirstFocusableField();
                if(w != null && !minimized && model.getFormMeta().isVisible()) {
                    DeferredCommand.add(new Command() {
                        public void execute() {
                            try {
                                w.setFocus(true);
                            } 
                            catch(Exception e) {
                            }
                        }
                    });
                    foundField = true;
                    break;
                }
            }
            if(foundField) {
                break;
            }
        }
    }

    private void subscribeEvents() {
        container.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                if(!selected) {
                    QFormController.Events.FORM_SELECTION_REQUESTED_EVENT
                            .setData(model.getIndex());
                    formElementsEventSource.fireEvent(
                            QFormController.Events.FORM_SELECTION_REQUESTED_EVENT);
                }
            }
        });

        // focus can be obtained in different ways
        container.addFocusListener(new FocusListenerAdapter() {
            public void onFocus(Widget sender) {
                if(!selected) {
                    QFormController.Events.FORM_SELECTION_REQUESTED_EVENT
                            .setData(model.getIndex());
                    formElementsEventSource.fireEvent(
                            QFormController.Events.FORM_SELECTION_REQUESTED_EVENT);
                }
            }
        });
    }

    private void setClearButtonCaption(String caption) {
        if(clearButton != null) {
            clearButton.setCaption(getUnderlined(caption));
            clearButton.setTooltip(getKeyCodeCaption(caption));

            presentButtons.remove(CLEAR_BUTTON_CANCEL_CAPTION);
            presentButtons.remove(CLEAR_BUTTON_CLEAR_CAPTION);
            presentButtons.add(caption);
        }
    }

    /**
     * This method apply state icon according form state.
     *
     * @param formState form state
     */
    void setStateIcon(int formState) {
        switch(formState) {
            case FormState.EDIT_STATE:
                stateIcon.setIconState(ICON_STATE_EDIT);
                setClearButtonCaption(CLEAR_BUTTON_CANCEL_CAPTION);
                break;
            case FormState.NEW_STATE:
                stateIcon.setIconState(ICON_STATE_NEW);
                setClearButtonCaption(CLEAR_BUTTON_CANCEL_CAPTION);
                break;
            case FormState.SEARCH_STATE:
                stateIcon.setIconState(ICON_STATE_SEARCH);
                setClearButtonCaption(CLEAR_BUTTON_CLEAR_CAPTION);
                break;
            case FormState.SELECTED_STATE:
                stateIcon.setIconState(ICON_STATE_SELECTED);
                setClearButtonCaption(CLEAR_BUTTON_CLEAR_CAPTION);
                break;
            case FormState.REPORT_DESIGN_STATE:
                stateIcon.setIconState(ICON_REPORT_DESIGN);
                setClearButtonCaption(CLEAR_BUTTON_CLEAR_CAPTION);
                break;
        }
    }


    /**
     * Returns a tooltip string
     *
     * @param buttonCaption
     * @return a string like "Modifier + KeyCode"
     */
    private String getKeyCodeCaption(String buttonCaption) {
        if(keyCodes.containsKey(buttonCaption)) {
            return FORM_KEYCODE_PREFIX + ((Character) keyCodes.get(
                    buttonCaption)).toString().toUpperCase();
        }
        return " ";
    }

    /**
     * If the keyCode is contained within a string,
     * it gets underlined in it
     *
     * @param buttonCaption caption to process
     * @return HTML code
     */
    private String getUnderlined(String buttonCaption) {
        if(keyCodes.containsKey(buttonCaption)) {
            Character c = (Character) keyCodes.get(buttonCaption);
            String csUp = c.toString().toUpperCase();
            String cs = c.toString();
            int indexUp = buttonCaption.indexOf(csUp);
            int index = buttonCaption.indexOf(cs);
            if(indexUp != -1) {
                return buttonCaption.substring(0, indexUp)
                        + "<u>" + csUp + "</u>"
                        + buttonCaption.substring(indexUp + 1,
                        buttonCaption.length());
            } else if(index != -1) {
                return buttonCaption.substring(0, index)
                        + "<u>" + cs + "</u>"
                        + buttonCaption.substring(index + 1,
                        buttonCaption.length());
            }
        }
        return buttonCaption;
    }

    /**
     * Initialize keyboard event handler
     */
    private void initKeyboardHandling() {
        keyCodes.put(SEARCH_BUTTON_CAPTION, SEARCH_BUTTON_KEYCODE);
        keyCodes.put(CLEAR_BUTTON_CLEAR_CAPTION, CLEAR_BUTTON_CLEAR_KEYCODE);
        keyCodes.put(CLEAR_BUTTON_CANCEL_CAPTION, CLEAR_BUTTON_CANCEL_KEYCODE);
        keyCodes.put(NEW_BUTTON_CAPTION, NEW_BUTTON_KEYCODE);
        keyCodes.put(CHANGE_BUTTON_CAPTION, CHANGE_BUTTON_KEYCODE);
        keyCodes.put(UPDATE_BUTTON_CAPTION, UPDATE_BUTTON_KEYCODE);

        keyboardEventPreview = new EventPreview() {
            public boolean onEventPreview(
                    final com.google.gwt.user.client.Event e) {
                switch(DOM.eventGetType(e)) {
                    case com.google.gwt.user.client.Event.ONKEYUP: {
                        char keyCode = (char) DOM.eventGetKeyCode(e);
                        int modifiers = KeyboardListenerCollection
                                .getKeyboardModifiers(e);
                        if(processKeyEvents(keyCode, modifiers)) {
                            DOM.eventPreventDefault(e);
                            return false;
                        }
                        break;
                    }
                    case com.google.gwt.user.client.Event.ONKEYDOWN: {
                        char keyCode = (char) DOM.eventGetKeyCode(e);
                        int modifiers = KeyboardListenerCollection
                                .getKeyboardModifiers(e);
                        return processKeyDown(keyCode, modifiers);
                    }
                }
                return true;
            }
        };
    }

    private boolean processKeyDown(char keyCode, int modifiers) {
        boolean res = true;
        Character kc = new Character(Character.toLowerCase(keyCode));
        if(keyCode == KeyboardListener.KEY_ENTER) {
            int formState = model.getFormState();
            if(formState == FormState.SEARCH_STATE) {
                if(textAreaHasFocus) {
                    return true;
                }
                formElementsEventSource.fireEvent(
                        QFormController.Events.FORM_SEARCH_BUTTON_EVENT);
            } else if(formState == FormState.SELECTED_STATE) {
                formElementsEventSource.fireEvent(
                        QFormController.Events.FORM_SEARCH_BUTTON_EVENT);
            } else if(formState == FormState.NEW_STATE || formState == FormState
                    .EDIT_STATE) {
                if(textAreaHasFocus) {
                    return true;
                }
                formElementsEventSource.fireEvent(
                        QFormController.Events.FORM_UPDATE_BUTTON_EVENT);
            }
        } else if(modifiers == DEFAULT_MODIFIER_KEY) {
            if(kc.equals((Character) keyCodes.get(SEARCH_BUTTON_CAPTION)) ||
                    kc.equals((Character) keyCodes.get(
                            CLEAR_BUTTON_CLEAR_CAPTION)) ||
                    kc.equals((Character) keyCodes.get(
                            CLEAR_BUTTON_CANCEL_CAPTION)) ||
                    kc.equals((Character) keyCodes.get(
                            CLEAR_BUTTON_CANCEL_CAPTION)) ||
                    kc.equals((Character) keyCodes.get(NEW_BUTTON_CAPTION)) ||
                    kc.equals((Character) keyCodes.get(CHANGE_BUTTON_CAPTION))
                    ||
                    kc.equals((Character) keyCodes.get(
                            UPDATE_BUTTON_CAPTION))) {
                return false;
            } else {
                res = true;
            }
        }
        return res;
    }

    private boolean isButtonEnabled(String buttonCaption) {
        if(buttonCaption.equals(SEARCH_BUTTON_CAPTION) && (searchButton != null)
                && searchButton.isEnabled() && presentButtons.contains(
                buttonCaption)) {
            return true;
        } else if(((buttonCaption.equals(UPDATE_BUTTON_CAPTION)
                && presentButtons.contains(UPDATE_BUTTON_CAPTION))
                || ((buttonCaption.equals(CHANGE_BUTTON_CAPTION))
                && presentButtons.contains(CHANGE_BUTTON_CAPTION)))
                && (changeOrUpdateButton != null) && changeOrUpdateButton
                .isEnabled()) {
            return true;
        } else if(((buttonCaption.equals(CLEAR_BUTTON_CLEAR_CAPTION)
                && presentButtons.contains(CLEAR_BUTTON_CLEAR_CAPTION))
                || ((buttonCaption.equals(CLEAR_BUTTON_CANCEL_CAPTION))
                && presentButtons.contains(CLEAR_BUTTON_CANCEL_CAPTION)))
                && (clearButton != null) && clearButton.isEnabled()) {
            return true;
        } else if(buttonCaption.equals(NEW_BUTTON_CAPTION)
                && (newButton != null) && newButton.isEnabled()
                && presentButtons.contains(buttonCaption)) {
            return true;
        }
        return false;
    }

    /**
     * Process keyboard events
     */
    private boolean processKeyEvents(char keyCode, int modifiers) {
        boolean res = true;
        Character kc = new Character(Character.toLowerCase(keyCode));
        if((modifiers == DEFAULT_MODIFIER_KEY) && (kc.equals(
                (Character) keyCodes.get(SEARCH_BUTTON_CAPTION)))
                && (isButtonEnabled(SEARCH_BUTTON_CAPTION))) {
            formElementsEventSource.fireEvent(
                    QFormController.Events.FORM_SEARCH_BUTTON_EVENT);
        } else if((modifiers == DEFAULT_MODIFIER_KEY) && (((kc.equals(
                (Character) keyCodes.get(CLEAR_BUTTON_CLEAR_CAPTION)))
                && (isButtonEnabled(CLEAR_BUTTON_CLEAR_CAPTION)))
                || ((kc.equals((Character) keyCodes.get(
                CLEAR_BUTTON_CANCEL_CAPTION)))
                && (isButtonEnabled(CLEAR_BUTTON_CANCEL_CAPTION))))) {
            formElementsEventSource.fireEvent(
                    QFormController.Events.FORM_CLEAR_BUTTON_EVENT);
        } else if((modifiers == DEFAULT_MODIFIER_KEY) && (kc.equals(
                (Character) keyCodes.get(CLEAR_BUTTON_CANCEL_CAPTION)))
                && (isButtonEnabled(CLEAR_BUTTON_CANCEL_CAPTION))) {
            formElementsEventSource.fireEvent(
                    QFormController.Events.FORM_CLEAR_BUTTON_EVENT);
        } else if((modifiers == DEFAULT_MODIFIER_KEY) && (kc.equals(
                (Character) keyCodes.get(NEW_BUTTON_CAPTION)))
                && (isButtonEnabled(NEW_BUTTON_CAPTION))) {
            formElementsEventSource.fireEvent(
                    QFormController.Events.FORM_NEW_BUTTON_EVENT);
        } else if((modifiers == DEFAULT_MODIFIER_KEY) && (kc.equals(
                (Character) keyCodes.get(CHANGE_BUTTON_CAPTION)))
                && (isButtonEnabled(CHANGE_BUTTON_CAPTION))) {
            formElementsEventSource.fireEvent(
                    QFormController.Events.FORM_CHANGE_BUTTON_EVENT);
        } else if((modifiers == DEFAULT_MODIFIER_KEY) && (kc.equals(
                (Character) keyCodes.get(UPDATE_BUTTON_CAPTION)))
                && (isButtonEnabled(UPDATE_BUTTON_CAPTION))) {
            formElementsEventSource.fireEvent(
                    QFormController.Events.FORM_UPDATE_BUTTON_EVENT);
        } else
        if(modifiers == (DEFAULT_MODIFIER_KEY + KeyboardListener.KEY_RIGHT)) {
            // Move to the next form
            DialogHelper.showModalMessageDialog("Next form");
        } else
        if(modifiers == (DEFAULT_MODIFIER_KEY + KeyboardListener.KEY_LEFT)) {
            // Move to the previous form
            DialogHelper.showModalMessageDialog("Previous form");
        } else {
            res = false;
        }
        return res;
    }

    public boolean isSelected() {
        return selected;
    }

    EventSource getContextMenuEventSource() {
        return contextMenu.getEventSource();
    }

    EventSource formElementsEventSource = new EventSource(this);

    EventSource getFormElementsEventSource() {
        return formElementsEventSource;
    }

    public void setAdhocControlsEnabled(boolean isEnabled) {
        int designState = isEnabled ? QFormElementModel.NOT_IN_REPORT
                :QFormElementModel.DISABLED_FOR_REPORT_DESIGN;
        Set ids = controls.keySet();
        for(Iterator iterator = ids.iterator(); iterator.hasNext();) {
            QFormElement formElement = ((QFormElement) controls.get(
                    iterator.next()));
            formElement.getBaseModel().setReportDesignState(designState);
        }
    }

    public void setAdhocControlState(String elementId, int state) {
        QFormElement qFormElement = getElement(elementId);
        if(qFormElement != null) {
            qFormElement.getBaseModel().setReportDesignState(state);
        }
    }

    public int getAdhocControlState(String elementId) {
        QFormElement qFormElement = getElement(elementId);
        return qFormElement != null ? qFormElement.getBaseModel()
                .getReportDesignState()
                :QFormElementModel.DISABLED_FOR_REPORT_DESIGN;
    }

    public boolean isAdhocControlInReport(String elementId) {
        QFormElement qFormElement = getElement(elementId);
        return qFormElement != null
                && qFormElement.getBaseModel().getReportDesignState()
                == QFormElementModel.IN_REPORT;
    }

    private QFormElement getElement(String elementId) {
        return (QFormElement) controls.get(elementId);
    }

    public FormLayoutMeta getLayoutMeta() {
        return layoutMeta;
    }

    /**
     * Used for adding instead of unknown elements (which is declared in FormLayoutMeta but doesn't actually
     * exist in model).
     */
    //TODO redesigne this class in order to save height of an element
    private final static class QErrorLabel extends QFormElementView {
        private Label label;

        public int getClientWidth() {
            return getOffsetWidth();
        }

        protected void setClientWidth(String clientWidth) {
            setWidth(clientWidth);
        }

        public void setClientWidth(int clientWidth) {
            if(clientWidth > getOffsetWidth()) {
                super.setClientWidth(clientWidth);
            }
        }

        protected void setEnabled(boolean isEnabled) {
        }

        public void initializeBaseUI(int layout) {
            label = new Label();
            label.setWordWrap(false);
            addToPanel(label);
            initPanel();
        }

        /*public QErrorLabel(int layout) {
            this(layout, "");
        }*/

        public QErrorLabel(int layout, String contant) {
            super(new ElementModelAdapter(), layout);
            label.setText(contant);
        }
    }

    private static class ElementModelAdapter extends QFormElementModelImpl {
        public ElementModelAdapter() {
            setBaseMeta(new BaseFieldMeta(FieldMeta.CHECKBOX,
                    "unknown_element_id", ""));//just fake
        }

        public boolean isLinkable() {
            return false;
        }

        public boolean isValid() {
            return false;
        }

        public int getDataType() {
            return 0;
        }

        public void cloneDataFrom(QFormElementModel sameTypeControlModel)
                throws DataCloneException {
        }
    }

    private static class ButtonLayoutInfo {
        private final int row;
        private final int col;
        private final int colspan;
        private final int rowspan;

        public ButtonLayoutInfo(int row, int col, int colspan, int rowspan) {
            this.row = row;
            this.col = col;
            this.colspan = colspan;
            this.rowspan = rowspan;
        }

        public ButtonLayoutInfo(int row, int col) {
            this(row, col, 1, 1);
        }
    }

    //used for having access to protected method getBodyElement()
    private static class InternalFlexTable extends FlexTable {
        protected Element getBodyElement() {
            return super.getBodyElement();
        }
    }

    public int getFormLabelsOrientation() {
        return labelsOrientation;
    }

    void hideContextMenu() {
        if(contextMenu.isVisible()) {
            contextMenu.hide();
        }
    }

    void collectUISettings() {
        Set ids = controls.keySet();
        for(Iterator iterator = ids.iterator(); iterator.hasNext();) {
            ((QFormElement) controls.get(iterator.next())).getBaseController()
                    .collectUISettings();
        }
    }

    void setCustomButtonsEnabled(boolean enabled) {
        setCustomButtonsEnabled(null, enabled);
    }

    public void setCustomButtonsEnabled(String[] buttonsId, boolean enabled) {
        Set ids;
        if(buttonsId != null && buttonsId.length > 0) {
            ids = new HashSet();
            for(int i = 0; i < buttonsId.length; i++) {
                ids.add(buttonsId[i]);
            }
        } else {
            ids = customButtons.keySet();
        }
        for(Iterator iterator = ids.iterator(); iterator.hasNext();) {
            ((QButton) customButtons.get(iterator.next())).setEnabled(enabled);
        }
    }

    public FormUISettings getUISettings() {
        return uiSettings == null ? createUISettings():uiSettings;
    }

    private FormUISettings createUISettings() {
        uiSettings = new FormUISettings(minimized);
        return uiSettings;
    }

    private void updateUISettings() {
        if(uiSettings == null) {
            uiSettings = new FormUISettings(minimized);
        } else {
            uiSettings.setMinimized(minimized);
        }
    }

    public boolean isVisible() {
        return model.getFormMeta().isVisible();
    }

}
