/*
 * 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;

import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasFocus;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.IndexedPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.queplix.core.client.common.StringUtil;

/**
 * QFormLayoutElement.
 *
 * @author Dmitry Antonov
 * @since 29 Dec 2006
 */
public abstract class QFormLayoutElement extends Composite {

    protected final Label captionLabel = new Label();
    protected Panel panel = new HorizontalPanel();
    protected final HorizontalPanel labelPanel = new HorizontalPanel();
    private int layout = QFormElement.HORIZONTAL;
    private int rowSpan;
    private int colSpan;

    /**
     * Control turn it's state to enabled for search
     * By calling this action QControl view shouldn't generate any of its events. If the component had been written
     * correctly controller and model should only listen notification from view.
     *
     * @param layout element layout
     */
    protected void initializeBaseUI(int layout) {
        this.layout = layout;
        if(isVerticalLayout()) {
            panel = new VerticalPanel();
        } else {
            HorizontalPanel hpanel = new HorizontalPanel();
            hpanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
            panel = hpanel;
        }
        captionLabel.setWordWrap(false);
        beforeInsertLabel();
        if(isVerticalLayout()) {
            labelPanel.add(captionLabel);
            addToPanel(labelPanel);
        } else {
            addToPanel(captionLabel);
        }
    }

    protected void beforeInsertLabel() {
    }

    public HasFocus getFirstFocusableField() {
        return getFirstFocusableField((IndexedPanel) panel);
    }

    /**
     * Recursively reach the first form field that implements HasFocus
     * @param panel to inspect for such a field
     * @return first focusable widget
     */
    private HasFocus getFirstFocusableField(IndexedPanel panel) {
        int elemCount = panel.getWidgetCount();
        for(int i = 0; i < elemCount; i++) {
            Widget w = panel.getWidget(i);
            if(w instanceof HasFocus) {
                return (HasFocus) w;
            } else if(w instanceof IndexedPanel) {
                return getFirstFocusableField((IndexedPanel) w);
            }
        }
        return null;
    }

    public final void addToPanel(Widget element) {
        panel.add(element);
    }

    public void addToLabelPanel(Widget element) {
        if(isVerticalLayout()) {
            labelPanel.add(element);
        } else {
            addToPanel(element);
        }
    }

    public final void initPanel() {
        initWidget(panel);
    }

    /**
     * Returns true if concrete element have to be aligned as horizontal-layered control.
     * It's contrary to the isVerticalLayout() by default.
     */
    public boolean alignableAsHorizontal() {
        return !isVerticalLayout();
    }

    /**
     * @return true if this element was created with Vertical layout
     */
    public final boolean isVerticalLayout() {
        return layout == QFormElement.VERTICAL;
    }

    protected void addCaptionClickListener(ClickListener listener) {
        captionLabel.addClickListener(listener);
    }

    public void setCaptionOffsetWidth(int captionOffsetWidth) {
        if(captionOffsetWidth > 0) {
            captionLabel.setWidth(StringUtil.pixelToSize(captionOffsetWidth));
        }
    }

    public int getCaptionOffsetWidth() {
        return captionLabel.getOffsetWidth();
    }

    /**
     * This method sets width of the editable field (e.g. text field width).
     * It doesn't affect for labels width etc. Furthermore it mustn't affect non-editable fields
     * (like calendars, checkboxes and other).
     *
     * @param clientWidth width of the edit field (usually in pixels). E.g. <src>setClientWidth("100px")</src>
     *                    for text edit sets size if the edit field to 100 pixels.
     */
    protected abstract void setClientWidth(String clientWidth);

    public void setClientWidth(int clientWidth) {
        setClientWidth(StringUtil.pixelToSize(clientWidth));
    }

    /**
     * Sets rowspan for controls. All components excluding TextArea ignore this
     * parameter by default.
     *
     * @param rowSpan - specified value of rowspan.
     */
    public void setRowSpan(int rowSpan) {
        this.rowSpan = rowSpan;
    }

    public void setColSpan(int colSpan) {
        this.colSpan = colSpan;
    }

    public int getColSpan() {
        return colSpan;
    }

    public int getRowSpan() {
        return rowSpan;
    }

    /**
     * Some controls shall not be colspanned - e.g. QButton. All other components
     * shall be. So base class contains default implementation.
     *
     * @return true if the control can be colspanned
     */
    public boolean allowColspanning() {
        return true;
    }

    public abstract int getClientWidth();

    /**
     * Returns width of a unoccupied controls part.
     *
     * @return width of a unoccupied controls part.
     */
    public abstract int getFilledWidth();

    /**
     * Returns if this element can be coupled as a part of couple (element such as memo button on the right of TextArea etc).
     *
     * @return either this element attached or not.
     */
    public boolean canBeCoupled(int coupleType) {
        return false;
    }

    public void coupleElement(int coupleType) {
    }

}
