com.extjs.gxt.ui.client.widget.grid.RowEditor.java Source code

Java tutorial

Introduction

Here is the source code for com.extjs.gxt.ui.client.widget.grid.RowEditor.java

Source

/*
 * Sencha GXT 2.3.1 - Sencha for GWT
 * Copyright(c) 2007-2013, Sencha, Inc.
 * licensing@sencha.com
 * 
 * http://www.sencha.com/products/gxt/license/
 */
package com.extjs.gxt.ui.client.widget.grid;

import java.util.Map;

import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.Style;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.core.FastMap;
import com.extjs.gxt.ui.client.core.XDOM;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.ColumnModelEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.GridEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.RowEditorEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.store.Record;
import com.extjs.gxt.ui.client.util.KeyNav;
import com.extjs.gxt.ui.client.util.Margins;
import com.extjs.gxt.ui.client.util.Point;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.ComponentHelper;
import com.extjs.gxt.ui.client.widget.ComponentManager;
import com.extjs.gxt.ui.client.widget.ComponentPlugin;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.Html;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.form.ComboBox;
import com.extjs.gxt.ui.client.widget.form.Field;
import com.extjs.gxt.ui.client.widget.form.LabelField;
import com.extjs.gxt.ui.client.widget.form.TriggerField;
import com.extjs.gxt.ui.client.widget.grid.EditorGrid.ClicksToEdit;
import com.extjs.gxt.ui.client.widget.layout.HBoxLayout;
import com.extjs.gxt.ui.client.widget.layout.HBoxLayoutData;
import com.extjs.gxt.ui.client.widget.layout.MarginData;
import com.extjs.gxt.ui.client.widget.layout.TableLayout;
import com.extjs.gxt.ui.client.widget.tips.ToolTip;
import com.extjs.gxt.ui.client.widget.tips.ToolTipConfig;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;

/**
 * This RowEditor should be used as a plugin to {@link Grid}. It displays an
 * editor for all cells in a row.
 * 
 * <dl>
 * <dt><b>Events:</b></dt>
 * 
 * <dd><b>BeforeEdit</b> : RowEditorEvent(rowEditor, rowIndex)<br>
 * <div>Fires before row editing is triggered. Listeners can cancel the action
 * by calling {@link BaseEvent#setCancelled(boolean)}.</div>
 * <ul>
 * <li>rowEditor : this</li>
 * <li>rowIndex : the row index of the row about to be edited</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>ValidateEdit</b> : RowEditorEvent(rowEditor, rowIndex, changes)<br>
 * <div>Fires right before the model is updated. Listeners can cancel the action
 * by calling {@link BaseEvent#setCancelled(boolean)}.</div>
 * <ul>
 * <li>rowEditor : this</li>
 * <li>rowIndex : the row index of the row about to be edited</li>
 * <li>changes : a map of property name and new values</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>AfterEdit</b> : RowEditorEvent(rowEditor, rowIndex, changes)<br>
 * <div>Fires after a row has been edited.</div>
 * <ul>
 * <li>rowEditor : this</li>
 * <li>rowIndex : the row index of the row that was edited</li>
 * <li>changes : a map of property name and new values</li>
 * </ul>
 * </dd>
 * </dl>
 * 
 * @param <M> the model type
 */
@SuppressWarnings("deprecation")
public class RowEditor<M extends ModelData> extends ContentPanel implements ComponentPlugin {
    public class RowEditorMessages {

        private String cancelText = GXT.MESSAGES.rowEditor_cancelText();
        private String dirtyText = GXT.MESSAGES.rowEditor_dirtyText();
        private String errorTipTitleText = GXT.MESSAGES.rowEditor_tipTitleText();
        private String saveText = GXT.MESSAGES.rowEditor_saveText();

        /**
         * Returns the buttons cancel text.
         * 
         * @return the text
         */
        public String getCancelText() {
            return cancelText;
        }

        /**
         * Returns the tool tip dirty text.
         * 
         * @return the dirtyText
         */
        public String getDirtyText() {
            return dirtyText;
        }

        /**
         * Returns the error tool tip title.
         * 
         * @return the errorTipTitleText
         */
        public String getErrorTipTitleText() {
            return errorTipTitleText;
        }

        /**
         * Returns the buttons save text.
         * 
         * @return the text
         */
        public String getSaveText() {
            return saveText;
        }

        /**
         * Sets the buttons cancel text
         * 
         * @param cancelText the cancel text
         */
        public void setCancelText(String cancelText) {
            this.cancelText = cancelText;
        }

        /**
         * Sets the tool tip dirty text.
         * 
         * @param dirtyText the dirtyText to set
         */
        public void setDirtyText(String dirtyText) {
            this.dirtyText = dirtyText;
        }

        /**
         * Sets the error tool tip title.
         * 
         * @param errorTipTitleText the errorTipTitleText to set
         */
        public void setErrorTipTitleText(String errorTipTitleText) {
            this.errorTipTitleText = errorTipTitleText;
        }

        /**
         * Sets the buttons save text
         * 
         * @param saveText the save text
         */
        public void setSaveText(String saveText) {
            this.saveText = saveText;
        }

    }

    protected ContentPanel btns;
    protected Grid<M> grid;
    protected RowEditorMessages messages;
    protected boolean renderButtons = true;
    protected int rowIndex;

    protected Button saveBtn, cancelBtn;
    private boolean bound;
    private int buttonPad = 3;
    private ClicksToEdit clicksToEdit = ClicksToEdit.ONE;
    private boolean editing;
    private boolean errorSummary = true;
    private int frameWidth = 5;
    private boolean initialized;
    private boolean lastValid;
    private Listener<GridEvent<M>> listener;
    private int monitorPoll = 200;
    private Timer monitorTimer;
    private boolean monitorValid = true;
    private Record record;
    private ToolTip tooltip;
    protected Html toolTipAlignWidget;

    public RowEditor() {
        super();
        setFooter(true);
        setLayout(new HBoxLayout());
        addStyleName("x-small-editor");
        baseStyle = "x-row-editor";
        messages = new RowEditorMessages();
    }

    /**
     * Returns the clicks to edit.
     * 
     * @return the clicks to edit
     */
    public ClicksToEdit getClicksToEdit() {
        return clicksToEdit;
    }

    /**
     * Returns the roweditors's messages.
     * 
     * @return the messages
     */
    public RowEditorMessages getMessages() {
        return messages;
    }

    /**
     * Returns the interval in ms in that the roweditor is validated
     * 
     * @return the interval in ms in that the roweditor is validated
     */
    public int getMonitorPoll() {
        return monitorPoll;
    }

    @SuppressWarnings("unchecked")
    public void init(Component component) {
        grid = (Grid<M>) component;
        grid.disableTextSelection(false);

        listener = new Listener<GridEvent<M>>() {

            public void handleEvent(GridEvent<M> be) {
                if (be.getType() == Events.RowDoubleClick) {
                    onRowDblClick(be);
                } else if (be.getType() == Events.RowClick) {
                    onRowClick(be);
                } else if (be.getType() == Events.OnKeyDown) {
                    onGridKey(be);
                } else if (be.getType() == Events.ColumnResize || be.getType() == Events.Resize) {
                    verifyLayout(false);
                } else if (be.getType() == Events.BodyScroll) {
                    positionButtons();
                } else if (be.getType() == Events.Detach) {
                    stopEditing(false);
                } else if (be.getType() == Events.Reconfigure && initialized) {
                    stopEditing(false);
                    removeAll();
                    initialized = false;
                }

            }

        };

        grid.addListener(Events.RowDoubleClick, listener);
        grid.addListener(Events.Resize, listener);
        grid.addListener(Events.RowClick, listener);
        grid.addListener(Events.OnKeyDown, listener);
        grid.addListener(Events.ColumnResize, listener);
        grid.addListener(Events.BodyScroll, listener);
        grid.addListener(Events.Detach, listener);
        grid.addListener(Events.Reconfigure, listener);

        grid.getColumnModel().addListener(Events.HiddenChange, new Listener<ColumnModelEvent>() {
            public void handleEvent(ColumnModelEvent be) {
                verifyLayout(false);
            }
        });
        grid.getColumnModel().addListener(Events.ColumnMove, new Listener<ColumnModelEvent>() {
            public void handleEvent(ColumnModelEvent be) {
                if (initialized) {
                    stopEditing(false);
                    removeAll();
                    initialized = false;
                }
            }
        });
        grid.getView().addListener(Events.Refresh, new Listener<BaseEvent>() {
            public void handleEvent(BaseEvent be) {
                stopEditing(false);
            }
        });
    }

    /**
     * Returns true of the RowEditor is active and editing.
     * 
     * @return true if the RowEditor is active
     */
    public boolean isEditing() {
        return editing;
    }

    /**
     * Returns true if a tooltip with an error summary is shown.
     * 
     * @return true if a tooltip with an error summary is shown
     */
    public boolean isErrorSummary() {
        return errorSummary;
    }

    /**
     * Returns true if this roweditor is monitored.
     * 
     * @return true if the roweditor is monitored
     */
    public boolean isMonitorValid() {
        return monitorValid;
    }

    @Override
    public void onComponentEvent(ComponentEvent ce) {
        super.onComponentEvent(ce);
        if (ce.getEventTypeInt() == KeyNav.getKeyEvent().getEventCode()) {
            if (ce.getKeyCode() == KeyCodes.KEY_ENTER) {
                onEnter(ce);
            } else if (ce.getKeyCode() == KeyCodes.KEY_ESCAPE) {
                onEscape(ce);
            } else if (ce.getKeyCode() == KeyCodes.KEY_TAB) {
                onTab(ce);
            }
        }
    }

    /**
     * Sets the number of clicks to edit (defaults to ONE).
     * 
     * @param clicksToEdit the clicks to edit
     */
    public void setClicksToEdit(ClicksToEdit clicksToEdit) {
        this.clicksToEdit = clicksToEdit;
    }

    /**
     * True to show a tooltip with an error summary (defaults to true)
     * 
     * @param errorSummary true to show an error summary.
     */
    public void setErrorSummary(boolean errorSummary) {
        this.errorSummary = errorSummary;
    }

    /**
     * Sets the roweditors's messages.
     * 
     * @param messages the messages
     */
    public void setMessages(RowEditorMessages messages) {
        this.messages = messages;
    }

    /**
     * Sets the polling interval in ms in that the roweditor validation is done
     * (defaults to 200)
     * 
     * @param monitorPoll the polling interval in ms in that validation is done
     */
    public void setMonitorPoll(int monitorPoll) {
        this.monitorPoll = monitorPoll;
    }

    /**
     * True to monitor the valid status of this roweditor (defaults to true)
     * 
     * @param monitorValid true to monitor this roweditor
     */
    public void setMonitorValid(boolean monitorValid) {
        this.monitorValid = monitorValid;
    }

    /**
     * Start editing of a specific row.
     * 
     * @param rowIndex the index of the row to edit.
     * @param doFocus true to focus the field
     */
    @SuppressWarnings("unchecked")
    public void startEditing(int rowIndex, boolean doFocus) {
        if (disabled) {
            return;
        }

        if (editing && isDirty()) {
            showTooltip(SafeHtmlUtils.fromTrustedString(getMessages().getDirtyText()));
            return;
        }

        if (editing && this.rowIndex != rowIndex) {
            stopEditing(true);
        }

        hideTooltip();
        M model = (M) grid.getStore().getAt(rowIndex);
        Record r = getRecord(model);
        RowEditorEvent ree = new RowEditorEvent(this, rowIndex);
        ree.setRecord(r);

        Element row = (Element) grid.getView().getRow(rowIndex);

        if (row == null || model == null || !fireEvent(Events.BeforeEdit, ree)) {
            return;
        }

        editing = true;
        record = r;

        this.rowIndex = rowIndex;

        if (!isRendered()) {
            render((Element) grid.getView().getEditorParent());
        }
        ComponentHelper.doAttach(this);

        if (!initialized) {
            initFields();
        }
        ColumnModel cm = grid.getColumnModel();

        for (int i = 0, len = cm.getColumnCount(); i < len; i++) {
            Field<Object> f = (Field<Object>) getItem(i);
            if (GXT.isAriaEnabled()) {
                if (i == 0 && saveBtn != null) {
                    saveBtn.getFocusSupport().setNextId(f.getId());
                }
                f.getAriaSupport().setLabel(cm.getColumnHeader(i).asString());
            }
            String dIndex = cm.getDataIndex(i);
            CellEditor ed = cm.getEditor(i);
            Object val = ed != null ? ed.preProcessValue(record.get(dIndex)) : record.get(dIndex);

            f.updateOriginalValue(val);
            f.setValue(val);
        }
        if (cancelBtn != null) {
            cancelBtn.getFocusSupport().setPreviousId(getItem(getItemCount() - 1).getId());
        }
        if (!isVisible()) {
            show();
        }

        el().setXY(getPosition(row));
        verifyLayout(true);
        if (doFocus) {
            deferFocus(null);
        }
        lastValid = false;

        el().scrollIntoView((Element) grid.getView().getEditorParent(), false,
                new int[] { renderButtons ? btns.getHeight() : 0, 0 });
    }

    /**
     * Stops editing.
     * 
     * @param saveChanges true to save the changes. false to ignore them.
     */
    public void stopEditing(boolean saveChanges) {
        if (disabled || !editing) {
            return;
        }
        editing = false;

        Map<String, Object> data = new FastMap<Object>();
        boolean hasChange = false;
        ColumnModel cm = grid.getColumnModel();
        for (int i = 0, len = cm.getColumnCount(); i < len; i++) {
            if (!cm.isHidden(i)) {
                Component c = getItem(i);
                if (c instanceof LabelField) {
                    continue;
                } else if (c instanceof Field<?>) {
                    Field<?> f = (Field<?>) c;

                    // store may be filtered from previous edit
                    if (f instanceof ComboBox<?>) {
                        ((ComboBox<?>) f).getStore().clearFilters();
                    }

                    String dindex = cm.getDataIndex(i);
                    Object oldValue = record.get(dindex);

                    CellEditor ed = cm.getEditor(i);
                    Object value = ed != null ? ed.postProcessValue(f.getValue()) : f.getValue();
                    if ((oldValue == null && value != null) || (oldValue != null && !oldValue.equals(value))) {
                        data.put(dindex, value);
                        hasChange = true;
                    }

                    // clear current value
                    if (f instanceof ComboBox<?>) {
                        ((ComboBox<?>) f).clear();
                    }
                }
            }
        }
        RowEditorEvent ree = new RowEditorEvent(this, rowIndex);
        ree.setRecord(record);
        ree.setChanges(data);

        if (!saveChanges || !isValid()) {
            fireEvent(Events.CancelEdit, ree);
        } else if (hasChange && fireEvent(Events.ValidateEdit, ree)) {
            record.beginEdit();
            for (String k : data.keySet()) {
                record.set(k, data.get(k));
            }
            record.endEdit();
            fireEvent(Events.AfterEdit, ree);
        }
        hide();
    }

    protected void afterRender() {
        super.afterRender();
        positionButtons();

        if (monitorValid) {
            startMonitoring();
        }
        if (renderButtons) {
            btns.setWidth((getMinButtonWidth() * 2) + (frameWidth * 2) + (buttonPad * 4));
        }
    }

    protected void bindHandler() {
        boolean valid = isValid();
        if (!valid) {
            lastValid = false;
            if (errorSummary) {
                showTooltip(getErrorText());
            }
        } else if (valid && !lastValid) {
            hideTooltip();
            lastValid = true;
        }

        if (saveBtn != null) {
            saveBtn.setEnabled(valid);
        }

        if (!isVisible() && tooltip != null && tooltip.isEnabled()) {
            hideTooltip();
        }
    }

    protected void createButtons() {
        btns = new ContentPanel() {
            protected void createStyles(String baseStyle) {
                baseStyle = "x-plain";
                headerStyle = baseStyle + "-header";
                headerTextStyle = baseStyle + "-header-text";
                bwrapStyle = baseStyle + "-bwrap";
                tbarStyle = baseStyle + "-tbar";
                bodStyle = baseStyle + "-body";
                bbarStyle = baseStyle + "-bbar";
                footerStyle = baseStyle + "-footer";
                collapseStyle = baseStyle + "-collapsed";
            }
        };

        btns.setHeaderVisible(false);
        btns.addStyleName("x-btns");
        btns.setLayout(new TableLayout(2));

        cancelBtn = new Button(getMessages().getCancelText(), new SelectionListener<ButtonEvent>() {
            @Override
            public void componentSelected(ButtonEvent ce) {
                stopEditing(false);
            }
        });
        cancelBtn.setMinWidth(getMinButtonWidth());
        btns.add(cancelBtn);

        saveBtn = new Button(getMessages().getSaveText(), new SelectionListener<ButtonEvent>() {
            @Override
            public void componentSelected(ButtonEvent ce) {
                stopEditing(true);
            }
        });
        saveBtn.setMinWidth(getMinButtonWidth());
        btns.add(saveBtn);

        cancelBtn.getFocusSupport().setNextId(saveBtn.getId());
        saveBtn.getFocusSupport().setPreviousId(cancelBtn.getId());

        btns.render(getElement("bwrap"));
        btns.layout();

        btns.getElement().removeAttribute("tabindex");
        btns.getFocusSupport().setIgnore(true);
    }

    protected void deferFocus(final int colIndex) {
        DeferredCommand.addCommand(new Command() {
            public void execute() {
                doFocus(colIndex);
            }
        });
    }

    protected void deferFocus(final Point pt) {
        DeferredCommand.addCommand(new Command() {
            public void execute() {
                doFocus(pt);
            }
        });
    }

    @Override
    protected void doAttachChildren() {
        super.doAttachChildren();
        ComponentHelper.doAttach(btns);
        ComponentHelper.doAttach(toolTipAlignWidget);
    }

    @Override
    protected void doDetachChildren() {
        super.doDetachChildren();
        ComponentHelper.doDetach(btns);
        ComponentHelper.doDetach(toolTipAlignWidget);
    }

    protected void doFocus(Point pt) {
        if (isVisible()) {
            int index = 0;
            if (pt != null) {
                index = getTargetColumnIndex(pt);
            }
            if (index >= 0) {
                doFocus(index);
            }
        }
    }

    protected void doFocus(int colIndex) {
        if (isVisible()) {
            ColumnModel cm = this.grid.getColumnModel();
            for (int i = colIndex, len = cm.getColumnCount(); i < len && i >= 0; i++) {
                ColumnConfig c = cm.getColumn(i);
                if (!c.isHidden() && c.getEditor() != null) {
                    c.getEditor().getField().focus();
                    break;
                }
            }
        }
    }

    protected void ensureVisible(CellEditor editor) {
        if (isVisible()) {
            grid.getView().ensureVisible(this.rowIndex, indexOf(editor), true);
        }
    }

    protected Component findField(Element elem) {
        El e = El.fly(elem).findParent(".x-row-editor-field", 3);
        if (e != null) {
            return ComponentManager.get().get(e.getId());
        }
        return null;
    }

    protected SafeHtml getErrorText() {
        StringBuffer sb = new StringBuffer();
        sb.append("<ul>");
        for (int i = 0; i < getItemCount(); i++) {

            Field<?> f = (Field<?>) getItem(i);
            if (!f.isValid(true)) {
                sb.append("<li><b>");
                sb.append(grid.getColumnModel().getColumn(i).getHeaderHtml().asString());
                sb.append("</b>: ");
                sb.append(SafeHtmlUtils.htmlEscape(f.getErrorMessage()));
                sb.append("</li>");
            }
        }
        sb.append("</ul>");
        return SafeHtmlUtils.fromTrustedString(sb.toString());
    }

    protected Point getPosition(Element row) {
        return El.fly(row).getXY();
    }

    protected Record getRecord(M model) {
        return grid.getStore().getRecord(model);
    }

    protected int getTargetColumnIndex(Point pt) {
        for (int i = 0, j = 0; i < grid.getColumnModel().getColumnCount(); i++) {
            ColumnConfig c = grid.getColumnModel().getColumn(i);
            if (!c.isHidden()) {
                if (El.fly(grid.getView().getHeaderCell(i)).getRegion().right >= pt.x) {
                    return j;
                }
                j++;
            }
        }
        return -1;
    }

    protected void hideTooltip() {
        if (tooltip != null) {
            tooltip.hide();
            tooltip.disable();
        }
    }

    protected void initFields() {
        ColumnModel cm = grid.getColumnModel();
        for (int i = 0, len = cm.getColumnCount(); i < len; i++) {
            ColumnConfig c = cm.getColumn(i);
            CellEditor ed = c.getEditor();

            Field<?> f = ed != null ? ed.getField() : new LabelField();
            if (f instanceof TriggerField<?>) {
                ((TriggerField<? extends Object>) f).setMonitorTab(true);
            }
            f.setWidth(cm.getColumnWidth(i));
            HBoxLayoutData ld = new HBoxLayoutData();
            if (i == 0) {
                ld.setMargins(new Margins(0, 1, 2, 1));
            } else if (i == len - 1) {
                ld.setMargins(new Margins(0, 0, 2, 1));
            } else {
                ld.setMargins(new Margins(0, 1, 2, 2));
            }

            f.setMessageTarget("tooltip");
            f.addStyleName("x-row-editor-field");
            // needed because we remove it from the celleditor
            clearParent(f);
            insert(f, i, ld);
        }
        initialized = true;
    }

    @SuppressWarnings("unchecked")
    protected boolean isDirty() {
        for (Component f : getItems()) {
            if (((Field<Object>) f).isDirty()) {
                return true;
            }
        }
        return false;
    }

    protected boolean isValid() {
        boolean valid = true;
        for (Component c : getItems()) {
            Field<?> f = (Field<?>) c;
            if (!f.isValid(true)) {
                return false;
            }
        }
        return valid;
    }

    protected void onEnter(ComponentEvent ce) {
        stopEditing(true);
    }

    protected void onEscape(ComponentEvent ce) {
        stopEditing(false);
    }

    protected void onGridKey(GridEvent<M> e) {
        int kc = e.getKeyCode();
        if ((kc == KeyCodes.KEY_ENTER || (kc == 113 && GXT.isWindows)) && !isVisible()) {
            M r = grid.getSelectionModel().getSelectedItem();
            if (r != null) {
                int index = this.grid.store.indexOf(r);
                startEditing(index, true);
                e.cancelBubble();
            }
        }
    }

    protected void onHide() {
        super.onHide();
        stopMonitoring();
        grid.getView().focusRow(rowIndex);
        record = null;
        ComponentHelper.doDetach(this);
    }

    @Override
    protected void onRender(Element target, int index) {
        super.onRender(target, index);

        el().makePositionable(true);
        sinkEvents(KeyNav.getKeyEvent().getEventCode());

        swallowEvent(Events.OnKeyDown, el().dom, false);
        swallowEvent(Events.OnKeyUp, el().dom, false);
        swallowEvent(Events.OnKeyPress, el().dom, false);

        if (renderButtons) {
            createButtons();
            ComponentHelper.setParent(this, btns);
        }
        toolTipAlignWidget = new Html();
        toolTipAlignWidget.render(getElement("bwrap"), 0);

    }

    protected void onRowClick(GridEvent<M> e) {
        if (clicksToEdit != ClicksToEdit.TWO) {
            startEditing(e.getRowIndex(), false);
            deferFocus(e.getColIndex());
        }
    }

    protected void onRowDblClick(GridEvent<M> e) {
        if (clicksToEdit == ClicksToEdit.TWO) {
            startEditing(e.getRowIndex(), false);
            deferFocus(e.getColIndex());
        }
    }

    protected void onShow() {
        super.onShow();
        if (monitorValid) {
            startMonitoring();
        }
    }

    protected void onTab(ComponentEvent ce) {
        Element target = ce.getTarget();
        Component c = findField(target);
        if (saveBtn != null && c != null && ce.isShiftKey() && indexOf(c) == 0) {
            ce.stopEvent();
            saveBtn.focus();
            return;
        }
    }

    protected void positionButtons() {
        if (isVisible()) {
            GridView view = grid.getView();
            int scroll = view.getScrollState().x;
            int mainBodyWidth = view.scroller.getWidth(true);
            int h = el().getClientHeight();
            if (btns != null) {

                int columnWidth = view.getTotalWidth();
                int width = columnWidth < mainBodyWidth ? columnWidth : mainBodyWidth;

                int bw = btns.getWidth(true);
                this.btns.setPosition((width / 2) - (bw / 2) + scroll, h - 2);
            }

            if (toolTipAlignWidget != null) {
                toolTipAlignWidget.setStyleAttribute("position", "absolute");
                toolTipAlignWidget
                        .setSize(mainBodyWidth - (view.scroller.isScrollableY() ? XDOM.getScrollBarWidth() : 0), h);
                toolTipAlignWidget.setPosition(scroll, Style.DEFAULT);
            }
        }
    }

    protected void showTooltip(SafeHtml msg) {
        if (tooltip == null) {
            ToolTipConfig config = new ToolTipConfig();
            config.setAutoHide(false);
            config.setMouseOffset(new int[] { 0, 0 });
            config.setTitle(getMessages().getErrorTipTitleText());
            config.setAnchor("left");
            tooltip = new ToolTip(toolTipAlignWidget, config);
            tooltip.setMaxWidth(600);
        }
        ToolTipConfig config = tooltip.getToolTipConfig();
        config.setHtml(msg);
        tooltip.update(config);
        tooltip.enable();
        if (!tooltip.isAttached()) {
            tooltip.show();
            tooltip.el().updateZIndex(0);
        }
    }

    protected void startMonitoring() {
        if (!bound && monitorValid) {
            bound = true;
            if (monitorTimer == null) {
                monitorTimer = new Timer() {
                    @Override
                    public void run() {
                        RowEditor.this.bindHandler();
                    }
                };
            }
            monitorTimer.scheduleRepeating(monitorPoll);
        }
    }

    protected void stopMonitoring() {
        bound = false;
        if (monitorTimer != null) {
            monitorTimer.cancel();
        }
        hideToolTip();
    }

    protected void verifyLayout(boolean force) {
        if (initialized && (isVisible() || force)) {
            Element row = (Element) grid.getView().getRow(rowIndex);

            setSize(El.fly(row).getWidth(false), renderButtons ? btns.getHeight() : 0);

            syncSize();

            ColumnModel cm = grid.getColumnModel();
            for (int i = 0, len = cm.getColumnCount(); i < len; i++) {
                if (!cm.isHidden(i)) {
                    Field<?> f = (Field<?>) getItem(i);
                    f.show();
                    f.getElement().setAttribute("gxt-dindex", "" + cm.getDataIndex(i));
                    MarginData md = (MarginData) ComponentHelper.getLayoutData(f);
                    f.setWidth(cm.getColumnWidth(i) - md.getMargins().left - md.getMargins().right);
                } else {
                    getItem(i).hide();
                }
            }
            layout(true);
            positionButtons();
        }
    }

    private native void clearParent(Widget parent) /*-{
                                                   parent.@com.google.gwt.user.client.ui.Widget::parent = null;
                                                   }-*/;
}