org.eclipse.gmf.runtime.common.ui.util.TreeInlineTextEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gmf.runtime.common.ui.util.TreeInlineTextEditor.java

Source

/******************************************************************************
 * Copyright (c) 2002, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IBM Corporation - initial API and implementation 
 ****************************************************************************/

package org.eclipse.gmf.runtime.common.ui.util;

import java.util.List;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.contentassist.ContentAssistHandler;

import org.eclipse.gmf.runtime.common.ui.contentassist.ContentAssistantHelper;
import org.eclipse.gmf.runtime.common.ui.internal.l10n.CommonUIMessages;

/**
 * A class that enables inline-text editing for tree nodes
 * 
 * @author Yasser Lulu
 *  
 */

public class TreeInlineTextEditor {

    /**
     * a tree editor used to aid in editing the nodes
     */
    private TreeEditor treeEditor;

    /**
     * the tree whose nodes are being edited
     */
    private Tree tree;

    /**
     * a text widget displayed to enable user string input
     */
    private Text textEditor;

    /**
     * a composite parent for the text widget
     */
    private Composite textEditorParent;

    /**
     * a zero size rectangle to force the text widget to disappear from screen
     * when ending editing
     */
    private static final Rectangle nullRectangle = new Rectangle(0, 0, 0, 0);

    /**
     * the final text entered and commited by the user
     */
    private String finalText;

    /**
     * the initial string displayed when editing started
     */
    private String initialText;

    /**
     * the edit string source and sink
     */
    private IEditStringProvider editStringProvider;

    /**
     * the tree item currently being edited
     */
    private TreeItem treeItem;

    /**
     * the viewer encapsulating the tree to edit
     */
    private TreeViewer viewer;

    /**
     * the text action hanndler
     */
    private IInlineTextActionHandler textActionHandler;

    /**
     * flag for disabling F2
     */
    private boolean isF2disabled;

    /**
     * A semaphore that prevents re-entrance into the endEdit() behavior.
     */
    private volatile boolean inEndEdit;

    /**
     * content assistant handler
     */
    private ContentAssistHandler contentAssistHandler = null;

    /**
     * content assist background color
     */
    private Color proposalPopupBackgroundColor;

    /**
     * content assist foreground color
     */
    private Color proposalPopupForegroundColor;

    /**
     * Returns the isF2disabled.
     * 
     * @return boolean
     */
    private boolean isF2disabled() {
        return isF2disabled;
    }

    /**
     * Sets the isF2disabled.
     * 
     * @param isF2disabled
     *            The isF2disabled to set
     */
    private void setIsF2disabled(boolean isF2disabled) {
        this.isF2disabled = isF2disabled;
    }

    /**
     * returns the tree viewer
     * 
     * @return TreeViewer
     */
    private TreeViewer getTreeViewer() {
        return viewer;
    }

    /**
     * sets the tree viewer
     * 
     * @param viewer
     *            the TreeViewer to set
     */
    private void setTreeViewer(TreeViewer viewer) {
        this.viewer = viewer;
    }

    /**
     * return the currently edited tree item, null if none
     * 
     * @return TreeItem the tree-item currently being edited or null if none
     */
    private TreeItem getTreeItem() {
        return treeItem;
    }

    /**
     * returns the edit string provider
     * 
     * @return IEditStringProvider theedit string provider
     */
    private IEditStringProvider getEditStringProvider() {
        return editStringProvider;
    }

    /**
     * return the tree-editor
     * 
     * @return TreeEditor the tree-editor
     */
    private TreeEditor getTreeEditor() {
        return treeEditor;
    }

    /**
     * sets the tree-editor
     * 
     * @param treeEditor
     *            The TreeEditor
     */
    private void setTreeEditor(TreeEditor treeEditor) {
        this.treeEditor = treeEditor;
    }

    /**
     * Constructor for TreeInlineTextEditor.
     */
    public TreeInlineTextEditor(TreeViewer treeViewer, IActionBars actionBars, List disableActionsIds,
            IEditStringProvider editStringProvider) {
        this(treeViewer, editStringProvider, false);
        initTextActionHandler(actionBars, disableActionsIds);
    }

    /**
     * Constructor for TreeInlineTextEditor.
     * 
     * @param treeViewer the tree viewer
     * @param editStringProvider
     * @param isF2disabled boolean flag indicating whether F2 is disabled
     */
    public TreeInlineTextEditor(TreeViewer treeViewer, IEditStringProvider editStringProvider,
            boolean isF2disabled) {
        setTreeViewer(treeViewer);
        setTree(treeViewer.getTree());
        setIsF2disabled(isF2disabled);
        setEditStringProvider(editStringProvider);
        createControl();
        init();
    }

    /**
     * answers whether this inline-editor has been disposed
     * 
     * @return boolean indicating its dispose status
     */
    public boolean isDisposed() {
        return ((getTextEditorParent() == null) || (getTextEditorParent().isDisposed()));
    }

    /**
     * answers if we can start editing
     * 
     * @return boolean indicating if we can start editing
     */
    public boolean canEdit() {
        return ((isDisposed() == false) && (getTree().isDisposed() == false) && (getTree().getEnabled())
                && (getTree().getVisible()) && (isSelectedItemEditable()));
    }

    /**
     * Answers whether the currently selected tree-item meets all the editablity
     * criteria
     * 
     * @return boolean true if editable, false otherwise
     */
    private boolean isSelectedItemEditable() {
        return ((getTree().getSelection().length == 1) && (getTree().getSelection()[0].isDisposed() == false)
                && (getTree().getSelection()[0].getData() != null)
                && getEditStringProvider().canEdit(getTree().getSelection()[0].getData()));
    }

    /**
     * starts the editing process
     */
    public void startEdit() {
        while (Display.getCurrent().readAndDispatch()) {
            // process handler.setEnabled(false) queued in
            // hide() before re-entering content assist mode
        }

        if (canEdit()) {
            cancelEdit();
            setTreeItem(getTree().getSelection()[0]);
            setInitialText(getEditStringProvider().getEditString(getTreeItem().getData()));
            getTextEditor().setText(getInitialText());
            getTreeEditor().setItem(getTreeItem());

            show();
        }
    }

    /**
     * cancels the editing process
     */
    public void cancelEdit() {
        if (canProceed()) {
            hide();
        }
    }

    /**
     * ends the editing process
     */
    public void endEdit() {
        if (inEndEdit) {
            // prevent re-entrance into this method when we are already
            //   ending the edit. This prevents, in particular, the focus
            //   listener from ending the edit while we are ending it
            return;
        }

        inEndEdit = true;
        try {
            if (canProceed()) {
                setFinalText(getCurrentText());
                if (getFinalText().equals(getInitialText()) == false) {
                    final Object obj = getTreeItem().getData();
                    getEditStringProvider().setEditString(obj, getFinalText());
                    // Hide first so that the focus change when the dialog opens
                    // doesn't attempt to re-enter "endEdit()".

                    //RATLC00529737
                    //ask the label-provider to the update tree-node's label in order to
                    //display the newly entered name  
                    getTreeItem().getDisplay().asyncExec(new Runnable() {

                        public void run() {
                            if (!isDisposed()) {
                                getTreeViewer().update(obj, null);
                            }
                        }
                    });
                    hide();
                } else {
                    hide();
                }
            }
        } finally {
            inEndEdit = false;
        }
    }

    /**
     * Opens an error dialog for the specified status object.
     * 
     * @param status
     *            The status object for which to open an error dialog.
     *  
     */
    protected void openErrorDialog(IStatus status) {
        ErrorDialog.openError(getShell(), CommonUIMessages.TreeInlineTextEditor_errorDialogTitle, null, status);
    }

    private Shell getShell() {
        return getTree().getShell();
    }

    /**
     * answers if it is ok to continue operation
     * 
     * @return boolean indicating if it is ok to continue operation
     */
    private boolean canProceed() {
        return isDisposed() == false && getTreeItem() != null && !getTreeItem().isDisposed();
    }

    /**
     * displays the text editing widget
     */
    private void show() {
        uninstallContentAssist(false);

        if (getTreeItem() != null) {
            IContentAssistProcessor processor = getEditStringProvider()
                    .getCompletionProcessor(getTreeItem().getData());
            if (processor != null) {
                // install content assist
                contentAssistHandler = ContentAssistantHelper.createTextContentAssistant(getTextEditor(),
                        proposalPopupForegroundColor, proposalPopupBackgroundColor, processor);
            }
        }
        getTextEditorParent().setEnabled(true);
        getTextEditorParent().setVisible(true);
        getTextEditor().setEnabled(true);
        getTextEditor().setVisible(true);
        adjustTextEditorBounds();
        getTextEditorParent().redraw();
        getTextEditor().selectAll();
        getTextEditor().setFocus();

        if (getTextActionHandler() != null) {
            getTextActionHandler().hookHandlers();
        }
    }

    /**
     * hides the text editing widget
     */
    private void hide() {
        setTreeItem(null);
        getTreeEditor().setItem(null);

        getTextEditor().setVisible(false);
        getTextEditor().setEnabled(false);

        getTextEditorParent().setBounds(getNullRectangle());
        getTextEditorParent().setVisible(false);
        getTextEditorParent().setEnabled(false);

        if (getTextActionHandler() != null) {
            getTextActionHandler().unHookHandlers();
        }

        uninstallContentAssist(true);
    }

    /**
     * Uninstalls content assist on the text widget, if installed
     * 
     * @param fork
     *            whether to queue the uninstall or not
     */
    private void uninstallContentAssist(boolean fork) {
        if (contentAssistHandler != null) {
            // uninstall content assist
            final ContentAssistHandler localHandler = contentAssistHandler;
            contentAssistHandler = null;
            if (fork) {
                Display.getCurrent().asyncExec(new Runnable() {

                    public void run() {
                        // Content Assist hack - queue disablement, otherwise
                        // cleanup on focus lost won't happen
                        localHandler.setEnabled(false);

                    }
                });
            } else {
                localHandler.setEnabled(false);
            }
        }
    }

    /**
     * Disposes the text widget and reset the editorText field. 
     * It becomes unreusable afterwards
     */
    public void dispose() {
        if (getTextEditorParent() != null) {
            if (getTextActionHandler() != null) {
                getTextActionHandler().dispose();
            }
            setTextActionHandler(null);
            setTextEditorParent(null);
            setTextEditor(null);
            getTreeEditor().setEditor(null, null);
            setTreeEditor(null);
            setTree(null);

            proposalPopupBackgroundColor.dispose();
            proposalPopupForegroundColor.dispose();
        }
    }

    /**
     * creates the text widget and its parent composite
     */
    private void createControl() {
        setTextEditorParent(new Composite(getTree(), SWT.NONE));
        setTreeEditor(new TreeEditor(getTree()));
        getTreeEditor().horizontalAlignment = SWT.LEFT;
        getTreeEditor().grabHorizontal = true;
        getTreeEditor().setEditor(getTextEditorParent(), null);
        getTextEditorParent().setVisible(false);
        setTextEditor(new Text(getTextEditorParent(), SWT.NONE));
        getTextEditorParent().setBackground(getTextEditor().getBackground());

        proposalPopupBackgroundColor = new Color(getShell().getDisplay(), new RGB(254, 241, 233));
        proposalPopupForegroundColor = new Color(getShell().getDisplay(), new RGB(0, 0, 0));

    }

    /**
     * initializes the controls and listeners needed to manage the editing
     * process
     */
    private void init() {
        getTextEditorParent().addListener(SWT.Paint, new Listener() {

            public void handleEvent(Event e) {
                Point textSize = getTextEditor().getSize();
                Point parentSize = getTextEditorParent().getSize();
                e.gc.drawRectangle(0, 0, Math.min(textSize.x + 4, parentSize.x - 1), parentSize.y - 1);
            }
        });

        getTextEditor().addListener(SWT.Modify, new Listener() {

            public void handleEvent(Event e) {
                adjustTextEditorBounds();
                getTextEditorParent().redraw();
            }
        });

        getTextEditor().addKeyListener(new KeyAdapter() {

            public void keyReleased(KeyEvent event) {
                if (event.character == SWT.CR) {
                    endEdit();
                } else if (event.character == SWT.ESC) {
                    cancelEdit();
                }
            }
        });

        getTextEditor().addFocusListener(new FocusAdapter() {

            public void focusGained(FocusEvent e) {
                return;
            }

            public void focusLost(FocusEvent fe) {
                Shell activeShell = fe.display.getActiveShell();
                if (activeShell != null && getTextEditor().getShell().equals(activeShell.getParent())) {
                    /*
                     * CONTENT ASSIST: focus is lost to the content assist pop
                     * up - stay in focus
                     */
                    return;
                }

                if ((getTreeViewer().getSelection().isEmpty() == false) && canProceed()) {
                    final Object obj = getTreeItem().getData();
                    getTreeItem().getDisplay().asyncExec(new Runnable() {

                        public void run() {
                            if (!isDisposed()) {
                                getTreeViewer().update(obj, null);
                            }
                        }
                    });
                }
                endEdit();
            }
        });

        getTreeViewer().addSelectionChangedListener(new ISelectionChangedListener() {

            public void selectionChanged(SelectionChangedEvent event) {
                cancelEdit();
            }
        });

        if (!isF2disabled()) {
            getTree().addKeyListener(new KeyAdapter() {

                public void keyReleased(KeyEvent event) {
                    if (event.keyCode == SWT.F2) {
                        startEdit();
                    }
                }
            });
        }
    }

    /**
     * initializes the text action handlers
     * 
     * @param actionBars
     *            The IActionBars for the view-site to retarget global
     *            edit-cut-copy events for the text box
     * @param disableActionsIds
     *            a List of global actions ids that are non-Eclipse and which
     *            we'll have to disable
     */
    private void initTextActionHandler(IActionBars actionBars, List disableActionsIds) {
        if (actionBars == null) {
            return;
        }

        actionBars.getMenuManager().addMenuListener(new IMenuListener() {

            public void menuAboutToShow(IMenuManager manager) {
                cancelEdit();
            }
        });

        setTextActionHandler(new InlineTextActionHandler(actionBars, getTextEditor(), disableActionsIds));

    }

    /**
     * adjusts the bounds of the text widget
     */
    private void adjustTextEditorBounds() {
        Point textSize = getTextEditor().computeSize(SWT.DEFAULT, SWT.DEFAULT);
        textSize.x += textSize.y; // increase width a little
        Point parentSize = getTextEditorParent().getSize();
        getTextEditor().setBounds(2, 1, Math.min(textSize.x, parentSize.x - 4), parentSize.y - 2);
    }

    /**
     * Get the Tree being edited.
     * 
     * @returnTree
     */
    private Tree getTree() {
        return tree;
    }

    /**
     * return the current text
     * 
     * @return String the text currently in the text widget, or null if it is
     *         disposed already
     */
    public String getCurrentText() {
        return (canProceed()) ? getTextEditor().getText() : null;
    }

    /**
     * returns the initial value when editing started
     * 
     * @return String the last initial value used when editing last started
     */
    public String getInitialText() {
        return initialText;
    }

    /**
     * returns the comitted string by the user when editing ended (not-canclled)
     * 
     * @return String the comitted string by the user
     */
    public String getFinalText() {
        return finalText;
    }

    /**
     * returns the text widget
     * 
     * @return Text the text widget used for editing
     */
    private Text getTextEditor() {
        return textEditor;
    }

    /**
     * returns the text widget parent composite
     * 
     * @return Composite text widget parent composite
     */
    private Composite getTextEditorParent() {
        return textEditorParent;
    }

    /**
     * Sets the editStringProvider.
     * 
     * @param editStringProvider
     *            The editStringProvider to set
     */
    private void setEditStringProvider(IEditStringProvider editStringProvider) {
        this.editStringProvider = editStringProvider;
    }

    /**
     * Sets the textEditor.
     * 
     * @param textEditor
     *            The textEditor to set
     */
    private void setTextEditor(Text textEditor) {
        this.textEditor = textEditor;
    }

    /**
     * Sets the textEditorParent.
     * 
     * @param textEditorParent
     *            The textEditorParent to set
     */
    private void setTextEditorParent(Composite textEditorParent) {
        this.textEditorParent = textEditorParent;
    }

    /**
     * Sets the tree.
     * 
     * @param tree
     *            The tree to set
     */
    private void setTree(Tree tree) {
        this.tree = tree;
    }

    /**
     * Sets the treeItem.
     * 
     * @param treeItem
     *            The treeItem to set
     */
    private void setTreeItem(TreeItem treeItem) {
        this.treeItem = treeItem;
    }

    /**
     * Returns the nullRectangle.
     * 
     * @return Rectangle
     */
    private Rectangle getNullRectangle() {
        return nullRectangle;
    }

    /**
     * Sets the finalText.
     * 
     * @param finalText
     *            The finalText to set
     */
    private void setFinalText(String finalText) {
        this.finalText = finalText;
    }

    /**
     * Sets the initialText.
     * 
     * @param initialText
     *            The initialText to set
     */
    private void setInitialText(String initialText) {
        this.initialText = initialText;
    }

    /**
     * Returns the textActionHandler.
     * 
     * @return TextActionHandler
     */
    private IInlineTextActionHandler getTextActionHandler() {
        return textActionHandler;
    }

    /**
     * Sets the textActionHandler.
     * 
     * @param textActionHandler
     *            The textActionHandler to set
     */
    private void setTextActionHandler(IInlineTextActionHandler textActionHandler) {
        this.textActionHandler = textActionHandler;
    }

}