/*******************************************************************************
 * Copyright (c) 2013, 2020 Dirk Fauth and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
 *******************************************************************************/
package org.eclipse.nebula.widgets.nattable.edit.editor;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum;
import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;

/**
 * A specialization of {@link TextCellEditor} that uses a multi line text editor
 * as editor control. To support multi line editing correctly, the behaviour to
 * commit on pressing the enter key is disabled.
 * <p>
 * A multi line editor usually needs some space. Therefore it might be a good
 * decision to set the configuration attribute
 * {@link EditConfigAttributes#OPEN_IN_DIALOG} to <code>true</code> for this
 * editor, so the editor always opens in a subdialog.
 * </p>
 * <p>
 * As some table layouts may support enough space for an inline cell editor,
 * this editor does not specify
 * {@link ICellEditor#openInline(org.eclipse.nebula.widgets.nattable.config.IConfigRegistry, java.util.List)}
 * to always return <code>false</code>.
 * </p>
 */
public class MultiLineTextCellEditor extends TextCellEditor {

    /**
     * Flag to configure whether the text control should enable automatic line
     * wrap behaviour or not. By default this editor will support automatic line
     * wrapping.
     */
    private boolean lineWrap = true;

    /**
     * Create a new multi line text editor that ensures to not commit the editor
     * value in case enter is typed. The text control will support automatic
     * line wrapping.
     */
    public MultiLineTextCellEditor() {
        this(true);
    }

    /**
     * Create a new multi line text editor that ensures to not commit the editor
     * value in case enter is typed.
     *
     * @param lineWrap
     *            Flag to configure whether the text control should enable
     *            automatic line wrap behaviour or not.
     */
    public MultiLineTextCellEditor(boolean lineWrap) {
        this.commitOnEnter = false;
        this.lineWrap = lineWrap;
    }

    @Override
    public Text createEditorControl(Composite parent) {
        boolean openInline = (this.editMode == EditModeEnum.INLINE);

        int style = HorizontalAlignmentEnum.getSWTStyle(this.cellStyle) | SWT.MULTI | SWT.BORDER;
        if (!openInline) {
            // if the editor control is opened in a dialog, we add scrolling as
            // the size of the control is dependent on the dialog size
            style = style | SWT.V_SCROLL;
        }
        if (this.lineWrap) {
            style = style | SWT.WRAP;
        } else if (!openInline) {
            // if the editor control is opened in a dialog, we add scrolling as
            // the size of the control is dependent on the dialog size
            style = style | SWT.H_SCROLL;
        }
        final Text textControl = super.createEditorControl(parent, style);

        if (!openInline) {
            // add the layout data directly so it will not be layouted by the
            // CellEditDialog
            GridDataFactory.fillDefaults().grab(true, true).hint(100, 50).applyTo(textControl);
        }

        // On inline editing there need to be a different handling of the return
        // key as the Text control is performing a new line on return, it is not
        // possible to commit a value by pressing enter. So for inline editing
        // we catch enter to perform the commit, while pressing ALT + ENTER will
        // add a new line. With setting commitWithCtrlKey to true the commit
        // will only be performed if CTRL + ENTER is pressed.
        if (openInline) {
            this.commitOnEnter = true;
            textControl.addKeyListener(new KeyAdapter() {

                @Override
                public void keyReleased(KeyEvent event) {
                    if (event.keyCode == SWT.CR
                            || event.keyCode == SWT.KEYPAD_CR) {
                        if (event.stateMask == SWT.MOD3) {
                            textControl.insert(textControl.getLineDelimiter());
                        }
                    }
                }
            });
        }

        return textControl;
    }

    @Override
    public Rectangle calculateControlBounds(final Rectangle cellBounds) {
        int widthHintForCompute = this.lineWrap ? cellBounds.width : SWT.DEFAULT;
        Point size = getEditorControl().computeSize(widthHintForCompute, SWT.DEFAULT);

        int diff = 0;
        if (this.lineWrap) {
            // Because of computeTrim internally the computed width is bigger
            // than the given width. We therefore need to calculate twice by
            // removing the trim diff to get the correct size.
            diff = size.x - cellBounds.width;
            size = getEditorControl().computeSize(widthHintForCompute - diff, SWT.DEFAULT);
        }

        final int widthHint = widthHintForCompute - diff;

        // add a listener that increases/decreases the size of the control if
        // the text is modified as the calculateControlBounds method is only
        // called in case of inline editing, this listener shouldn't hurt
        // anybody else
        getEditorControl().addModifyListener(e -> {
            Point p = getEditorControl().computeSize(widthHint, SWT.DEFAULT, true);
            Point loc = getEditorControl().getLocation();
            getEditorControl().setBounds(
                    loc.x,
                    loc.y,
                    MultiLineTextCellEditor.this.lineWrap ? cellBounds.width : Math.max(p.x, cellBounds.width),
                    Math.max(p.y, cellBounds.height));
        });

        return new Rectangle(
                cellBounds.x,
                cellBounds.y,
                this.lineWrap ? cellBounds.width : Math.max(size.x, cellBounds.width),
                Math.max(size.y, cellBounds.height));
    }

    /**
     * @param lineWrap
     *            <code>true</code> if the text control should enable automatic
     *            line wrap behaviour, <code>false</code> if not
     */
    public void setLineWrap(boolean lineWrap) {
        this.lineWrap = lineWrap;
    }
}
